What is Docker Compose?
Docker is like managing individual musicians. Docker Compose is like an orchestra conductor: it knows who enters when, how loud to play, and how musicians hear each other.
Junior Level
Simple Explanation
Docker Compose is a tool for defining and running multi-container applications. You describe the entire infrastructure (services, networks, volumes) in a single YAML file. If Docker manages individual containers, Docker Compose manages a system of interconnected containers.
Analogy
Docker is like managing individual musicians. Docker Compose is like an orchestra conductor: it knows who enters when, how loud to play, and how musicians hear each other.
Example docker-compose.yml
version: "3.8"
services:
app:
build: .
ports:
- "8080:8080"
environment:
- DATABASE_URL=jdbc:postgresql://db:5432/mydb
depends_on:
db:
condition: service_healthy
db:
image: postgres:15
environment:
- POSTGRES_DB=mydb
- POSTGRES_USER=admin
- POSTGRES_PASSWORD=secret
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin"]
interval: 5s
timeout: 3s
retries: 5
# Healthcheck — periodic check whether the service inside the container is alive
# (not just running, but accepting connections).
volumes:
postgres_data:
Main Commands
| Command | What it does |
|---|---|
docker compose up -d |
Start all services in background |
docker compose down |
Stop and remove containers and networks |
docker compose logs -f |
Logs of all services in real time |
docker compose exec app bash |
Enter a running container |
docker compose ps |
Show running services |
What to Remember
- Docker Compose describes the entire infrastructure in one YAML file
- One command
docker compose upbrings up the entire application - Services communicate with each other by names via an internal network
- Volumes persist data between restarts
- Ideal for local development
Middle Level
Key Features
- Declarativity — you describe the desired state in
docker-compose.yml. - Environment isolation — a separate network is created for each project, allowing multiple copies to run without port conflicts.
- Dependency management —
depends_onwithcondition: service_healthywaits for the DB to be ready. - Data persistence — volumes for DB data persistence.
Compose is better because: one command instead of N docker run commands, automatic networking between services, declarative description instead of imperative commands.
YAML File Structure
version: "3.8"
services: # Application containers
app:
build: .
ports: ["8080:8080"]
environment:
DATABASE_URL: jdbc:postgresql://db:5432/mydb
depends_on:
db: { condition: service_healthy } # waits for DB readiness, not just container start
networks: [app-network]
db:
image: postgres:15
volumes: [postgres_data:/var/lib/postgresql/data]
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 5s
networks: [app-network]
networks: # Networks for service communication
app-network:
driver: bridge
volumes: # External storage
postgres_data:
Typical Mistakes
| Mistake | Consequence | How to avoid |
|---|---|---|
depends_on without healthcheck |
App starts before DB is ready | Use condition: service_healthy |
| Hardcoded passwords in YAML | Secret leakage in git | Use .env files |
No restart policy |
Container doesn’t restart on crash | restart: unless-stopped |
| Port conflicts on host | “Port already in use” | Use different ports or no ports for internal services |
Forgetting networks |
All services on default network, no isolation | Create a separate network |
When NOT to Use Docker Compose
DON’T use Compose for: production with >1 server, zero-downtime deploy requirements, horizontal scaling, self-healing. For these — use Kubernetes.
Docker Compose in Development vs Production
- In development — the ideal tool. With one command a developer brings up backend, frontend, Postgres, Redis, and Kafka.
- In production — used only for small projects on a single server. For scalable systems, orchestrators are used (Kubernetes, Docker Swarm, Nomad).
Useful Features
- Environment Variables — support for
.envfiles for storing settings. - Profiles (v3.9+) — run only part of services:
docker compose --profile monitoring up. - Healthchecks — Compose waits for full DB readiness before starting dependent services.
- Override files —
docker-compose.override.ymlfor local settings on top of the main file.
What to Remember
- Docker Compose is one of the most popular tools for local development (alternatives: docker run scripts, devcontainer, Tilt, Okteto)
- Automates configuration of connections between microservices
- Don’t store passwords in YAML files, use
.env - For deployment use Helm charts or K8s manifests
depends_onwithout healthcheck doesn’t guarantee readiness order
Senior Level
Docker Compose as an Architectural Tool
Docker Compose is not just “convenient launch” — it is a declarative description of a microservice system topology. It defines: what services exist, how they are connected, what data is persistent, how service discovery works.
Internal Workings
Docker Compose is not a daemon. It:
- Parses
docker-compose.ymland builds a dependency graph - Converts the compose model into Docker API calls (create network → create volume → create containers → start)
- With
depends_onand healthcheck — polls health status before starting dependent services - Creates a Docker network with embedded DNS — services resolve each other by name
Trade-offs
| Aspect | Docker Compose | Kubernetes |
|---|---|---|
| Complexity | Low (YAML + 1 command) | High (many objects) |
| Scaling | Manual (docker compose up --scale) |
Automatic (HPA) |
| Self-healing | No | Yes |
| Rolling updates | No | Yes |
| Service discovery | DNS within compose network | kube-proxy + CoreDNS |
| Multi-node | No (single host) | Yes |
| Learning curve | Hours | Weeks/months |
Edge Cases
depends_ondoesn’t wait for readiness by default:depends_onguarantees only the start order of containers, not the readiness order. The DB service may have started but not yet accepting connections. Solution:condition: service_healthy+ healthcheck.- Network name resolution: Compose creates a network with DNS. Service
dbresolves asdb. But ifdocker compose upis run from different directories, the networks are different —dbin project A ≠dbin project B. - Volume naming:
postgres_datain Compose gets a project prefix:myproject_postgres_data. This can break migrations when renaming the project. - Resource limits: Compose v3 supports
deploy.resources, but only for Docker Swarm. For standalone Docker use v2 syntaxmem_limit,cpus. - Windows paths: on Windows volumes are mounted via CIFS/SMB. Issues with permissions, performance (especially with many small files), symlinks.
Production Approach for Small Systems
version: "3.8"
services:
app:
image: myregistry.com/app:${APP_VERSION:-latest} # pin version
restart: unless-stopped
deploy:
resources:
limits:
cpus: "2"
memory: 1G
reservations:
cpus: "0.5"
memory: 512M
environment:
- SPRING_PROFILES_ACTIVE=prod
- DATABASE_URL=jdbc:postgresql://db:5432/mydb
depends_on:
db: { condition: service_healthy }
networks: [app-net]
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
db:
image: postgres:15.4-alpine
restart: unless-stopped
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
networks: [app-net]
volumes:
postgres_data:
driver: local
networks:
app-net:
driver: bridge
Security
- Don’t store secrets in YAML — use
.envfiles added to.gitignore. - Docker secrets — for Swarm mode:
echo "password" | docker secret create db_pass -. - Read-only containers —
read_only: true+tmpfsfor temporary files. - Non-root user —
user: "1000:1000"in service definition. - Network isolation — separate networks for frontend/backend services.
Performance
| Metric | Compose (single host) | Kubernetes (cluster) |
|---|---|---|
| Max services | ~50-100 (host limit) | Thousands |
| Startup time all services | 10-60s | 1-5 minutes |
| RAM consumption | Minimal (containers only) | 500MB+ (system components) |
| Network overhead | Bridge network, minimal | kube-proxy, CNI overlay |
Migration Compose → Kubernetes
Conversion tools exist:
- Kompose —
kompose convert -f docker-compose.yml→ K8s manifests - Docker Compose for K8s (experimental) —
docker compose updirectly to K8s - Okteto — development in K8s with Compose-like DX
But automatic conversion doesn’t give production-ready K8s manifests. You need to manually configure: HPA, PDB, ingress, liveness/readiness probes, resource quotas.
Production Story
A startup of 5 developers used Docker Compose for production on a single VPS (8 CPU, 16GB RAM). Application: 6 microservices + PostgreSQL + Redis + Nginx. It ran stably for 18 months. When traffic grew 10x, Compose was no longer sufficient: no horizontal scaling, no rolling updates, no self-healing. Migration to Kubernetes took 3 weeks but gave: automatic scaling under load, zero-downtime deploy, automatic recovery on crash. Compose remained for local development — as “launch documentation.”
Monitoring
docker compose ps— service statusdocker compose top— processes in containersdocker stats— real-time CPU/RAM/network- Log aggregation:
driver: json-file+ fluentd/Fluentbit for centralized logging - Healthcheck status:
docker compose psshows unhealthy services
Summary
- Docker Compose is the standard for local development and prototyping.
- Allows automating the configuration of connections between microservices.
depends_onwithout healthcheck doesn’t guarantee readiness order — a critical mistake.- Acceptable for production on a single server, but with limitations (no scaling, no self-healing).
- In large systems — Compose as “local launch documentation”, for deployment — K8s.
- Security:
.envfor secrets, non-root user, read-only filesystem.
Interview Cheat Sheet
Must know:
- Docker Compose — declarative launch of multi-container applications from a single YAML file
- Services communicate via internal network by names (embedded DNS)
depends_onwithoutcondition: service_healthyguarantees start order, not readiness- Volumes ensure data persistence between container restarts
- Compose is the standard for local development; for production — Kubernetes
- Override files (
docker-compose.override.yml) for local settings - Kompose converts Compose → K8s manifests (but not production-ready)
Frequent follow-up questions:
- “Why is depends_on without healthcheck insufficient?” — DB container started, but not yet accepting connections
- “How do services find each other?” — Compose creates a network with DNS; service
dbresolves asdb - “Can Compose be used in production?” — Yes, for small projects on a single server (no HA, scaling)
- “What are profiles in Compose?” — Allow running only part of services (
--profile monitoring)
Red flags (DO NOT say):
- “Compose replaces Kubernetes” (no HA, self-healing, rolling updates)
- “I store passwords directly in docker-compose.yml” (use .env, Docker secrets)
- “depends_on guarantees DB is ready” (only guarantees start order)
- “Compose works across multiple servers” (single host only)
Related topics:
- [[What is Kubernetes and why is it needed]] — orchestration for production
- [[What is containerization and why is it needed]] — containerization basics
- [[How to monitor applications in Kubernetes]] — monitoring for production