Running PostgreSQL on Minikube with CloudNativePG

Kubernetes has become the standard platform for running distributed, resilient workloads, and PostgreSQL is no exception. The Cloud Native PostgreSQL (CNPG) operator extends Kubernetes with custom resources that let you manage PostgreSQL clusters declaratively, handling everything from instance scaling to backups through ordinary manifests and kubectl commands.

Here is a practical walkthrough for getting the CNPG operator running on a local Minikube environment and deploying your first single-instance cluster.

Installing the Operator

The quickest way to install CNPG is to apply its core manifest directly from the project's release page:

kubectl apply -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.26/releases/cnpg-1.26.0.yaml

That single manifest provisions all of the operator's prerequisites:

  • Dedicated namespace: The cnpg-system namespace is created to host the operator components.
  • Custom Resource Definitions (CRDs): New resource types such as Cluster, Backup, ScheduledBackup, Publication, and Subscription are registered, enabling you to manage PostgreSQL objects directly with kubectl.
  • RBAC policies: The necessary ClusterRole, ClusterRoleBinding, and ServiceAccount objects are set up so the operator can interact with the cluster securely.
  • Admission webhooks: Mutating and validating configurations are deployed to check and, where needed, adjust custom resources as they are created or changed.
  • Controller manager: The cnpg-controller-manager deployment is placed in the cnpg-system namespace. This component watches your CRDs and coordinates PostgreSQL operations.

Confirming the Operator Is Ready

After applying the manifest, check that the controller pod is running:

kubectl rollout status deployment -n cnpg-system cnpg-controller-manager

A successful rollout returns output similar to:

deployment "cnpg-controller-manager" successfully rolled out

Once the deployment reports ready, the operator can accept cluster definitions.

To keep subsequent commands concise and correctly scoped, it can help to set the default context to the cnpg-system namespace:

kubectl config set-context --current --namespace=cnpg-system

From then on, every kubectl command targets that namespace unless you override it with -n.

Defining a Simple PostgreSQL Cluster

The next step is to declare the database cluster itself. Save the following manifest as single.yaml:

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: cluster-example
spec:
  instances: 1

  storage:
    size: 1Gi

The manifest contains two core fields:

  • instances: 1 sets the number of PostgreSQL pods to run.
  • storage.size: 1Gi requests a 1-gigabyte persistent volume for the database data.

Apply the manifest to create the cluster:

kubectl apply -f single.yaml

If accepted, you will see a confirmation similar to:

cluster.postgresql.cnpg.io/cluster-example created

The operator now handles provisioning the PostgreSQL instance. Verify the pod status with:

kubectl get pods

The output shows the cluster-example-1 pod running:

NAME               READY  STATUS   RESTARTS  AGE
cluster-example-1  1/1    Running  0         2m

Connecting to the Database

With the pod up, open an interactive shell inside the container:

kubectl exec -it pod/cluster-example-1 -- /bin/bash

Once the shell prompt appears, launch the PostgreSQL client:

Defaulted container "postgres" out of: postgres, bootstrap-controller (init)
psql
psql (17.5 (Debian 17.5-1.pgdg110+1))
Type "help" for help.

postgres=# \du
                                 List of roles
     Role name     |                         Attributes                         
-------------------+------------------------------------------------------------
 app               | 
 postgres          | Superuser, Create role, Create DB, Replication, Bypass RLS
 streaming_replica | Replication


postgres=# \l
                                                List of databases
   Name    |  Owner   | Encoding | Locale Provider | Collate | Ctype | Locale | ICU Rules |   Access privileges   
-----------+----------+----------+-----------------+---------+-------+--------+-----------+-----------------------
 app       | app      | UTF8     | libc            | C       | C     |        |           | 
 postgres  | postgres | UTF8     | libc            | C       | C     |        |           | 
 template0 | postgres | UTF8     | libc            | C       | C     |        |           | =c/postgres          +
           |          |          |                 |         |       |        |           | postgres=CTc/postgres
 template1 | postgres | UTF8     | libc            | C       | C     |        |           | =c/postgres          +
           |          |          |                 |         |       |        |           | postgres=CTc/postgres
(4 rows)

Because the cluster manifest did not specify any database options, CNPG automatically creates an app user and an app database, with the former as the owner of the latter. You can connect using that default user and database directly, or create additional roles and databases as needed.

What CNPG Brings to the Table

The process above shows how CNPG turns complex database administration into simple, repeatable operations. A production-grade PostgreSQL deployment, including scaling, backups, and failover in larger configurations, is expressed entirely in YAML and managed through the Kubernetes API. For a local environment such as Minikube, it provides a convenient way to experiment with cloud-native database workflows before moving to a full production cluster.