Files
home-network-k3s/decisions/adr-0019-postgresql-cloudnativepg.md
T
jakman85andClaude Opus 5.5 250fb54154 docs: bring the specification in line with the accepted implementation
The operator ruled the implementation repo's main (50c8719) the latest
and accepted state. Sync the spec to it.

Accepted changes are reflected: one bare-metal environment, TrueNAS bulk
storage, R2 off-cluster backup, CloudNativePG, single-node VPN gateway
with Cilium fail-closed, inbound WireGuard, per-app auth sidecars,
Renovate, the CI gate as approver, and the pm-agent. New ADR-0017..0025;
ADR-0016 corrected and accepted; R-* IDs unchanged, eight amended.

Where the implementation has simply not met a requirement, the
requirement stands and the shortfall is recorded in docs/16-known-gaps.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-08 22:46:00 -06:00

3.0 KiB

ADR-0019 — PostgreSQL via CloudNativePG

  • Status: Accepted
  • Date: 2026-11-08
  • Accepted by: operator ruling of 2026-11-08 that the implementation is the accepted state; effective on merge.
  • Serves: R-IDP-05, R-STATE-01, R-STATE-02, R-STATE-03
  • Supersedes: —

Context

ADR-0014 requires Keycloak's PostgreSQL to live on replicated Longhorn, be DR-critical and be backed up off-cluster; it does not say how PostgreSQL is run. Nextcloud and Semaphore need PostgreSQL too.

The first design used the Bitnami Keycloak chart's bundled PostgreSQL. Bitnami's free images were purged or relocated, so a deploy on stock tags fails to pull.

Single-instance PostgreSQL also proved insufficient on its own terms. Longhorn replication preserves the data through a node loss but not the service. With one instance each, all three primaries drifted onto the same node, so one node failure would have taken Nextcloud, Semaphore and SSO down together.

Decision

Run every platform PostgreSQL database under the CloudNativePG operator, one cluster per database:

  • Three instances with hard anti-affinity, one per node, each on the replicated longhorn class.
  • Off-cluster recovery through Barman: continuous WAL archiving plus a daily base backup to the object store (ADR-0018), retained 30 days.
  • Primary changes roll by switchover rather than an in-place restart.
  • Standbys are added only behind a working archive, because they replicate corruption and are not a backup.

Scope: services that need PostgreSQL. An application with a small embedded datastore (Gitea's SQLite, for example) keeps it on its Longhorn PVC.

Alternatives considered

  • Bitnami-bundled PostgreSQL — images no longer dependably available from the free registry.
  • Single-instance PostgreSQL on Longhorn — keeps the data through a node loss but loses the service until the pod reschedules, and gives no point-in-time recovery by itself.
  • Relying on file-system volume backup alone — no point-in-time recovery; a kopia copy of a live data directory is the weaker path. It is retained as a secondary copy, not as the database backup.
  • PostgreSQL embedded in each application's chart — one lifecycle per application, with backup and failover solved separately each time.

Consequences

  • Failover takes seconds, and every database has one declarative point-in-time recovery path.
  • WAL archiving is an availability dependency. If archiving fails, PostgreSQL correctly refuses to recycle WAL and the volume fills, so archive health is alerted on as an availability signal.
  • Storage cost multiplies: three instances on three-way replicated volumes means nine copies of each database. Accepted, because the databases are small.
  • The operator's custom resources need CRDs before they apply, which fits the platform's controllers-then-configs ordering.
  • Detail: platform/cnpg/ and the database manifests in the implementation repo.