PostgreSQL Docker and Operations
Introduction
Running PostgreSQL in Docker is the fastest way to share a dev database across teammates. Production adds persistent volumes, dedicated app roles, backups, monitoring, and upgrade planning. This chapter covers Compose patterns, docker-entrypoint-initdb.d, postgresql.conf / pg_hba.conf overview, backup habits, and links to managed hosting. Compare with MongoDB Docker ops and Linux Docker.
Prerequisites
Single-Node Docker (Development)
Connect:
psql "postgresql://postgres:devpass@127.0.0.1:5432/hello_demo"Code explanation:
pg_datavolume survives container recreation- Pin
postgres:16tag for reproducible dev—avoidlatestin teams
Init Scripts on First Boot
Mount SQL or shell into /docker-entrypoint-initdb.d/:
volumes:
- pg_data:/var/lib/postgresql/data
- ./init:/docker-entrypoint-initdb.d:roinit/01_schema.sql:
CREATE TABLE IF NOT EXISTS app_meta (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
INSERT INTO app_meta (key, value) VALUES ('schema_version', '1')
ON CONFLICT (key) DO NOTHING;Scripts run only when the data directory is empty. To re-run: docker volume rm (destructive) or use migration tools (Flyway, Alembic).
See Project deploy for full Compose with API + Redis.
App User (Least Privilege)
After container starts, create a non-superuser role:
CREATE ROLE app_user WITH LOGIN PASSWORD 'app_pass';
GRANT CONNECT ON DATABASE hello_demo TO app_user;
GRANT USAGE ON SCHEMA public TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_user;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;App connection string:
postgresql://app_user:app_pass@127.0.0.1:5432/hello_demoNever use postgres superuser in application code—Roles and Permissions.
Configuration Files
| File | Purpose |
|---|---|
postgresql.conf | Memory, logging, listen_addresses, autovacuum |
pg_hba.conf | Client authentication rules |
Mount custom config (advanced):
volumes:
- ./conf/postgresql.conf:/etc/postgresql/postgresql.conf:ro
- ./conf/pg_hba.conf:/etc/postgresql/pg_hba.conf:ro
command: postgres -c config_file=/etc/postgresql/postgresql.conf -c hba_file=/etc/postgresql/pg_hba.confUseful dev logging:
log_min_duration_statement = 500
log_line_prefix = '%m [%p] %u@%d 'Reload without restart:
docker exec postgres pg_ctl reload -D /var/lib/postgresql/dataProduction tuning: shared_buffers, work_mem, max_connections—Performance chapter.
Backup in Docker
Dump from running container:
docker exec postgres pg_dump -U postgres -d hello_demo -Fc -f /tmp/hello_demo.dump
docker cp postgres:/tmp/hello_demo.dump ./backups/Schedule cron on host or CI:
pg_dump "$DATABASE_URL" -Fc -f "backup_$(date +%F).dump"Test restore monthly to a throwaway database—Backup and Restore.
Upgrades
| Change | Approach |
|---|---|
| Patch within major (16.1 → 16.4) | Replace image tag, restart |
| Major (16 → 17) | pg_dumpall or logical replication; read release notes |
| Data directory format | Never mount PGDATA from older major without upgrade |
Always backup before upgrade.
Production Checklist
| Item | Action |
|---|---|
| Persistence | Named volume or cloud disk |
| Secrets | Env from vault—not committed .env |
| Health | pg_isready in Compose/K8s probe |
| Monitoring | Disk, connections, replication lag, autovacuum |
| Pooling | PgBouncer if many app replicas |
| TLS | hostssl in pg_hba.conf + cert |
| Backups | Automated pg_dump + off-site; PITR for critical data |
Managed PostgreSQL (Awareness)
| Provider | Notes |
|---|---|
| AWS RDS | Multi-AZ, automated backups |
| Google Cloud SQL | Similar managed ops |
| Neon | Serverless branching for dev |
| Supabase | Postgres + auth/API extras |
Same SQL and drivers—connection strings and roles differ from self-hosted Docker.
Multi-Service Compose
Full stack from chapter 27:
postgres (5432) + redis (6379) + api (8000)depends_on with condition: service_healthy avoids API starting before Postgres accepts connections.
FAQ
Data lost after docker compose down?
Only if you removed the volume. docker compose down keeps pg_data; docker volume rm pg_data wipes data.
Port 5432 already in use?
Stop local Postgres or map 5433:5432.
Init script did not run?
Volume already initialized—drop volume or run migrations manually.
Where are logs?
docker logs postgres; file logs depend on log_destination in postgresql.conf.
Replica set in Docker?
Streaming replication is advanced—Replication intro; use managed HA for production learning path.
Nginx in front?
Terminate TLS at Nginx; app connects to Postgres on private network.