ToolsOps

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: 20s

Secrets 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:.