Database & migrations
If <app> needs Postgres, CNPG (the
operator installed cluster-wide in the cnpg-system namespace)
provisions it from a Cluster CR. CNPG auto-generates an
application Secret whose uri key is the connection string. The app and the
migrations Job both read DB_URL from it.
flowchart LR Cluster[CNPG Cluster<br/>app-pg] -->|auto-creates| Secret[(Secret<br/>app-pg-app)] Secret -->|uri → DB_URL| Job[migrations Job<br/>PreSync hook] Secret -->|uri → DB_URL| Rollout[Rollout pods] Job -->|runs first| Rollout Cluster -->|WAL + base backup| OOS[(Object Storage)]
CNPG Cluster
Section titled “CNPG Cluster”Single instance (one node, no HA). bootstrap.initdb creates the database and an owner role; CNPG then creates the
<app>-pg-app secret. The env and backup blocks configure WAL archiving + base backups to Oracle
Object Storage (S3-compatible).
apiVersion: postgresql.cnpg.io/v1kind: Clustermetadata: name: <app>-pg namespace: <app>-<env>spec: instances: 1
# Oracle S3-compat backups: disable AWS SDK checksum trailers (Oracle returns # NotImplemented) and sign with the bucket's real region. env: - name: AWS_REQUEST_CHECKSUM_CALCULATION value: when_required - name: AWS_RESPONSE_CHECKSUM_VALIDATION value: when_required - name: AWS_DEFAULT_REGION value: <region>
storage: size: 5Gi storageClass: local-path # k3s default (node-local disk)
bootstrap: initdb: database: <app> owner: <app>
resources: requests: cpu: 100m memory: 256Mi limits: memory: 512Mi
# Continuous backup + WAL archiving to Object Storage; creds in Secret # <app>-pg-backup-creds (created out of band, see Secrets). backup: retentionPolicy: "7d" barmanObjectStore: destinationPath: "s3://<bucket>/" endpointURL: "https://<os-namespace>.compat.objectstorage.<region>.oraclecloud.com" s3Credentials: accessKeyId: name: <app>-pg-backup-creds key: ACCESS_KEY_ID secretAccessKey: name: <app>-pg-backup-creds key: ACCESS_SECRET_KEY wal: compression: gzip data: compression: gzipThe <app>-pg-backup-creds secret is created by hand (Oracle Customer Secret Key). See Secrets.
On this platform, staging overlays set retentionPolicy: "3d"; prod and Infisical stay 7d.
barmanObjectStore is deprecated upstream (removed in CNPG 1.30); migrate to the Barman Cloud plugin later.
Nightly base backup
Section titled “Nightly base backup”Pair the cluster with a ScheduledBackup so WAL archiving + a nightly base gives PITR across the retention window. CNPG
cron is 6-field (seconds first).
apiVersion: postgresql.cnpg.io/v1kind: ScheduledBackupmetadata: name: <app>-pg-daily namespace: <app>-<env>spec: schedule: "0 0 2 * * *" # 02:00 daily backupOwnerReference: self cluster: name: <app>-pgReading DB_URL from the auto-generated secret
Section titled “Reading DB_URL from the auto-generated secret”CNPG creates Secret/<app>-pg-app automatically. Its uri key holds a ready-to-use connection string. Wire it
straight into DB_URL:
env: - name: DB_URL valueFrom: secretKeyRef: name: <app>-pg-app # auto-created by CNPG from the Cluster name key: uriYou never write this secret yourself, and it never goes in Git. The same secretKeyRef
is used in both the Rollout (from Deploy & reconcile) and the migrations Job below.
Migrations: two valid patterns, pick one
Section titled “Migrations: two valid patterns, pick one”Penvoice applies its schema in-process at startup: no separate Job, no bundled
migrate CLI. That’s the simpler default and what to reach for first: fewer moving parts, one less thing that can drift
from the app’s actual code.
The alternative. Worth it if you need migrations to strictly complete before any new pod serves traffic. Is a
PreSync hook Job, using the migrate CLI bundled into the image (from Build & CI). The
Argo CD PreSync hook ordering guarantees migrations complete before the Rollout updates; hook-delete-policy: BeforeHookCreation cleans up the previous run.
apiVersion: batch/v1kind: Jobmetadata: name: <app>-migrate namespace: <app>-<env> annotations: argocd.argoproj.io/hook: PreSync argocd.argoproj.io/hook-delete-policy: BeforeHookCreationspec: backoffLimit: 3 activeDeadlineSeconds: 300 template: metadata: labels: app: <app>-migrate spec: restartPolicy: Never containers: - name: migrate image: ghcr.io/<owner>/<app>:sha-XXXXXXX command: ["/app/migrate"] args: ["-path", "/app/migrations", "-database", "$(DB_URL)", "up"] env: - name: DB_URL valueFrom: secretKeyRef: name: <app>-pg-app key: uriKeep the Job’s image tag in lockstep with the Rollout’s tag so migrations and code ship together.
Canary & metrics. Expose /metrics and gate the canary on the success rate.