Articles

Docker Compose Explained: Running Multi-Container Apps

Docker Compose defines multiple containers, networks, and volumes in one YAML file so you can start a whole application stack with a single command.

The Lycoris Team The Lycoris Team · · 3 min read
Docker whale logo on a container-themed illustration

Docker Compose is a tool for defining and running multi-container applications from a single YAML file. Instead of running docker run separately for your API, database, and cache with a long list of flags each time, you describe every service, its configuration, and how the services connect in one docker-compose.yml, then bring the whole stack up or down with one command.

The basic file

A minimal docker-compose.yml for an app with an API and a database looks like this:

services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/app
    depends_on:
      - db
  db:
    image: postgres:16
    environment:
      - POSTGRES_PASSWORD=pass
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  db-data:

Running docker compose up builds the api image from the local Dockerfile, pulls the postgres:16 image, creates a shared network between them, and starts both containers. Inside that network, the api service reaches the database at the hostname db — Compose handles DNS resolution between services automatically, so containers don’t need hardcoded IP addresses.

What Compose actually manages

Compose coordinates four things behind that one file:

  • Services — each entry under services: becomes one or more containers, built from a Dockerfile or pulled as a prebuilt image.
  • Networks — by default, Compose creates a single network shared by every service in the file, so they can reach each other by service name.
  • Volumes — named volumes (like db-data above) persist data across container restarts, so a database doesn’t lose its data every time you rebuild.
  • Environment configuration — variables, ports, and dependencies (depends_on) are declared per service instead of passed as long CLI flags.

This is the same set of concerns covered generally in Docker image layers and caching — Compose doesn’t change how images are built, it just orchestrates running several of them together.

depends_on doesn’t mean “wait until ready”

A common mistake: depends_on: [db] controls start order, not readiness. Docker starts the db container before the api container, but Postgres inside that container might still be initializing when the API’s first connection attempt fires. For services that need to wait for a dependency to actually be ready to accept connections, add a healthcheck to the dependency and a condition: service_healthy on the depends_on entry, or have the application itself retry the connection with backoff — the same pattern described in exponential backoff retry strategies.

Compose for local development, not production

Compose is overwhelmingly used for local development and small deployments — spinning up a database, cache, and message queue alongside your app with one command, so a new contributor can clone a repo and be running in minutes without installing Postgres or Redis locally. It’s a genuinely good fit for that: fast iteration, easy teardown (docker compose down -v wipes everything including volumes), and a file that documents the stack’s shape.

It’s a weaker fit for production. Compose has no built-in concept of rolling deploys, multi-host scheduling, or automatic recovery across machines — if a container dies, Compose alone won’t reschedule it elsewhere. For production container orchestration at any real scale, Kubernetes is the more common choice, precisely because it handles the scheduling, self-healing, and multi-node concerns Compose was never designed for.

A few practical habits

  • Pin image versions (postgres:16, not postgres:latest) so a rebuild months later doesn’t silently pull a different major version.
  • Use .env files for secrets and environment-specific values instead of hardcoding them in the YAML, and keep that file out of version control.
  • Name your volumes explicitly rather than relying on Compose’s auto-generated names, so docker compose down doesn’t accidentally orphan data you meant to keep.
  • Use profiles (profiles: on a service) to opt certain services in only when needed — for example, a debugging tool you don’t want running by default.

The takeaway

Docker Compose turns a multi-container application into one declarative file and one command, replacing a list of manual docker run invocations with services, networks, and volumes defined together. It’s the right tool for local development and small self-hosted stacks; for production-grade orchestration across multiple machines, that’s Kubernetes’ job, not Compose’s.

Chisato Chisato · · 4 min read

Docker vs Podman: Container Runtimes Compared

Docker runs containers through a persistent root daemon; Podman runs them daemonless and rootless by default. What that architectural split changes.

#Docker #DevOps #Containers
The Lycoris Team The Lycoris Team · · 4 min read

Docker Multi-Stage Builds Explained

Multi-stage builds let a Dockerfile use one stage to compile code and a separate, minimal stage to ship it — cutting image size and attack surface.

#Docker #DevOps #Containers