The official postgres image makes running PostgreSQL in Docker easy. Doing it safely — data that survives, backups that work, upgrades that don’t destroy the database — needs a few deliberate choices.
Compose file
services:
db:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_USER: app
POSTGRES_DB: app
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
volumes:
- pgdata:/var/lib/postgresql/data
- ./initdb:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 5s
timeout: 5s
retries: 10
shm_size: 256mb
# ports:
# - "127.0.0.1:5432:5432" # only if host tools need direct access
volumes:
pgdata:
secrets:
db_password:
file: ./secrets/db_password.txt
mkdir -p secrets initdb
openssl rand -base64 24 > secrets/db_password.txt
chmod 600 secrets/db_password.txt
docker compose up -d
Key points:
- Pin the major version (
postgres:17).postgres:latestwill eventually jump to a new major version, and PostgreSQL won’t start on a data directory from an older major version. - Named volume for
/var/lib/postgresql/data— the data survives container recreation. POSTGRES_PASSWORD_FILEreads the password from a Docker secret instead of a plain environment variable.- No published port by default: other containers on the same Compose network connect to
db:5432. If you publish, bind to127.0.0.1— never expose PostgreSQL to the internet. shm_sizeavoids “could not resize shared memory segment” errors on larger queries (Docker’s default /dev/shm is 64 MB).
Environment variables only apply on first start
POSTGRES_USER, POSTGRES_DB and the password are used only when the data directory is empty. Changing them later has no effect — change the password with SQL instead:
docker compose exec db psql -U app -c "ALTER ROLE app PASSWORD 'new-password';"
Init scripts
Files in ./initdb (*.sql, *.sql.gz, *.sh) run once at first initialisation, in alphabetical order — handy for extra databases, roles or extensions:
-- initdb/01-extensions.sql
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE DATABASE analytics OWNER app;
Connect
docker compose exec db psql -U app -d app
From an app container: postgres://app:PASSWORD@db:5432/app.
Tuning
Pass settings as command arguments:
command:
- postgres
- -c
- shared_buffers=512MB
- -c
- effective_cache_size=1536MB
- -c
- work_mem=16MB
- -c
- max_connections=100
Size them to the memory you give the container (see Docker CPU and memory limits) and PostgreSQL performance tuning.
Backups
Logical dumps through the container:
docker compose exec -T db pg_dump -U app -Fc app > app-$(date +%F).dump
docker compose exec -T db pg_dumpall -U app --globals-only > globals-$(date +%F).sql
Restore:
docker compose exec -T db pg_restore -U app -d app --clean --if-exists < app-2026-11-04.dump
Schedule with cron and copy dumps off the server. Don’t back up by copying the volume while PostgreSQL is running — the files may be inconsistent. See pg_dump and pg_restore.
Major version upgrades (e.g. 16 → 17)
Changing the image tag isn’t enough — PostgreSQL refuses to start with an older data directory. The simple, reliable method is dump and restore:
# 1. dump with the old version
docker compose exec -T db pg_dumpall -U app > full-dump.sql
# 2. stop and keep the old volume as a fallback
docker compose down
docker volume create pgdata17
# 3. point the service at postgres:17 and the new volume (edit compose.yaml), start it
docker compose up -d
# 4. restore
docker compose exec -T db psql -U app -d postgres < full-dump.sql
Check the application, then remove the old volume after a while. For big databases, pg_upgrade (via community images that bundle two versions) is faster. See upgrading PostgreSQL major versions.
Minor updates
Minor releases (17.1 → 17.2) are drop-in: docker compose pull && docker compose up -d. They contain bug and security fixes — apply them regularly.
Should you run your database in Docker?
For small and medium applications on a VPS, yes — with a pinned version, a volume, and real backups. For heavy production databases, many teams prefer PostgreSQL installed directly on the host (or a dedicated database server) for simpler tuning and upgrades. See installing PostgreSQL on a VPS.