Quick Start: Your First PostgreSQL Cluster in 15 Minutes

This tutorial walks you from a fresh install of the Alauda CloudNativePG operator to a running 3-instance PostgreSQL cluster with backup configured. Each section is sized to a few minutes; total wall-clock is about 15 minutes if your cluster is responsive.

Prerequisites (5 min)

You need:

  1. An ACP managed cluster running Kubernetes 1.30+ with:
    • A default block-mode StorageClass (typically a topolvm class; verify with kubectl get sc).
    • cert-manager installed (required for the Barman Cloud backup plugin's TLS prerequisites). Verify with kubectl api-resources --api-group=cert-manager.io. If absent, install it via the marketplace before continuing.
  2. Cluster admin or platform admin access for the install steps. Day-2 operations (creating clusters, backups) only require namespace admin on the target namespace.
  3. kubectl configured for the target cluster (the ACP web console's Download kubeconfig action is the simplest path).
  4. An S3-compatible object store for backups (MinIO, Ceph RGW, or any S3 endpoint reachable from the cluster), with access keys and a bucket you can write to.

If you don't yet have the operator installed, read Installation first and come back here once the CSV is Succeeded.

Step 1 — Install the operator (3 min)

Skip this step if you already followed Installation.

# install.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: cnpg-system
---
apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
  name: cnpg-system-og
  namespace: cnpg-system
spec: {}
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
  name: cloudnative-pg
  namespace: cnpg-system
spec:
  channel: stable
  installPlanApproval: Automatic
  name: cloudnative-pg
  source: platform
  sourceNamespace: cpaas-system

Apply and wait for the CSV to reach Succeeded:

kubectl apply -f install.yaml
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded \
  -n cnpg-system csv -l operators.coreos.com/cloudnative-pg.cnpg-system='' \
  --timeout=5m

The operator pod (cnpg-controller-manager-* in cnpg-system) should show Running 1/1 within ~60 seconds of Succeeded.

Step 2 — Your first PostgreSQL cluster (4 min)

Create a workload namespace and apply a 3-instance Cluster. With no image specified, the operator uses the PostgreSQL version shipped with this product release — the simplest, supported default:

# my-cluster.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: pg-demo
---
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: my-postgres
  namespace: pg-demo
spec:
  instances: 3
  storage:
    size: 2Gi
    # storageClass: <name>   # omit to use the default StorageClass
  bootstrap:
    initdb:
      database: app
      owner: app
TIP

To pick a specific PostgreSQL major version, set spec.imageName to a PostgreSQL operand image from your platform registry, or create a ClusterImageCatalog and reference it with spec.imageCatalogRef — see Configuration (Images section). A ClusterImageCatalog is not installed by default.

kubectl apply -f my-cluster.yaml
kubectl wait --for=jsonpath='{.status.phase}'='Cluster in healthy state' \
  -n pg-demo cluster.postgresql.cnpg.io/my-postgres --timeout=5m
WARNING

Always address CNPG clusters as cluster.postgresql.cnpg.io in kubectl commands. On ACP clusters the bare cluster shortname resolves to a different resource (clusters.cluster.x-k8s.io from Cluster API), and commands like kubectl wait cluster/my-postgres fail with NotFound.

A 3-instance cluster typically reaches Cluster in healthy state in two to five minutes (image pull time dominates on first use). The first instance is the primary; the other two stream replication from it.

Verify with a smoke SQL query through the writable -rw service, using the same operand image the cluster runs:

# Pull the auto-generated app-user password
PGPASSWORD=$(kubectl get secret my-postgres-app -n pg-demo \
  -o jsonpath='{.data.password}' | base64 -d)

# The image the cluster is running (use it for the client too)
IMG=$(kubectl get cluster.postgresql.cnpg.io my-postgres -n pg-demo \
  -o jsonpath='{.status.image}')

# Connect through the cluster's writable service (-rw routes to the primary)
kubectl run psql-client --rm -it --restart=Never \
  --image="$IMG" --env=PGPASSWORD="$PGPASSWORD" -n pg-demo \
  -- psql -h my-postgres-rw -U app -d app \
  -c "CREATE TABLE hello (msg text);" \
  -c "INSERT INTO hello VALUES ('first row');" \
  -c "SELECT * FROM hello;" \
  -c "SELECT version();"

Expected output includes the row you inserted and a PostgreSQL 18.x ... version line.

Step 3 — Configure backup to object storage (3 min)

Backups go through the Barman Cloud plugin, which is included in the Alauda CNPG bundle and bootstrapped automatically by the operator on startup (assuming cert-manager is present).

First, create a Kubernetes Secret with the S3 credentials. Avoid passing secrets on the command line — use --from-file against short-lived temp files:

TMP=$(mktemp -d) && umask 077
printf '%s' '<your-access-key>'  > "$TMP/ACCESS_KEY_ID"
printf '%s' '<your-secret-key>'  > "$TMP/ACCESS_SECRET_KEY"
kubectl -n pg-demo create secret generic s3-creds \
  --from-file=ACCESS_KEY_ID="$TMP/ACCESS_KEY_ID" \
  --from-file=ACCESS_SECRET_KEY="$TMP/ACCESS_SECRET_KEY"
rm -rf "$TMP"

Then create an ObjectStore resource describing where backups land, and attach the Barman Cloud plugin to the Cluster:

# backup.yaml
apiVersion: barmancloud.cnpg.io/v1
kind: ObjectStore
metadata:
  name: my-backup-store
  namespace: pg-demo
spec:
  configuration:
    destinationPath: s3://<bucket>/quickstart/my-postgres/
    endpointURL: http://<your-s3-endpoint>
    s3Credentials:
      accessKeyId:     { name: s3-creds, key: ACCESS_KEY_ID }
      secretAccessKey: { name: s3-creds, key: ACCESS_SECRET_KEY }
    wal:  { compression: gzip, maxParallel: 4 }
    data: { compression: gzip }
  retentionPolicy: "30d"
---
# Patch the Cluster to use the plugin for backups + WAL archiving
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: my-postgres
  namespace: pg-demo
spec:
  instances: 3
  storage:
    size: 2Gi
  bootstrap:
    initdb:
      database: app
      owner: app
  plugins:
  - name: barman-cloud.cloudnative-pg.io
    isWALArchiver: true
    parameters:
      barmanObjectName: my-backup-store
kubectl apply -f backup.yaml
WARNING

Attaching the plugin triggers a rolling restart of the instances (the plugin sidecar is injected into each pod). Wait for the cluster to return to Cluster in healthy state before triggering a backup — a Backup created mid-rollout fails with requested plugin is not available: barman-cloud.cloudnative-pg.io.

kubectl wait --for=jsonpath='{.status.phase}'='Cluster in healthy state' \
  -n pg-demo cluster.postgresql.cnpg.io/my-postgres --timeout=5m

Trigger an immediate backup:

# trigger-backup.yaml
apiVersion: postgresql.cnpg.io/v1
kind: Backup
metadata:
  name: my-postgres-first-backup
  namespace: pg-demo
spec:
  cluster: { name: my-postgres }
  method: plugin
  pluginConfiguration:
    name: barman-cloud.cloudnative-pg.io
kubectl apply -f trigger-backup.yaml
kubectl wait --for=jsonpath='{.status.phase}'=completed \
  -n pg-demo backup/my-postgres-first-backup --timeout=3m

A small cluster's first backup typically completes in 10–30 seconds. Verify the artifacts in S3 using mc (or any S3 client):

mc alias set my-store http://<your-s3-endpoint> \
  '<your-access-key>' '<your-secret-key>'
mc ls --recursive my-store/<bucket>/quickstart/my-postgres/
# Expect:  base/<timestamp>/{backup.info,data.tar.gz}
#          wals/0000000100000000/*.gz

You now have a self-managed PostgreSQL cluster with HA replication and S3-backed point-in-time recovery.

What's next

  • Configuration — parameters, users and databases, synchronous replication, storage, images.
  • How-To: Ops Runbook — day-2 operations including failover, scaling, upgrades, and restore.
  • How-To: Grafana dashboards — wire the built-in metrics into the platform Prometheus and import the curated dashboards.
  • Migrating from Zalando — moving existing Zalando postgres-operator clusters to CloudNativePG.
  • Architecture — how primary election, replication, and plugins work.

Troubleshooting

SymptomCauseFix
kubectl wait timing out on CSV SucceededOLM still resolving the channel; or cert-manager not installedkubectl describe csv in cnpg-system and check the install conditions
kubectl wait cluster/my-postgres fails NotFound while the cluster existsbare cluster resolves to Cluster API's resourceUse cluster.postgresql.cnpg.io/my-postgres (see the warning in Step 2)
Cluster stuck Setting up primary with ImagePullBackOffThe default operand image is not reachable from this cluster's nodesSet spec.imageName to the PostgreSQL operand image in your platform registry (see Configuration)
PVC stuck PendingNo default StorageClasskubectl get sc to confirm a default exists, or add storageClass: to spec.storage
Backup phase: failed with requested plugin is not availableBackup triggered before the plugin-attach rollout finishedWait for Cluster in healthy state, then re-create the Backup
Backup phase: failed with no such hostObjectStore endpoint not reachable from cluster nodesUse a cluster-internal address for endpointURL

For configuration-level issues, see the troubleshooting quick reference.