Running
PostgreSQL on Kubernetes used to feel like a daunting challenge reserved for platform engineering teams with deep operational expertise. That reality has changed. With
CloudNativePG (CNPG), deploying a production-grade PostgreSQL cluster on Kubernetes is now a declarative, repeatable, and remarkably approachable task. In this guide, we will set sail and walk through installing CloudNativePG on a local Minikube environment, then create your very first single-instance PostgreSQL cluster from scratch.
Whether you are a database administrator exploring the cloud-native world for the first time, or a Kubernetes practitioner who wants to bring stateful workloads under control, this tutorial gives you a clear, hands-on path. By the end, you will have a running PostgreSQL instance that you can connect to with
psql, all managed through simple YAML manifests.
Why CloudNativePG and PostgreSQL on Kubernetes?
The cloud is no longer optional. It has woven itself into virtually every corner of the modern IT landscape, and organizations that have not yet embraced it are increasingly finding themselves at a competitive disadvantage. For teams building dynamic, scalable applications, the question is not
if they will adopt cloud infrastructure, but
when.
At the heart of modern cloud infrastructure sits
Kubernetes. It is the orchestrator that brings order and efficiency to distributed computing, providing a resilient foundation for everything from simple web services to sprawling microservices architectures. Kubernetes is not just another tool in the box; it has become the backbone of the cloud-native future.
Historically, though, databases were considered the awkward guest at the Kubernetes party. Stateful workloads like PostgreSQL demand persistent storage, careful failover handling, and predictable scaling behavior. This is precisely the gap that
CloudNativePG fills. CNPG is a Kubernetes operator that lets you manage PostgreSQL clusters declaratively, handling provisioning, high availability, backups, scaling, and rolling updates, all through native Kubernetes manifests.
Because CloudNativePG follows the Kubernetes operator pattern, it extends the cluster with custom resources that describe your desired PostgreSQL state. The operator then works continuously to make reality match that description. This means less manual intervention, fewer error-prone scripts, and a consistent workflow that fits naturally into GitOps pipelines.
Prerequisites Before You Begin
Before we install CloudNativePG, make sure your workstation is ready. This walkthrough targets a local development setup, which is perfect for learning and experimentation. You will need a running Minikube cluster, the
kubectl command-line tool configured to talk to that cluster, and a stable internet connection to pull the operator manifest and container images.
Minikube is ideal here because it spins up a single-node Kubernetes environment on your laptop in minutes, giving you a safe sandbox to explore CNPG without touching production infrastructure. Once Minikube is up and
kubectl get nodes reports a ready node, you are set to proceed.
Applying the CNPG Operator Manifest
First things first: we need to install the CloudNativePG operator itself. The operator is the brain of the whole system, the component that watches your custom resources and turns them into running PostgreSQL clusters. Installing it is a single command that applies the official release manifest directly from the CloudNativePG GitHub repository.
kubectl apply -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.26/releases/cnpg-1.26.0.yaml
Although this looks like a single, unassuming line, a great deal happens behind the scenes. Applying this CloudNativePG manifest deploys several critical building blocks into your cluster:
- Namespaces: It creates a dedicated
cnpg-system namespace where the operator itself will live, keeping its components neatly isolated from your application workloads.
- Custom Resource Definitions (CRDs): This is where the real magic begins. CNPG registers brand-new Kubernetes resource types such as
Cluster, Backup, ScheduledBackup, Publication, and Subscription. These CRDs let you manage every aspect of PostgreSQL directly through kubectl.
- RBAC (Role-Based Access Control): The manifest sets up the necessary ClusterRoles, ClusterRoleBindings, and ServiceAccounts. These control permissions and ensure the operator can interact with your cluster securely.
- Webhooks: Mutating and validating webhook configurations are deployed. They validate your custom resources and can inject sensible default values as resources are created or updated.
- Controller Deployment: The
cnpg-controller-manager deployment is created inside the cnpg-system namespace. This is the heart of the operator, responsible for watching your CRDs and orchestrating PostgreSQL operations.
Verifying the Operator Deployment
Once the manifest is applied, it is always wise to confirm that the controller has deployed successfully and is running without issues. We can check the rollout status of the deployment with the following command:
kubectl rollout status deployment -n cnpg-system cnpg-controller-manager
If everything went smoothly, you should see output similar to this:
deployment "cnpg-controller-manager" successfully rolled out
This message confirms that the CloudNativePG operator is up, healthy, and ready to accept your PostgreSQL cluster definitions. If the rollout appears stuck, give the pods a moment to pull their images, then re-run the command.
Setting Your Namespace Context (Optional but Recommended)
From this point forward, we will interact primarily within the
cnpg-system namespace, especially when defining and managing our PostgreSQL clusters. To save yourself repetitive typing and avoid accidentally targeting the wrong namespace, you can set your default Kubernetes context:
kubectl config set-context --current --namespace=cnpg-system
After running this, all subsequent
kubectl commands will automatically operate within the
cnpg-system namespace, unless you explicitly override it with the
-n flag. It is a small quality-of-life improvement that makes the rest of the workflow smoother.
Your Minikube environment is now equipped with the powerful CloudNativePG operator. You are ready to start defining and managing PostgreSQL clusters in a truly cloud-native way. So let's create our very first cluster. We will keep things intentionally simple with a single-instance database, avoiding fancy options for now so you can focus on the fundamentals.
Preparing Your PostgreSQL Cluster Manifest
The beauty of CloudNativePG is that an entire PostgreSQL cluster can be described in a compact YAML file. Create a manifest named
single.yaml with the following content:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cluster-example
spec:
instances: 1
storage:
size: 1Gi
Despite its brevity, this manifest tells CloudNativePG everything it needs to know:
instances: 1 instructs CNPG to deploy a single PostgreSQL pod for this cluster. In production, you would typically raise this number to enable high availability with streaming replicas.
storage.size: 1Gi requests a one-gigabyte persistent volume to hold your PostgreSQL data. Because the storage is persistent, your data survives pod restarts and rescheduling.
This declarative approach is the essence of the cloud-native philosophy. You describe
what you want, and the operator figures out
how to achieve and maintain it.
Applying the Manifest to Create Your Cluster
With
single.yaml ready, it is time to ask Kubernetes to bring your PostgreSQL cluster to life:
kubectl apply -f single.yaml
If everything goes well, you will see confirmation that the cluster resource was created:
cluster.postgresql.cnpg.io/cluster-example created
Kubernetes has accepted your request, and the CloudNativePG operator immediately begins its work: provisioning the persistent volume, bootstrapping the PostgreSQL instance, configuring the required users, and wiring up the internal services that make the cluster reachable.
Validating the Deployment
Let's confirm that our PostgreSQL pod is actually up and running. Check the status of your pods with:
kubectl get pods
The output should show your
cluster-example-1 pod in a
Running state, ready to accept connections:
NAME READY STATUS RESTARTS AGE
cluster-example-1 1/1 Running 0 2m
Seeing
1/1 under the READY column and
Running under STATUS means the CloudNativePG operator has successfully provisioned your database. If the pod is still initializing, wait a few moments and run the command again while the image is pulled and the instance bootstraps.
Accessing PostgreSQL with psql
Now for the satisfying part: connecting to your brand-new PostgreSQL instance. We will open an interactive shell inside the pod's container and then launch the
psql client from within it.
kubectl exec -it pod/cluster-example-1 -- /bin/bash
After a few initial messages, you will land inside the container. From there, simply type
psql to enter the PostgreSQL interactive terminal:
Defaulted container "postgres" out of: postgres, bootstrap-controller (init)
psql
psql (17.5 (Debian 17.5-1.pgdg110+1))
Type "help" for help.
You are now connected to PostgreSQL running inside Kubernetes. Let's inspect the roles that CloudNativePG created automatically using the
\du meta-command:
postgres=# \du
List of roles
Role name | Attributes
-------------------+------------------------------------------------------------
app |
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS
streaming_replica | Replication
And let's list the databases with the
\l command:
postgres=# \l
List of databases
Name | Owner | Encoding | Locale Provider | Collate | Ctype
-----------+----------+----------+-----------------+---------+-------
app | app | UTF8 | libc | C | C
postgres | postgres | UTF8 | libc | C | C
template0 | postgres | UTF8 | libc | C | C
template1 | postgres | UTF8 | libc | C | C
(4 rows)
Notice something interesting here. Even though our manifest did not specify any database or user options, CloudNativePG automatically created an
app user and an
app database, with the
app user set as the owner. This is CNPG's sensible default: it provisions a ready-to-use application database so you are not forced to connect as the superuser for everyday workloads. The
streaming_replica role is also created in advance to support replication when you scale the cluster later.
Understanding What Just Happened
Take a step back and appreciate how much you accomplished with so little effort. In just a handful of commands and two small YAML files, you installed a full Kubernetes operator, defined a PostgreSQL cluster declaratively, watched Kubernetes provision persistent storage and a running instance, and connected to it with the standard
psql client.
Traditionally, achieving this would have involved manually configuring storage, writing init scripts, managing users, and stitching together services by hand. CloudNativePG collapses all of that complexity into a clean, repeatable workflow. Because everything is expressed as Kubernetes resources, your database configuration can be version-controlled, code-reviewed, and rolled out through the same pipelines you already use for the rest of your applications.
Where to Go From Here
This tutorial deliberately kept the cluster minimal so you could focus on the essentials. In a real deployment, CloudNativePG offers a rich set of capabilities worth exploring next. You can raise the
instances count to create a high-availability cluster with automatic failover, configure continuous backups and point-in-time recovery to object storage, define scheduled backups, tune PostgreSQL parameters directly in the manifest, and manage connection pooling with PgBouncer through the built-in Pooler resource.
As you grow more comfortable, you can layer in monitoring with Prometheus metrics, set resource requests and limits, and integrate the whole setup into a GitOps workflow using tools like Argo CD or Flux. Each of these features follows the same declarative pattern you have already learned, so the mental model scales gracefully as your requirements grow.
Conclusion
Getting started with CloudNativePG on Minikube is a powerful first step toward managing PostgreSQL in a genuinely cloud-native way. With just one or two YAML manifests and a few basic
kubectl commands, we deployed a production-grade database system on Kubernetes, verified it, and connected to it directly with
psql. Tasks that were once complex and manual become simple, declarative, and repeatable.
CloudNativePG bridges the long-standing gap between stateful databases and Kubernetes, giving PostgreSQL a first-class home in the cloud-native ecosystem. Whether you are experimenting on your laptop or planning a large-scale production rollout, the fundamentals you learned here form a solid foundation. If you need expert help running PostgreSQL in production, explore our
PostgreSQL consulting and support services. Now that your cluster is up and sailing, you are ready to explore the deeper waters of high availability, backups, and automated recovery that make CNPG such a compelling choice for running PostgreSQL on Kubernetes.