Recipe: PostgreSQL
Persistent data, secrets, healthcheck.
A database in Docker is where beginners lose data and leak credentials, so it is the recipe worth getting exactly right. Three things matter: the data must live on a named volume (never the writable layer), the password must arrive as a secret file (never a plain -e), and a healthcheck must gate anything that depends on it. The official postgres image supports all three out of the box.
# a named volume so data survives docker rm; a Docker secret for the password$ docker volume create pgdata$ printf 'S3cr3tP@ss' | docker secret create pg_pw - # (swarm) — or a file for compose$ docker run -d --name db \-e POSTGRES_DB=payments -e POSTGRES_USER=app \-e POSTGRES_PASSWORD_FILE=/run/secrets/pg_pw \-v pgdata:/var/lib/postgresql/data \postgres:16-alpine
Secrets, not environment variables
The postgres image accepts POSTGRES_PASSWORD directly, but that value then shows up in docker inspect, the process environment, and often logs. The _FILE-suffixed variants (POSTGRES_PASSWORD_FILE) tell it to read the value from a file instead — mount that file as a Docker secret (tmpfs, in memory) and the credential never appears in inspect or on disk. Most official database images support this _FILE convention.
services:db:image: postgres:16-alpineenvironment:POSTGRES_DB: paymentsPOSTGRES_USER: appPOSTGRES_PASSWORD_FILE: /run/secrets/pg_pw # value read from the filesecrets: [pg_pw]volumes: ["pgdata:/var/lib/postgresql/data"]healthcheck:test: ["CMD-SHELL", "pg_isready -U app -d payments"]interval: 10stimeout: 3sretries: 5volumes: { pgdata: {} }secrets: { pg_pw: { file: ./secrets/pg_pw.txt } }
The healthcheck earns its keep
pg_isready reports when the server is actually accepting connections, which is later than “container started.” With the healthcheck defined, docker ps shows (healthy), and any app service using depends_on: { db: { condition: service_healthy } } waits for it — eliminating the flaky “app crashed on boot because the DB was not up yet” race.
$ docker compose up -d$ docker inspect -f '{{.State.Health.Status}}' $(docker compose ps -q db)healthy$ docker compose exec db psql -U app -d payments -c '\l' # connect and list DBs