Secure, containerized Postgres 17 with S3 backups and mutual TLS (mTLS) access.
Highlights
- Postgres 17 (alpine), public TLS on 5432
- Certificate-only authentication (mTLS) — no passwords
- S3-compatible backups + restore tooling
Quickstart
- Copy env template and fill values
cp .env.example .env(set S3 credentials and DB names/users)
- Generate TLS certs (CA, server with SAN, client per user)
./generate-certs.sh --cn db.ozinozi.com --client admin --client appuser- The script automatically adds
DNS:postgresto the server certificate SANs so internal services (backup/restore/exporter) can connect using hostpostgreswithPGSSLMODE=verify-full. Add extra SANs with repeated--sanflags. - Places server/CA under
certs/and client identities under structured foldersclient-certs/<user>/client.crt|client.key(and flat copies atclient-certs/<user>.crt|.key). It also copiescerts/ca.crttoclient-certs/ca.crtfor convenience. - Script auto-sets permissions; it also attempts
chown 999:999on server certs (optional) - If you see
Permission deniedfor server.key in logs, run:./fix-certs-perms.sh --restart
- Start Postgres
docker compose up -d postgres- Note: We use a PGDATA subdirectory so first initialization works even if the host mount (e.g.,
/mnt/.../pgdata) containslost+found. The image initializes the cluster on first start. Application databases can be created later.
- Create extra DB users (if you generated additional client certs)
docker exec -it postgres-prod psql -U postgres -c "CREATE ROLE appuser LOGIN;"- The client cert CN must equal the Postgres role name
- Open firewall to 5432 for your allowed networks (mTLS required regardless)
- Test client connection (psql example)
psql "host=db.ozinozi.com port=5432 dbname=postgres user=dbuser sslmode=verify-full sslrootcert=ca.crt sslcert=client.crt sslkey=client.key"
- Configure and test backups
- Generate an
adminclient cert (SUPERUSER) for full-cluster backups:./generate-certs.sh --cn db.ozinozi.com --client admin - Backup/restore default to the
adminidentity and structured paths:client-certs/admin/client.{crt,key}andclient-certs/ca.crt ./backup-manager.sh setup(validates S3, builds images, runs a test backup)./backup-manager.sh list(verify uploaded backup)- If the backup service reports it cannot connect and your server cert was generated without
DNS:postgres, re-run cert generation with--forceor setPGSSLMODE=verify-cafor backup/restore indocker-compose.yaml. - To back up ALL databases and roles, set
BACKUP_SCOPE=clusterin your.env(requires the backup user to be a superuser). Database-only backups remain the default.
- Generate an
- Optional: run exporter (Prometheus metrics on 9187)
docker compose up -d postgres-exporter
More
- docs/overview.md — high-level summary
- docs/tls-mtls.md — TLS and client cert setup
- docs/clients.md — how to connect from clients
- docs/backup-restore.md — backups and restores with mTLS
- docs/commands.md — script usage (generate certs + backup manager)
- docs/roles.md — creating app and datagrip roles + psql access
- docs/cloudflare-tunnel.md — add an optional Cloudflare Tunnel