Compose, volumes and networking
Describe a multi-container stack in one file, decide where data lives, and understand how containers address each other.
Describing a stack with Compose
Compose takes one YAML file and creates the whole set of containers, networks and volumes with a single command. It is the fastest way to run an application next to its database on a laptop.
services:
api:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://app:secret@db:5432/app
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: secret
POSTGRES_USER: app
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 5s
retries: 10
volumes:
pgdata:docker compose up -d # build if needed, then start everything
docker compose ps
docker compose logs -f api
docker compose exec api sh
docker compose down # stop and remove containers (volumes survive)depends_oncontrols start order only. The health condition above is what actually waits for Postgres to accept connections.docker compose down -valso deletes named volumes. That is your data.- Keep local tweaks in
compose.override.yamlso the committed file stays production-shaped.
Volumes and bind mounts
| Type | Lives on the host | Use it for |
|---|---|---|
| Named volume | A Docker-managed area | Database files and uploads; anything that must outlive the container |
| Bind mount | A path you choose | Source code during development, config files, certificates |
| tmpfs | Memory only | Scratch data that must never touch disk |
| Container layer | Inside the container | Nothing you intend to keep |
docker run -d --name db \
-v pgdata:/var/lib/postgresql/data \
postgres:16-alpine
docker run --rm -it -v "$PWD:/work" -w /work node:20-alpine npm test
docker volume ls
docker volume inspect pgdata⚠️
A bind mount of your project directory over
node_modules hides the dependencies installed in the image. Mount the source and keep node_modules in a separate anonymous volume, or install dependencies into the mounted path once.Networking
Compose puts every service on one network and registers each service name in an internal DNS server. Containers reach each other by name and container port: db:5432, never localhost.
docker network ls
docker network inspect myapp_default
docker run --rm -it --network myapp_default nicolaka/netshoot nslookup dbports: "3000:3000"publishes to the host; service-to-service traffic does not need it at all.- Publish only the edge service. A database on a host port is a database reachable by anything on your network.
localhostinside a container is that container, never the host and never a sibling. On Docker Desktop the host ishost.docker.internal.- Services on different Compose networks cannot resolve each other. That is a deliberate default you can extend per service.
FAQ
depends_on does not wait for the database. Why?
It waits for the container to start, not for the process to be ready. Add a
healthcheck and use condition: service_healthy, then still retry the connection inside the application.My code edits do not show up in the container?
The running process loaded the code at start. Bind-mount the source and run a development command with reloading, for example
npm run dev.Related
Images and containers Writing a Dockerfile
Last refreshed 2026-09-18.