PostgreSQL Examples¶
Complete examples for PostgreSQL database provisioning.
Basic Setup¶
Step 1: Admin Credentials¶
apiVersion: v1
kind: Secret
metadata:
name: postgres-admin-credentials
type: Opaque
stringData:
username: postgres
password: your-admin-password
Step 2: Database Instance¶
apiVersion: dbops.dbprovision.io/v1alpha1
kind: DatabaseInstance
metadata:
name: postgres-primary
spec:
engine: postgres
connection:
host: postgres.database.svc.cluster.local
port: 5432
database: postgres
sslMode: prefer
secretRef:
name: postgres-admin-credentials
healthCheck:
enabled: true
intervalSeconds: 30
timeoutSeconds: 5
Step 3: Database¶
apiVersion: dbops.dbprovision.io/v1alpha1
kind: Database
metadata:
name: myapp-database
spec:
instanceRef:
name: postgres-primary
name: myapp
deletionPolicy: Retain
postgres:
encoding: UTF8
extensions:
- name: uuid-ossp
schema: public
- name: pgcrypto
schema: public
Step 4: Application User¶
apiVersion: dbops.dbprovision.io/v1alpha1
kind: DatabaseUser
metadata:
name: myapp-user
spec:
instanceRef:
name: postgres-primary
username: myapp_user
passwordSecret:
generate: true
length: 32
secretName: myapp-user-credentials
secretTemplate:
labels:
app: myapp
data:
DATABASE_URL: "postgresql://{{ .Username }}:{{ .Password }}@{{ .Host }}:{{ .Port }}/myapp?sslmode=prefer"
Role-Based Access¶
Read-Only Role¶
apiVersion: dbops.dbprovision.io/v1alpha1
kind: DatabaseRole
metadata:
name: myapp-readonly
spec:
instanceRef:
name: postgres-primary
roleName: myapp_readonly
postgres:
login: false
grants:
- database: myapp
schema: public
tables: ["*"]
privileges: [SELECT]
- database: myapp
schema: public
sequences: ["*"]
privileges: [USAGE, SELECT]
Read-Write Role¶
apiVersion: dbops.dbprovision.io/v1alpha1
kind: DatabaseRole
metadata:
name: myapp-readwrite
spec:
instanceRef:
name: postgres-primary
roleName: myapp_readwrite
postgres:
login: false
inRoles: [myapp_readonly]
grants:
- database: myapp
schema: public
tables: ["*"]
privileges: [INSERT, UPDATE, DELETE]
- database: myapp
schema: public
sequences: ["*"]
privileges: [UPDATE]
Assign Role to User¶
apiVersion: dbops.dbprovision.io/v1alpha1
kind: DatabaseGrant
metadata:
name: myapp-user-roles
spec:
userRef:
name: myapp-user
postgres:
roles:
- myapp_readwrite
Multi-Schema Setup¶
Database with Multiple Schemas¶
apiVersion: dbops.dbprovision.io/v1alpha1
kind: Database
metadata:
name: enterprise-app
spec:
instanceRef:
name: postgres-primary
name: enterprise
postgres:
encoding: UTF8
extensions:
- name: uuid-ossp
- name: pg_stat_statements
schemas:
- name: core
owner: enterprise_admin
- name: billing
owner: enterprise_admin
- name: analytics
owner: analytics_admin
- name: audit
owner: enterprise_admin
Schema-Specific Grants¶
apiVersion: dbops.dbprovision.io/v1alpha1
kind: DatabaseGrant
metadata:
name: billing-service-grants
spec:
userRef:
name: billing-service
postgres:
grants:
# Full access to billing schema
- database: enterprise
schema: billing
tables: ["*"]
privileges: [SELECT, INSERT, UPDATE, DELETE]
# Read-only on core schema
- database: enterprise
schema: core
tables: ["*"]
privileges: [SELECT]
# Write to audit
- database: enterprise
schema: audit
tables: [billing_audit]
privileges: [INSERT]
Init SQL¶
Bootstrap your database with tables, indexes, and seed data using initSQL.
Inline SQL¶
apiVersion: dbops.dbprovision.io/v1alpha1
kind: Database
metadata:
name: myapp-database
spec:
instanceRef:
name: postgres-primary
name: myapp
postgres:
extensions:
- name: uuid-ossp
- name: pgcrypto
schemas:
- name: app
initSQL:
inline:
- |
CREATE TABLE IF NOT EXISTS app.users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
- CREATE INDEX IF NOT EXISTS idx_users_email ON app.users (email);
- |
CREATE OR REPLACE FUNCTION app.update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
- |
CREATE TRIGGER trg_users_updated_at
BEFORE UPDATE ON app.users
FOR EACH ROW EXECUTE FUNCTION app.update_updated_at();
failurePolicy: Block
ConfigMap SQL with Block Policy¶
Store longer migration scripts in a ConfigMap. Statements are separated by ---.
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-bootstrap
data:
init.sql: |
CREATE TABLE IF NOT EXISTS app.organizations (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name TEXT UNIQUE NOT NULL,
plan TEXT NOT NULL DEFAULT 'free',
created_at TIMESTAMPTZ DEFAULT now()
);
---
CREATE TABLE IF NOT EXISTS app.members (
org_id UUID REFERENCES app.organizations(id) ON DELETE CASCADE,
user_id UUID REFERENCES app.users(id) ON DELETE CASCADE,
role TEXT NOT NULL DEFAULT 'member',
PRIMARY KEY (org_id, user_id)
);
---
INSERT INTO app.organizations (name, plan)
VALUES ('default', 'enterprise')
ON CONFLICT (name) DO NOTHING;
---
apiVersion: dbops.dbprovision.io/v1alpha1
kind: Database
metadata:
name: myapp-database
spec:
instanceRef:
name: postgres-primary
name: myapp
initSQL:
configMapRef:
name: myapp-bootstrap
key: init.sql
failurePolicy: Block
Writing idempotent init SQL
- Use
CREATE TABLE IF NOT EXISTSandCREATE INDEX IF NOT EXISTS - Use
INSERT ... ON CONFLICT DO NOTHINGfor seed data - Use
CREATE OR REPLACE FUNCTIONfor functions and triggers - Order statements so that referenced tables are created before foreign keys
Backup Configuration¶
One-Time Backup to S3¶
# S3 credentials
apiVersion: v1
kind: Secret
metadata:
name: s3-backup-credentials
type: Opaque
stringData:
AWS_ACCESS_KEY_ID: your-access-key
AWS_SECRET_ACCESS_KEY: your-secret-key
---
# Backup
apiVersion: dbops.dbprovision.io/v1alpha1
kind: DatabaseBackup
metadata:
name: myapp-manual-backup
spec:
databaseRef:
name: myapp-database
storage:
type: s3
s3:
bucket: my-database-backups
region: us-east-1
prefix: postgres/myapp
secretRef:
name: s3-backup-credentials
compression:
enabled: true
algorithm: gzip
postgres:
format: custom
jobs: 4
ttl: "720h" # 30 days
Scheduled Backups¶
apiVersion: dbops.dbprovision.io/v1alpha1
kind: DatabaseBackupSchedule
metadata:
name: myapp-daily-backup
spec:
databaseRef:
name: myapp-database
schedule: "0 2 * * *" # Daily at 2 AM
timezone: "UTC"
retention:
keepLast: 7
keepDaily: 7
keepWeekly: 4
keepMonthly: 3
concurrencyPolicy: Forbid
backupTemplate:
storage:
type: s3
s3:
bucket: my-database-backups
region: us-east-1
prefix: postgres/myapp/scheduled
secretRef:
name: s3-backup-credentials
compression:
enabled: true
algorithm: gzip
postgres:
format: custom
jobs: 4
Restore from Backup¶
apiVersion: dbops.dbprovision.io/v1alpha1
kind: DatabaseRestore
metadata:
name: myapp-restore
spec:
backupRef:
name: myapp-manual-backup
targetDatabaseRef:
name: myapp-database-restored
createTarget: true
postgres:
noOwner: true
jobs: 4
analyze: true
Complete Application Stack¶
Full Example¶
---
# Admin credentials
apiVersion: v1
kind: Secret
metadata:
name: postgres-admin
type: Opaque
stringData:
username: postgres
password: super-secret-admin-password
---
# Database instance
apiVersion: dbops.dbprovision.io/v1alpha1
kind: DatabaseInstance
metadata:
name: postgres-primary
spec:
engine: postgres
connection:
host: postgres.database.svc.cluster.local
port: 5432
database: postgres
sslMode: require
secretRef:
name: postgres-admin
healthCheck:
enabled: true
intervalSeconds: 30
---
# Application database
apiVersion: dbops.dbprovision.io/v1alpha1
kind: Database
metadata:
name: myapp-db
spec:
instanceRef:
name: postgres-primary
name: myapp
deletionPolicy: Retain
deletionProtection: true
postgres:
encoding: UTF8
extensions:
- name: uuid-ossp
- name: pgcrypto
schemas:
- name: app
---
# Read-only role
apiVersion: dbops.dbprovision.io/v1alpha1
kind: DatabaseRole
metadata:
name: myapp-readonly
spec:
instanceRef:
name: postgres-primary
roleName: myapp_readonly
postgres:
login: false
grants:
- database: myapp
schema: app
tables: ["*"]
privileges: [SELECT]
---
# Read-write role
apiVersion: dbops.dbprovision.io/v1alpha1
kind: DatabaseRole
metadata:
name: myapp-readwrite
spec:
instanceRef:
name: postgres-primary
roleName: myapp_readwrite
postgres:
login: false
inRoles: [myapp_readonly]
grants:
- database: myapp
schema: app
tables: ["*"]
privileges: [INSERT, UPDATE, DELETE]
---
# Application user
apiVersion: dbops.dbprovision.io/v1alpha1
kind: DatabaseUser
metadata:
name: myapp-user
spec:
instanceRef:
name: postgres-primary
username: myapp_user
passwordSecret:
generate: true
length: 32
secretName: myapp-credentials
secretTemplate:
data:
DATABASE_URL: "postgresql://{{ .Username }}:{{ .Password }}@{{ .Host }}:{{ .Port }}/myapp?sslmode=require"
postgres:
connectionLimit: 50
---
# Assign role to user
apiVersion: dbops.dbprovision.io/v1alpha1
kind: DatabaseGrant
metadata:
name: myapp-user-grant
spec:
userRef:
name: myapp-user
postgres:
roles:
- myapp_readwrite
defaultPrivileges:
- database: myapp
schema: app
grantedBy: postgres
objectType: tables
privileges: [SELECT, INSERT, UPDATE, DELETE]
Verify Setup¶
# Check instance status
kubectl get databaseinstance postgres-primary
# Check all resources
kubectl get databases,databaseusers,databaseroles,databasegrants
# Get connection string
kubectl get secret myapp-credentials -o jsonpath='{.data.DATABASE_URL}' | base64 -d
# Test connection
kubectl run psql --rm -it --image=postgres:15 -- \
psql "$(kubectl get secret myapp-credentials -o jsonpath='{.data.DATABASE_URL}' | base64 -d)"