Skip to content

DevOps

Docker Compose in practice: a guide to the parts you use every day

A practical Docker Compose guide: services, volumes, networks, environment files, health checks, depends_on conditions, profiles, common commands and mistakes to avoid.

By · Published · 3 min read

Short answer: Docker Compose describes a group of containers, their networks and volumes in one compose.yaml file, and starts them with docker compose up -d. It is the simplest way to run a multi-container application on a single machine, from development to small production deployments. Learn services, volumes, environment files, health checks and a handful of commands, and you can do most of what you need.

What does a Compose file look like?

services:
  web:
    build: .
    ports:
      - "127.0.0.1:3000:3000"
    env_file: .env
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      retries: 10
    restart: unless-stopped

volumes:
  db_data:

Two services, one named volume. Compose creates a private network automatically, and each service can reach the others by service name. From web, the database host is simply db.

How do volumes work?

  • Named volumes (db_data:) are managed by Docker and are the right place for database files. They survive docker compose down and are removed by docker compose down -v.
  • Bind mounts (./config:/etc/app) map a host folder. Handy for config and in development. They depend on the host path existing, which caused a surprise on an install where the repository was not present, as I describe in an empty bind mount on CasaOS.

docker compose down -v deletes your named volumes and therefore your data. Do not put it in a habit or a script you run on a server.

How do you manage configuration and secrets?

  • Put variables in a .env file next to compose.yaml. Compose substitutes ${VAR} from it automatically.
  • env_file: passes a whole file into the container.
  • Keep .env out of Git and add a .env.example with fake values.
  • After changing environment variables, recreate the container. A plain restart does not pick up new values from the file, which I learned the hard way and wrote up in docker restart ignores env-file.

What are health checks and `depends_on` for?

depends_on alone only waits for a container to start, not for the application inside to be ready. A database can take seconds to accept connections. Add a healthcheck to the database and use condition: service_healthy so the web service starts afterward. Without it you see random startup failures that vanish on restart.

Which commands matter?

docker compose up -d            # create and start in the background
docker compose ps               # status
docker compose logs -f web      # follow logs for one service
docker compose exec db psql -U postgres   # run a command in a running container
docker compose pull && docker compose up -d   # update images
docker compose down             # stop and remove containers (keeps volumes)
docker compose config           # print the final, merged file

docker compose config is underused. It shows exactly what Compose will do after variable substitution, which finds most typos.

What else is worth knowing?

  • Profiles let you define optional services (profiles: [debug]) and start them with --profile debug.
  • Overrides: compose.override.yaml is merged automatically, so you can keep development tweaks separate from the base file.
  • Restart policies: unless-stopped brings services back after a reboot.
  • Resource limits: mem_limit and cpus stop one container from taking the machine.
  • Logging: set a log size limit, otherwise container logs can fill the disk.
    logging:
      driver: json-file
      options: { max-size: "10m", max-file: "3" }

What are the common mistakes?

  • Publishing the database port to the internet. Do not publish it at all, the other services reach it over the Compose network.
  • Using latest tags, then being surprised by a major version jump.
  • Storing data in the container filesystem. It disappears when the container is recreated.
  • Forgetting that published ports can bypass the host firewall. Bind to 127.0.0.1 and use a reverse proxy.
  • Treating Compose as high availability. It runs on one machine.

When one machine is not enough, read Kubernetes for beginners: do you need it. For security settings, see is Docker safe for production.

References

Author

Raktim Ranjit is a software engineer and the founder of NodeDR Infotech. He builds and maintains the software described here.

Have something in mind?

Let’s build something useful.

Tell me about the idea, product, or workflow you’re working through.

Tap to say hello