Docker Compose healthchecks: depends_on, secrets and .env
How to write correct Docker Compose healthchecks, use depends_on with condition: service_healthy, manage secrets with .env.example and avoid exposing database ports.
Modern Compose: no version:
In Docker Compose v2 the version: key is obsolete. Compose ignores it and emits a warning. Modern files start directly with services: and, when needed, declare volumes: and networks: at the same level.
Per-service healthchecks
A healthcheck tells Docker whether a service is truly ready, not just that the process started. It is the basis for depends_onto wait reliably. Commands reference the container's own variables with $$VAR: Compose turns $$ into a single $, which the container shell evaluates at runtime (where the service variable does exist).
- PostgreSQL:
pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB - MySQL:
mysqladmin ping -h localhost -u $$MYSQL_USER -p$$MYSQL_PASSWORD - Redis:
redis-cli ping(with-a "$$REDIS_PASSWORD"when a password is set) - HTTP / web:
wget --no-verbose --tries=1 --spider http://localhost/ || exit 1
depends_on with condition: service_healthy
The short form of depends_on (a list) only guarantees start order. To wait until the dependency is healthy, use the long form:
services:
app:
build: .
depends_on:
postgres:
condition: service_healthy
postgres:
image: postgres:16-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 5
start_period: 20sSecrets and .env.example
Never put literal passwords in compose.yaml: they end up in the repo and git history. Use ${VARIABLE} in environment and define values in a .env that is not committed. Version only a .env.example with placeholders like CHANGE_ME to document which variables are required.
Database ports: local vs production
Publishing 5432:5432 or 3306:3306 exposes the database to the whole host network. In development, if you need host access, bind to loopback (127.0.0.1:5432:5432). In production, keep the database on the internal Compose network and do not publish its port: other services reach it by name.
Volumes and backups
Database data should live in a named volume (for example postgres_data:/var/lib/postgresql/data) so it survives docker compose down. A volume is not a backup: schedule periodic dumps (pg_dump, mysqldump) and store them off-host. For Nginx as a reverse proxy, mount the config read-only (./nginx.conf:/etc/nginx/nginx.conf:ro).
Next steps
Generate a starting compose.yaml and validate yours with the Docker Compose generator and validator. Build ready-to-paste healthchecks and check that your Compose does not expose ports or secrets by mistake.
Frequently asked questions
- How do I write a healthcheck for Postgres in Docker Compose?
- Use pg_isready inside the container itself: test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]. The double $$ tells Compose to pass a single $ to the container, where POSTGRES_USER and POSTGRES_DB exist as the service's environment variables. Pair it with interval, timeout, retries and start_period.
- Why doesn't depends_on wait for the database to be ready?
- The short form of depends_on (a list of names) only waits for the container to start, not for the service to accept connections. To truly wait you need a healthcheck on the target service and the long form: depends_on with condition: service_healthy.
- Where do I put the passwords in my compose.yaml?
- In a .env file that is NOT committed. In compose.yaml reference variables with ${VARIABLE} and version only a .env.example with placeholders (for example CHANGE_ME). That way the real secret never enters git or its history.
- Should I expose the database port?
- In production, no: keep the database on the internal Compose network and connect from other services by name. In development, if you need host access, bind to 127.0.0.1 (127.0.0.1:5432:5432) so it is not open to the whole network.
- Do I need the version: key in modern Compose?
- No. In Docker Compose v2 the version: key is obsolete: it is ignored and triggers a warning. Start the file directly with services:.