Interview Prep

Interview: Docker Compose

Multi-container apps, compose files, and production patterns. Read learning notes.

Docker Compose

Declaratively define and run multi-container application stacks.

What is Docker Compose and when should you use it?

Docker Compose is a tool for defining and running multi-container applications from a single YAML file. Instead of chaining multiple docker run commands, you declare services, networks, and volumes once and manage the stack with docker compose up. It is ideal for local development, integration testing in CI, and small single-host production deployments. For multi-node orchestration at scale, Kubernetes is the typical next step.

What is the anatomy of a Compose file?

A Compose file (typically compose.yml) is versioned YAML with a top-level services key (required), plus optional networks, volumes, configs, and secrets sections. Each service defines an image or build context, ports, environment, dependencies, health checks, and resource limits. Compose v2 (the docker compose CLI plugin) uses the Compose Specification - there is no longer a required version: field at the top of the file.

How do you define and configure services in Compose?
  • image: pull a pre-built image, or build: with context and dockerfile to build locally.
  • ports: publish container ports to the host ("8080:80").
  • environment: or env_file: inject configuration.
  • command: / entrypoint: override the image defaults.
  • restart: policy (unless-stopped, on-failure) for resilience.
  • deploy.resources (in Swarm-compatible files) or mem_limit / cpus for resource caps.
How does networking work in Docker Compose?

Compose creates a default project network named {project}_{network}. Every service on that network can reach others by service name as a DNS hostname - e.g., a web container connects to postgres:5432 without hard-coding IPs. You can define custom networks with the networks: key, attach services to multiple networks, and set network_mode: host to bypass bridge networking when needed.

How do you manage persistent data with Compose volumes?
  • Named volumes (volumes: db_data:/var/lib/postgresql/data) are managed by Docker and survive docker compose down.
  • Bind mounts (./src:/app/src) map host paths - great for live-reload in development.
  • Anonymous volumes are created inline and removed with docker compose down -v.

Declare named volumes under the top-level volumes: key so Compose creates them with a project-scoped name.

How do health checks and service dependencies work?

The healthcheck block runs a probe command inside the container on an interval. Docker marks the container healthy or unhealthy based on exit codes. depends_on with condition: service_healthy (Compose v2) waits for a dependency to pass its health check before starting a dependent service - solving the race where a database port is open but not yet ready to accept connections. Without a health condition, depends_on only waits for the container to start, not to be ready.

What are override files and how do they enable environment-specific configs?

Compose automatically merges multiple files in order: compose.yml is the base, and compose.override.yml (if present) is applied on top for local dev overrides (bind mounts, debug ports, etc.). You can also pass explicit files: docker compose -f compose.yml -f compose.prod.yml up. Later files override earlier ones for conflicting keys. This keeps a single source of truth while tailoring dev, staging, and production without duplicating the entire stack definition.

What is the difference between docker compose up and docker compose run?

docker compose up creates and starts all (or specified) services defined in the compose file, wiring networks and volumes as declared. docker compose run web pytest starts a one-off container for the web service, overriding the default command - useful for migrations, test suites, or admin tasks. One-off containers do not get the service's restart policy and are removed after exit unless --rm is omitted.

How do you handle environment variables and secrets in Compose?
  • environment: sets key-value pairs inline; env_file: .env loads a file.
  • A .env file in the project directory can substitute variables in the compose file via ${VAR} syntax.
  • secrets: (Swarm-compatible) mount sensitive files into containers at /run/secrets/<name> without baking them into images.

Never commit real secrets to version control - use .env.example as a template and inject production secrets via CI or a secrets manager.

What production patterns should you follow with Compose?
  • Pin images by digest, not floating tags like latest.
  • Set restart: unless-stopped and define healthcheck on every critical service.
  • Use named volumes for stateful services; back them up regularly.
  • Place a reverse proxy (Traefik, Nginx) in front of app services for TLS termination.
  • Limit resources with deploy.resources or cgroup flags to prevent one service from starving others.
  • Run containers as non-root users defined in the Dockerfile.
  • Log to stdout/stderr and collect with a log driver or sidecar - do not rely on container filesystem logs.
What are Compose profiles and when are they useful?

Profiles let you opt services in or out of a stack. Add profiles: ["debug"] to a service and it is ignored unless you activate that profile: docker compose --profile debug up. This keeps optional tooling (mail catchers, debug exporters, seed scripts) out of the default startup path without maintaining separate compose files for every combination.

Compose v1 (docker-compose) vs v2 (docker compose) - what changed?

The legacy docker-compose was a standalone Python tool; it is deprecated. Compose v2 is a Go-based Docker CLI plugin invoked as docker compose (no hyphen). It integrates with Docker Buildx, supports the Compose Specification without a version field, and aligns with docker context and credential helpers. New projects should always use docker compose.