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_on controls start order only. The health condition above is what actually waits for Postgres to accept connections.
  • docker compose down -v also deletes named volumes. That is your data.
  • Keep local tweaks in compose.override.yaml so the committed file stays production-shaped.

Volumes and bind mounts

TypeLives on the hostUse it for
Named volumeA Docker-managed areaDatabase files and uploads; anything that must outlive the container
Bind mountA path you chooseSource code during development, config files, certificates
tmpfsMemory onlyScratch data that must never touch disk
Container layerInside the containerNothing 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 db
  • ports: "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.
  • localhost inside a container is that container, never the host and never a sibling. On Docker Desktop the host is host.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.

Images and containers Writing a Dockerfile

Last refreshed 2026-09-18.