A lightweight Kubernetes operator that provisions PostgreSQL databases and users declaratively via CRDs.
Applications can request their own database, credentials, and schema-level access without manual SQL or shared admin access.
The operator manages two CRD kinds:
| Kind | What it provisions |
|---|---|
PostgresDatabase |
A PostgreSQL database and owner role, with credentials stored in a K8s Secret |
PostgresUser |
A read-only or read-write user in an existing database, with schema-level grants stored in a K8s Secret |
All resources are reconciled continuously — the operator detects and repairs out-of-band drift every 5 minutes.
PostgresDatabase
└── PostgresUser (ownerRef → Database; cascade-deleted with database)
└── K8s Secret (ownerRef → CR; GC'd with CR)
Deleting a PostgresDatabase CR triggers K8s garbage collection of all child PostgresUser CRs, which revoke grants and drop roles before the database CR itself is cleaned up.
Add the Helm repository:
helm repo add postgres-provisioner https://drewsonne.github.io/postgres-provisioner
helm repo update
Install the chart:
helm install postgres-provisioner postgres-provisioner/postgres-provisioner \
--namespace app-db \
--set postgres.host=pg-rw.app-db.svc.cluster.local \
--set postgres.secretName=pg-superuser
apiVersion: v1
kind: Secret
metadata:
name: pg-superuser
namespace: app-db
stringData:
username: postgres
password: <superuser-password>
Default values:
image:
repository: drewsonne/postgres-provisioner
tag: latest
postgres:
host: pg-rw.app-db.svc.cluster.local
secretName: pg-superuser
namespace: app-db
Creates a PostgreSQL database, an owner role, and a K8s Secret with connection credentials.
apiVersion: pgprovisioner.drewsonne.github.io/v1
kind: PostgresDatabase
metadata:
name: my-app-db
namespace: app-db
spec:
dbName: my_app
owner: my_app_owner
secretName: my-app-db-credentials
secretNamespace: my-app # optional; defaults to CR namespace
Creates a login role with read or readwrite access to an existing database. Grants are applied at the schema level (not just database level), including ALTER DEFAULT PRIVILEGES for future tables.
apiVersion: pgprovisioner.drewsonne.github.io/v1
kind: PostgresUser
metadata:
name: my-app-reader
namespace: app-db
spec:
username: my_app_reader
dbName: my_app
access: read # read | readwrite
schemas: # optional; defaults to all user schemas
- public
- analytics
secretName: my-app-reader-credentials
secretNamespace: my-app # optional; defaults to CR namespace
access values:
| Value | Grants |
|---|---|
read |
USAGE on schemas, SELECT on all tables, ALTER DEFAULT PRIVILEGES for SELECT |
readwrite |
USAGE + CREATE on schemas, SELECT/INSERT/UPDATE/DELETE on tables, USAGE on sequences, ALTER DEFAULT PRIVILEGES for DML + sequences |
Every CR accepts an optional host and superuserSecret to use a different PostgreSQL cluster (useful for multi-tenant setups):
spec:
host: other-pg-rw.other-ns.svc.cluster.local
superuserSecret:
name: other-pg-superuser
namespace: other-ns
Both CRDs create a Secret containing:
username — the PostgreSQL role namepassword — the role passworddatabase — the target database namehost — the PostgreSQL hostport — 5432
kubectl get secret my-app-db-credentials -n my-app -o yaml
password is preserved rather than regeneratedPostgresDatabase CR removes the K8s Secret but intentionally does NOT drop the PostgreSQL database or owner role, preventing accidental data lossPostgresUser CR revokes all schema grants, revokes CONNECT, drops the role, and removes the SecretPostgresUser grants are applied per-schema with ALTER DEFAULT PRIVILEGES, so future tables created in the schema are automatically accessiblekubectl describe as a K8s Warning event and in the Drift printer columnPostgresDatabase CR triggers cleanup of all child PostgresUser CRs before the database CR is finalisedPostgresUser references a database that doesn't exist yet, the controller retries automatically until it is ready
kubectl get postgresdatabases
kubectl get postgresusers
Printer columns include Ready, Drift, and Age. Drift events are also emitted as Kubernetes Warning events visible via kubectl describe.
Build locally:
docker build -t drewsonne/postgres-provisioner:dev .
Run locally (requires a reachable PostgreSQL instance):
docker run --rm \
-e PG_HOST=pg-rw.app-db.svc.cluster.local \
-e PG_USER=postgres \
-e PG_PASSWORD=... \
drewsonne/postgres-provisioner:dev
MIT
| Version | App Version | Created | Download |
|---|---|---|---|
| 1.0.19 | 1.0.19 | 2026-04-19T16:38:33 | postgres-provisioner-1.0.19.tgz |
| 1.0.18 | 1.0.18 | 2026-04-19T16:26:23 | postgres-provisioner-1.0.18.tgz |
| 1.0.17 | 1.0.17 | 2026-04-19T16:21:58 | postgres-provisioner-1.0.17.tgz |
| 1.0.16 | 1.0.16 | 2026-04-19T16:02:29 | postgres-provisioner-1.0.16.tgz |
| 1.0.15 | 1.0.15 | 2026-04-18T20:04:07 | postgres-provisioner-1.0.15.tgz |
| 1.0.14 | 1.0.14 | 2026-04-18T19:48:20 | postgres-provisioner-1.0.14.tgz |
| 1.0.13 | 1.0.13 | 2026-04-18T19:30:27 | postgres-provisioner-1.0.13.tgz |
| 1.0.12 | 1.0.12 | 2026-04-18T19:28:09 | postgres-provisioner-1.0.12.tgz |
| 1.0.11 | 1.0.11 | 2026-04-18T19:23:15 | postgres-provisioner-1.0.11.tgz |
| 1.0.10 | 1.0.10 | 2026-04-18T19:23:15 | postgres-provisioner-1.0.10.tgz |
| 1.0.9 | 1.0.9 | 2026-04-18T19:23:15 | postgres-provisioner-1.0.9.tgz |
| 1.0.8 | 1.0.8 | 2026-04-18T19:23:15 | postgres-provisioner-1.0.8.tgz |
| 1.0.7 | 1.0.7 | 2026-04-18T19:23:15 | postgres-provisioner-1.0.7.tgz |
| 1.0.6 | 1.0.6 | 2026-04-18T19:23:15 | postgres-provisioner-1.0.6.tgz |
| 1.0.5 | 1.0.5 | 2026-04-18T19:23:15 | postgres-provisioner-1.0.5.tgz |
| 1.0.4 | 1.0.4 | 2026-04-18T19:23:15 | postgres-provisioner-1.0.4.tgz |
| 1.0.3 | 1.0.3 | 2026-04-18T19:23:15 | postgres-provisioner-1.0.3.tgz |
| 1.0.2 | 1.0.2 | 2026-04-18T19:23:15 | postgres-provisioner-1.0.2.tgz |
| 1.0.1 | 1.0.1 | 2026-04-18T19:23:15 | postgres-provisioner-1.0.1.tgz |
| 1.0.0 | 1.0.0 | 2026-04-18T19:23:15 | postgres-provisioner-1.0.0.tgz |
| 0.1.4 | 0.1.4 | 2026-04-18T19:23:15 | postgres-provisioner-0.1.4.tgz |
| 0.1.3 | 0.1.3 | 2026-04-18T19:23:15 | postgres-provisioner-0.1.3.tgz |
| 0.1.0 | 0.1.0 | 2026-04-18T19:23:15 | postgres-provisioner-0.1.0.tgz |