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]>
51 lines
3.0 KiB
Markdown
51 lines
3.0 KiB
Markdown
# 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](adr-0014-idp-keycloak.md) 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](adr-0018-storage-backup-truenas-r2.md)), 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](https://gitea.home.keithjackson.dev/Home-Network/home-network-k3s-impl).
|