Docker Compose
A tool for defining and running multi-container Docker applications using a YAML file. A single docker compose up command creates and starts all services, networks, and volumes defined in the compose file.
Docker Compose — One Command to Run Your Entire Application Stack
What Is Docker Compose in Simple Terms?
Docker Compose solves the problem of running multi-container applications. A real application is never just one container — it needs a web server, a database, a cache, and maybe a message queue. Without Compose, you would start each container separately with individual docker run commands, manage their networks manually, and remember all the flags and configuration every time.
With Docker Compose, you write one YAML file that describes your entire application stack — what containers to run, how they connect, where data is stored — and a single docker compose up command starts everything at once. One command to start, one command to stop, one file to understand the whole application.
+------------------------------------------+| docker-compose.yml || || services: || api: <- payment API container || postgres: <- database container || redis: <- cache container || || networks: || app-network: <- how they connect || || volumes: || postgres-data: <- where data persists |+------------------------------------------+ | | docker compose up -d v+------------------------------------------+| Three containers running || All on app-network (DNS works) || Data persisted in postgres-data volume || api reaches postgres by name: postgres |+------------------------------------------+A Complete docker-compose.yml Example
version: "3.8" services: payment-api: image: registry.razorpay.in/payment-api:v3.1.0 ports: - "8080:8080" # host:container environment: NODE_ENV: production DB_HOST: postgres # use service name as hostname REDIS_HOST: redis depends_on: postgres: condition: service_healthy # wait for postgres to be ready restart: unless-stopped deploy: resources: limits: cpus: "1.5" memory: 512M postgres: image: postgres:15-alpine environment: POSTGRES_DB: payments POSTGRES_USER: api_user POSTGRES_PASSWORD: secret volumes: - postgres-data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U api_user -d payments"] interval: 10s timeout: 5s retries: 5 redis: image: redis:7-alpine volumes: - redis-data:/data volumes: postgres-data: # Docker creates and manages this redis-data:Essential Compose Commands
# Start all services in the backgrounddocker compose up -d # Start and rebuild images firstdocker compose up -d --build # View status of all servicesdocker compose ps# NAME STATUS PORTS# payment-api Up 5 minutes 0.0.0.0:8080->8080/tcp# postgres Up 5 minutes (healthy)# redis Up 5 minutes # View logs from all servicesdocker compose logs # Follow logs from a specific servicedocker compose logs -f payment-api # Run a command in a running servicedocker compose exec postgres psql -U api_user # Stop and remove containers (keeps volumes = keeps data)docker compose down # Stop and remove containers AND volumes (DELETES ALL DATA)docker compose down -v# WARNING: This permanently deletes all volume data # Restart a specific servicedocker compose restart payment-api # Scale a service to multiple instancesdocker compose up -d --scale payment-api=3 # Pull latest images for all servicesdocker compose pull # Build images that have a build contextdocker compose build # See resource usagedocker compose topService Discovery in Compose
Docker Compose automatically creates a user-defined bridge network for the project. Every service can reach every other service using the service name as the hostname:
services: api: environment: DB_HOST: postgres # reaches the postgres service REDIS_HOST: redis # reaches the redis service AMQP_HOST: rabbitmq # reaches rabbitmq# Verify from inside the api containerdocker compose exec api ping postgres# PING postgres (172.20.0.3)# Service names are DNS hostnames automaticallyCompose v1 vs Compose v2
# Compose v1 (old — separate binary)docker-compose up # uses docker-compose command # Compose v2 (current — built into Docker CLI)docker compose up # uses docker compose command (space not hyphen) # Check your versiondocker compose version# Docker Compose version v2.23.0 # Always use v2 (docker compose) on modern systems# The compose file format is identical between versionsEnvironment Variables and Secrets
# .env file — loaded automatically by Compose# Add to .gitignore — never commit secretsDB_PASSWORD=supersecretAPI_KEY=abc123NODE_ENV=production # Reference in docker-compose.ymlservices: api: environment: DB_PASSWORD: ${DB_PASSWORD} # from .env file API_KEY: ${API_KEY} # Verify variables are loaded correctlydocker compose config# Shows fully resolved compose file with all substitutions appliedCommon Mistakes
| Mistake | Consequence | Fix |
|---|---|---|
docker compose down -v carelessly |
All database data deleted permanently | Know what -v does — only use intentionally |
No depends_on with health checks |
API crashes because DB not ready | Add condition: service_healthy with healthcheck |
| Hardcoding secrets in compose file | Secrets committed to git | Use .env file (in .gitignore) with ${VAR} |
| No resource limits | One service starves others | Always set deploy.resources.limits |
Using container_name with scaling |
Name conflict when scaling | Remove container_name for scalable services |
TipRun
docker compose configto see the fully resolved compose file with all variable substitutions applied. This is the fastest way to verify your.envfile is being read correctly and that all service configurations look exactly as expected before starting the stack.
Remember
docker compose downkeeps your volumes and data.docker compose down -vdeletes your volumes and all data inside them permanently. This is the most common cause of accidental data loss with Docker Compose. Know exactly which flag you are using.
Common MistakeUsing
depends_onwithout thecondition: service_healthyclause. Basicdepends_on: postgresonly waits for the container to start — not for PostgreSQL to be ready to accept connections. PostgreSQL takes 2-5 seconds to initialise after container start. Without the health check condition, your API will try to connect before the database is ready and crash.
SecurityNever put secrets directly in docker-compose.yml — they end up in version control. Use a
.envfile for local development and a proper secret manager (AWS Secrets Manager, HashiCorp Vault) for production. The.envfile must be in your.gitignorefrom day one.
Frequently Asked Questions
What problem does Compose solve that a shell script with docker run commands doesn't?
Wiring up a multi-container app by hand means remembering network names, volume mounts, environment variables, and startup order across several `docker run` invocations, and re-typing them correctly every time. Compose captures all of that declaratively in one YAML file, and `docker compose up` recreates the entire stack — networks, volumes, and inter-service DNS — from a clean state reproducibly, which is why it became the default way to run local dev environments with a database, cache, and app together.
Where does Compose stop being the right tool?
Compose runs everything on a single Docker host with no built-in self-healing across machines, no rolling updates, and no auto-scaling — if that host dies, everything on it goes down together. It's excellent for local development, CI test environments, and small single-server deployments, but production workloads that need multi-host resilience should move to Kubernetes or Docker Swarm. A common mistake is treating a Compose-based 'production' setup as durable when it has no failover story at all.