Web development
Docker Compose for development: services, volumes and healthchecks
A compose.yaml for database and app: healthchecks, volumes that survive a restart, a team override, and the command that deletes data without asking.
Key points
- The `compose.yaml` stays in the repository: two services, one named volume and a healthcheck cover database and app, on Docker Compose v5.6.1.
- `depends_on` with `condition: service_healthy` waits for Postgres to answer; without the condition it only waits for the container to start.
- `docker compose down` keeps the volumes, `down -v` deletes them without asking: the command never to run out of habit.
Short answer: write a compose.yaml with the services the project needs, start it with docker compose up -d and follow the output with docker compose logs -f. The file stays in the repository and covers the whole team.
A compose.yaml that starts
services:
db:
image: postgres:17-alpine
environment:
POSTGRES_PASSWORD: dev
ports:
- "5432:5432"
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 10
app:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://postgres:dev@db:5432/postgres
depends_on:
db:
condition: service_healthy
volumes:
db-data:
Two services cover most projects: a database and the application. The named volume db-data keeps the data across a restart, and depends_on with condition: service_healthy waits for Postgres to answer, not merely for the container to start.
The commands you use every day
docker compose up -d
docker compose ps
docker compose logs -f app
docker compose exec db psql -U postgres -c '\dt'
docker compose down
ps shows state and ports, logs -f follows one service, exec enters a container that is already up. down stops and removes the containers but keeps the volumes: the data stays.
Local data: when to delete it
docker compose down -v
docker compose exec db psql -U postgres -c 'select count(*) from users;'
down -v removes the volumes as well. It is the right command when you want an empty database and the wrong one to run out of habit: it asks for no confirmation and has no undo. Check first with docker volume ls.
A team override
A compose.override.yaml is merged into the base file automatically: code mounts, exposed ports, a watch command. Keep the override out of the repository when it holds personal paths.
A named limitation
On Windows, bind mounts go through WSL2 and cost: a node_modules mounted from the host slows startup down clearly. The Docker documentation suggests a named volume or a build cache for dependencies instead of a bind mount.
Where to continue
The catalogue holds the system monitor, the progress metric card and the dashboard blocks. For the rest read the structure of a Next.js monorepo, the PostgreSQL indexes worth adding and the comparison Next.js or Vite.
Frequently asked questions
Is Docker Compose still free?
Yes. The Docker Compose plugin ships with Docker Desktop and the Docker CLI under the Apache 2.0 licence: no part of the compose file needs a paid plan.
What is the difference between compose.yaml and docker-compose.yml?
None in behaviour: compose.yaml is the name the documentation recommends, docker-compose.yml stays supported for compatibility. Pick one and never keep both.
Why does the app start before the database is ready?
Because depends_on alone waits only for the container to start, not for the service to answer. Add condition: service_healthy with a pg_isready healthcheck, or retry inside the application code.
How do I look at the database data?
With docker compose exec db psql -U postgres, which opens a session inside the running container. Alternatively expose port 5432 and connect a client from the host system.
Can I use Docker Compose in production?
You can, but that is not the job it was built for: there is no multi-node control, rolling update or failover. For local development it is the right tool; production needs an orchestrator.