Docker Intermediate: Images, Networks and Compose
Compose in Depth
In this lesson you will
- Configure services with environment variables and .env files
- Use health checks and depends_on conditions
- Split settings across Compose files for development and production
Introduction
A Compose file describes a whole application. This lesson covers the features that make it dependable: configuration, start-up order, health and separate setups for development and production.
Configuration without secrets in code
Keep settings out of the Compose file by using an .env file next to it:
POSTGRES_PASSWORD=change-me
APP_PORT=3000Never commit .env files with real secrets. Add .env to .gitignore. Then refer to the values:
YAML
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
api:
build: .
ports:
- "127.0.0.1:${APP_PORT}:3000"
env_file:
- .env${NAME} is filled in from the .env file or your shell. For real deployments use a proper secrets mechanism instead of plain environment files.
Health checks and start-up order
depends_on alone only waits for a container to start, not to be ready. A database takes time to accept connections. Add a health check and wait for it to be healthy:
YAML
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 10
api:
build: .
depends_on:
db:
condition: service_healthyYour app should also retry its connection politely, because dependencies can restart at any time.
Quick quiz
What does depends_on with condition service_healthy wait for?
Volumes
Use named volumes for data you must keep, and declare them at the bottom:
YAML
services:
db:
volumes:
- dbdata:/var/lib/postgresql/data
volumes:
dbdata:For development, a bind mount lets your container see your live code:
YAML
api:
volumes:
- ./src:/app/srcOne app, several setups
Keep shared settings in compose.yaml, and add an override file:
Terminal
docker compose -f compose.yaml -f compose.dev.yaml up -dThe files are merged, with later ones overriding earlier ones. A development override might add bind mounts and debug settings. A production file would use pinned image tags, no bind mounts and tighter settings.
Useful commands
Terminal
docker compose config # show the final merged configuration
docker compose up -d --build # rebuild images, then start
docker compose logs -f api # follow one service's logs
docker compose exec api sh # open a shell in a service
docker compose down -v # stop and also delete volumes (careful!)Try it yourself
Create a Compose file with a database and an app that depends on it being healthy. Move the password to a .env file that is git-ignored. Run docker compose config to see how the values are filled in.
Summary
- Keep configuration in
.envfiles that are not committed, and use real secrets tools in production. - Health checks plus
depends_onconditions control readiness, not just start-up. - Named volumes keep data, bind mounts help development, and override files separate environments.
Lesson complete
+20 XP
Nice work. Keep going while it is fresh.
Sources and further reading