postgres-provisioner

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.


What it does

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.


Architecture


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.


Installation

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

Requirements


apiVersion: v1
kind: Secret
metadata:
  name: pg-superuser
  namespace: app-db
stringData:
  username: postgres
  password: <superuser-password>

Configuration

Default values:


image:
  repository: drewsonne/postgres-provisioner
  tag: latest

postgres:
  host: pg-rw.app-db.svc.cluster.local
  secretName: pg-superuser

namespace: app-db

Usage

PostgresDatabase

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

PostgresUser

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

Per-CR credential override

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

Output

Both CRDs create a Secret containing:


kubectl get secret my-app-db-credentials -n my-app -o yaml

Behaviour notes


Observability


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.


Development

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

License

MIT


Helm Chart Releases

VersionApp VersionCreatedDownload
1.0.191.0.192026-04-19T16:38:33postgres-provisioner-1.0.19.tgz
1.0.181.0.182026-04-19T16:26:23postgres-provisioner-1.0.18.tgz
1.0.171.0.172026-04-19T16:21:58postgres-provisioner-1.0.17.tgz
1.0.161.0.162026-04-19T16:02:29postgres-provisioner-1.0.16.tgz
1.0.151.0.152026-04-18T20:04:07postgres-provisioner-1.0.15.tgz
1.0.141.0.142026-04-18T19:48:20postgres-provisioner-1.0.14.tgz
1.0.131.0.132026-04-18T19:30:27postgres-provisioner-1.0.13.tgz
1.0.121.0.122026-04-18T19:28:09postgres-provisioner-1.0.12.tgz
1.0.111.0.112026-04-18T19:23:15postgres-provisioner-1.0.11.tgz
1.0.101.0.102026-04-18T19:23:15postgres-provisioner-1.0.10.tgz
1.0.91.0.92026-04-18T19:23:15postgres-provisioner-1.0.9.tgz
1.0.81.0.82026-04-18T19:23:15postgres-provisioner-1.0.8.tgz
1.0.71.0.72026-04-18T19:23:15postgres-provisioner-1.0.7.tgz
1.0.61.0.62026-04-18T19:23:15postgres-provisioner-1.0.6.tgz
1.0.51.0.52026-04-18T19:23:15postgres-provisioner-1.0.5.tgz
1.0.41.0.42026-04-18T19:23:15postgres-provisioner-1.0.4.tgz
1.0.31.0.32026-04-18T19:23:15postgres-provisioner-1.0.3.tgz
1.0.21.0.22026-04-18T19:23:15postgres-provisioner-1.0.2.tgz
1.0.11.0.12026-04-18T19:23:15postgres-provisioner-1.0.1.tgz
1.0.01.0.02026-04-18T19:23:15postgres-provisioner-1.0.0.tgz
0.1.40.1.42026-04-18T19:23:15postgres-provisioner-0.1.4.tgz
0.1.30.1.32026-04-18T19:23:15postgres-provisioner-0.1.3.tgz
0.1.00.1.02026-04-18T19:23:15postgres-provisioner-0.1.0.tgz

Helm Repository Index (index.yaml)