Zum Inhalt springen

Run Postgres with CloudNativePG

Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.

Most real applications need Postgres, and running it well on Kubernetes used to be the argument against running it on Kubernetes at all. CloudNativePG (CNPG) changed that: a CNCF operator that manages PostgreSQL clusters declaratively — provisioning, replication, failover, backups — from one short manifest. This guide goes from nothing to a backed-up database your app can use. The steps work on any conformant cluster; we use a paasbox cluster as the example.

  • A cluster and its kubeconfig — the quickstart gets you one in ten minutes.
  • kubectl installed locally.
  • For the backup section: an S3-compatible bucket and its access keys — Hetzner Object Storage works well here (it lives in the same locations as your cluster and lands on your own Hetzner bill), as does any other S3-compatible store.

1. Install the operator (once per cluster)

Section titled “1. Install the operator (once per cluster)”
Terminal window
kubectl apply --server-side -f \
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.30/releases/cnpg-1.30.0.yaml

This installs the operator into cnpg-system (release 1.30 as of 2026-07-09 — check CNPG releases for current). From here on, Postgres clusters are just Kubernetes resources.

2. A single-instance database (dev, staging)

Section titled “2. A single-instance database (dev, staging)”

Save as pg.yaml — this is the exact shape a small app starts with:

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg
namespace: myapp
spec:
instances: 1
# A zero-eviction PDB would block node drains and protects nothing with a
# single replica. Re-enable (the default) when instances > 1.
enablePDB: false
bootstrap:
initdb:
database: myapp
owner: myapp
storage:
size: 2Gi
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 512Mi
Terminal window
kubectl create namespace myapp
kubectl apply -f pg.yaml
kubectl -n myapp wait --for=condition=Ready cluster/pg --timeout=300s

The 2 Gi volume is provisioned as a Hetzner volume in your project — it appears on your Hetzner bill, resizes online when you edit storage.size, and persists through hibernation, so your data survives a sleeping cluster.

CNPG generates everything your app needs. For a cluster named pg, you get Services pg-rw (primary, read-write), pg-ro (replicas, read-only), and a Secret pg-app containing ready-made credentials — including a uri key of the form postgresql://myapp:…@pg-rw.myapp:5432/myapp. Most frameworks consume that directly:

env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: pg-app
key: uri

No password handling, no manual Secret creation — the operator rotates and owns the credentials.

For production, change one number:

spec:
instances: 3

CNPG runs one primary and two streaming replicas, spreads them across nodes, and fails over automatically if the primary’s node dies — the pg-rw Service always points at the current primary, so your app doesn’t change. Remove the enablePDB: false line (the default PodDisruptionBudget now protects you during node drains), and make sure the cluster has at least three schedulable workers — see scale & autoscale workloads.

Volumes protect against pod restarts, not against DROP TABLE. CNPG does continuous WAL archiving plus scheduled base backups to object storage. Create a Secret with your bucket credentials, then add the backup section:

Terminal window
kubectl -n myapp create secret generic pg-backup-creds \
--from-literal=ACCESS_KEY_ID=<your-access-key> \
--from-literal=ACCESS_SECRET_KEY=<your-secret-key>
spec:
backup:
barmanObjectStore:
destinationPath: s3://my-backup-bucket/pg
endpointURL: https://nbg1.your-objectstorage.com # your S3-compatible endpoint
s3Credentials:
accessKeyId:
name: pg-backup-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: pg-backup-creds
key: ACCESS_SECRET_KEY
retentionPolicy: 30d

Then schedule a nightly base backup:

apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
name: pg-nightly
namespace: myapp
spec:
cluster:
name: pg
schedule: "0 0 3 * * *" # 03:00 daily (six-field cron, seconds first)

With WAL archiving running continuously between base backups, you get point-in-time recovery: a new Cluster manifest with a bootstrap.recovery section restores to any moment inside your retention window — worth rehearsing once before you need it for real.

A declarative Postgres with automated failover, credentials your app consumes from a Secret, and off-cluster backups with point-in-time recovery — the checklist that used to justify a managed database bill. What remains yours is the semantics: schema migrations, data quality, and testing the restore path. (The platform backs up your cluster’s control plane; your database content is exactly the kind of application data that’s yours to protect — this setup is how.)

To see it inside a full application, the deploy a Django SaaS guide uses this exact CNPG pattern as its database layer.

Terminal window
kubectl delete namespace myapp # removes the cluster and its volumes (backups in the bucket remain)