6.5 KiB
apps-pg Shared PostgreSQL Cluster
apps-pg is the shared CloudNativePG cluster for S5 application
databases. It lives in the databases namespace and is owned by
railiance-platform as an S3 platform service.
Cluster Identity
- CNPG Cluster:
apps-pg - Namespace:
databases - PostgreSQL major version:
16 - Primary endpoint:
apps-pg-rw.databases.svc.cluster.local:5432 - Read-only endpoint:
apps-pg-ro.databases.svc.cluster.local:5432 - Bootstrap database:
apps_meta - Bootstrap role:
apps_admin
apps_admin is a platform bootstrap and smoke-test role. Do not copy it
into application namespaces, use it in S5 runtime configuration, or treat
it as a consumer credential.
Which cluster this is
Two reachable clusters each carry a CNPG Cluster named apps-pg in a
namespace named databases. The one this document describes is
railiance01 (k3s v1.35.1) — it also carries platform-pg and forgejo-db,
and holds both apps-pg consumers (vergabe_db, coulomb_social_db). The other
cluster carries gitea-db and only one apps-pg consumer.
Selecting the right one by kubeconfig filename is not safe: KUBECONFIG is an
environment variable that overrides the Makefile default, and both files
resolve to a 127.0.0.1 tunnel port. Every apps-pg-* target therefore runs
railiance01-guard first, which compares the live kube-system namespace UID
against RAILIANCE01_CLUSTER_UID and fails closed on mismatch or
unreachability.
make cluster-id # what KUBECONFIG currently selects
make railiance01-guard # assert it is railiance01, or refuse
If a target refuses, the fix is KUBECONFIG=~/.kube/config-railiance01, not
--force and not editing the pinned UID. db-status deliberately targets the
other cluster for gitea-db, which is why the guard is per-target.
Consumer Onboarding
Each S5 application gets its own role, database, and runtime Secret. The current flow is platform-administered until OpenBao or a dedicated database onboarding automation owns the lifecycle end to end.
-
Request an app database in the consuming repo workplan. Include the app name, namespace, database name, role name, intended owners, and any required extensions.
-
Platform reviews and approves the database/role names. Names should be app-scoped, for example
vergabeandvergabe_db. -
Platform provisions the backing PostgreSQL role and credential for the approved app. Until automation exists, this is a controlled operator SQL action against
apps-pg, not a self-service repo apply. -
Add a CNPG
Databasemanifest in the platform-managed database manifests, withspec.cluster.name: apps-pgandspec.ownerset to the approved role. -
Label the consuming namespace so NetworkPolicy allows access:
kubectl label namespace <app-namespace> \ railiance.io/postgres-client=apps-pg -
Publish or mirror the runtime Secret into the consumer namespace. The Secret should contain only that app's role credential and DSN fields.
-
Wire the DSN into the application Helm values or runtime configuration. Prefer the RW service for migrations and writes:
postgresql://<role>:<password>@apps-pg-rw.databases.svc.cluster.local:5432/<database>.
Example CNPG Database resource:
apiVersion: postgresql.cnpg.io/v1
kind: Database
metadata:
name: vergabe-db
namespace: databases
spec:
cluster:
name: apps-pg
name: vergabe_db
owner: vergabe
CNPG Boundary
CNPG 1.28 provides a standalone Database CRD. It does not provide a
standalone Role CRD in this cluster. Role lifecycle is cluster-scoped,
through Cluster.spec.managed.roles or a controlled SQL workflow.
Consumer repos must therefore not assume they can create PostgreSQL roles themselves. They can request a database and consume a runtime Secret after the platform role has been provisioned.
Network Policy
The databases namespace has a default-deny posture. apps-pg accepts
client traffic on TCP/5432 only from namespaces labeled:
railiance.io/postgres-client=apps-pg
The CNPG operator in cnpg-system is allowed to manage the cluster on
the standard PostgreSQL, instance manager, and metrics ports.
Consumers (live)
| App | Role | Database | Password secret (databases ns) | Consumer ns label |
|---|---|---|---|---|
| vergabe-teilnahme | vergabe |
vergabe_db |
vergabe-app-credentials |
vergabe-teilnahme |
| coulomb-social | coulomb_social |
coulomb_social_db |
coulomb-social-app-credentials |
coulomb-social |
Bootstrapped 2026-08-09 on railiance01: cluster healthy; both Database CRs applied; coulomb-social connectivity smoke from labeled consumer ns OK.
Backup And Roadmap
apps-pg remains a conservative single-instance, 10Gi cluster. The desired
state now includes continuous WAL archival, a daily 02:15 UTC base backup and
30-day retention under the distinct platform-pg/apps-pg/ object-store
prefix. The credential lane and its bucket-policy top-level prefix are shared
with platform-pg; the backup data path beneath that prefix is not.
The declared ceiling is three consumers. Each gets at most 20 connections;
40 of the explicit 100-connection aggregate remains for CNPG and operator
headroom. Memory (1Gi limit), not the clean connection refusal, is treated as
the binding safety constraint. A fourth consumer goes to the named
apps-pg-2 overflow substrate, which must be provisioned before onboarding.
Reviewed, unapplied source for that cell is
helm/apps-pg-2-{cluster,backup,networkpolicies}.yaml; it uses a distinct
bootstrap Secret and backup prefix. make apps-pg-verify-capacity rejects a
fourth role on either cell, missing connection limits, duplicate roles, missing
resource envelopes, or a reused backup path.
statement_timeout and idle_in_transaction_session_timeout are 15 seconds
per consumer role. CNPG 1.28 cannot express those role settings, so
helm/apps-pg-consumer-controls.sql is the idempotent controlled-operator
step; connectionLimit remains declaratively reconciled by CNPG. The same SQL
removes PostgreSQL's default PUBLIC CONNECT/TEMPORARY grants and grants
each consumer role access only to its own database.
Resource evidence for resource:railiance:apps-pg (capacity, recovery,
labor, allocation drivers) is published under
docs/evidence/RAILIANCE-WP-0016-apps-pg-resource-evidence.md. The
2026-08-14 observation remains historically correct. Live backup, restore and
isolation evidence captured on 2026-08-20 is in
docs/evidence/RPF-WP-0019-backup-restore-2026-08-20.md and
docs/evidence/RPF-WP-0019-isolation-2026-08-20.md.