Docker Compose Basics

Docker Compose is a tool for defining and running multi-container Docker applications. With a single YAML file, you can configure all your application's services, networks, and volumes. This guide covers everything you need to get started with Docker Compose.

docker-compose.yml Services Networks Volumes
What is Docker Compose?

Docker Compose is a tool that allows you to define and run multi-container Docker applications. Instead of running multiple docker run commands with complex networking and volume configurations, you define everything in a single YAML file called docker-compose.yml. With one command, Compose starts all your services, connects them on a shared network, and manages their lifecycle together.

Compose is ideal for development, testing, and staging environments, as well as simple production deployments. It's the standard way to orchestrate multiple containers on a single host. Every service defined in the compose file runs as a separate container.

Docker Compose is included with Docker Desktop. On Linux, you may need to install it separately. Check with docker compose version (modern) or docker-compose --version (legacy).
Installing Docker Compose
# Docker Desktop (macOS/Windows) # Compose is included automatically # Linux (Ubuntu/Debian) - install via apt sudo apt update sudo apt install docker-compose-plugin # Or manually install latest version sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose # Verify installation docker compose version # or legacy docker-compose --version
docker-compose.yml Structure

A Docker Compose file has three main sections: services (define your containers), networks (define custom networks), and volumes (define persistent storage). The services section is the only required section, but networks and volumes are commonly used.

# docker-compose.yml - Basic structure version: '3.8' services: web: image: nginx:alpine ports: - "8080:80" networks: - app-network app: build: ./app environment: - NODE_ENV=production depends_on: - database networks: - app-network database: image: postgres:15 volumes: - postgres_data:/var/lib/postgresql/data networks: - app-network networks: app-network: driver: bridge volumes: postgres_data:
Services: Defining Your Containers

Each service represents a container. You can define services using existing images (with image) or build from a Dockerfile (with build). Services can have ports, volumes, environment variables, dependencies, and more.

services: # Using an existing image web: image: nginx:alpine ports: - "80:80" restart: always # Building from a Dockerfile app: build: context: ./app dockerfile: Dockerfile.prod args: NODE_VERSION: 18 image: myapp:latest container_name: myapp-container working_dir: /app user: node # With environment variables api: image: myapi:latest environment: - DATABASE_URL=postgresql://db:5432/mydb - API_KEY=secret123 env_file: - .env.production # With command override worker: image: myapp:latest command: npm run worker entrypoint: /bin/sh -c "wait-for-it db:5432 && npm run worker"
Networks: Connecting Services

Compose automatically creates a default network for all services. Services can reach each other by their service name (e.g., database resolves to the database container's IP). You can also define custom networks for more control over isolation.

services: frontend: image: nginx networks: - frontend-net - backend-net backend: image: node:18 networks: - backend-net # Cannot access frontend-net (isolated) database: image: postgres networks: - backend-net networks: frontend-net: driver: bridge ipam: config: - subnet: 172.20.0.0/16 gateway: 172.20.0.1 backend-net: driver: bridge external: true # Use existing network
Services on the same network can communicate using the service name as the hostname. For example, the backend service can connect to database:5432.
Volumes: Persistent Data

Volumes persist data beyond container lifecycles. You can use named volumes (managed by Docker) or bind mounts (host directories). Named volumes are recommended for production data.

services: postgres: image: postgres:15 volumes: - postgres_data:/var/lib/postgresql/data # Named volume - ./backups:/backups:ro # Bind mount (read-only) - type: tmpfs target: /dev/shm tmpfs: size: 100M redis: image: redis:alpine volumes: - redis_data:/data - ./redis.conf:/usr/local/etc/redis/redis.conf:ro volumes: postgres_data: driver: local driver_opts: type: none device: /mnt/ssd/postgres o: bind redis_data: external: true # Use existing volume
Complete Example: Web App + Database + Redis
# docker-compose.yml version: '3.8' services: nginx: image: nginx:alpine ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro depends_on: - app networks: - webnet app: build: . environment: - DATABASE_URL=postgresql://postgres:secret@db:5432/myapp - REDIS_URL=redis://redis:6379 depends_on: - db - redis networks: - webnet - backend db: image: postgres:15 environment: - POSTGRES_DB=myapp - POSTGRES_USER=postgres - POSTGRES_PASSWORD=secret volumes: - postgres_data:/var/lib/postgresql/data networks: - backend redis: image: redis:alpine command: redis-server --appendonly yes volumes: - redis_data:/data networks: - backend volumes: postgres_data: redis_data: networks: webnet: backend: internal: true # No external access to backend network
This example shows a complete web application stack with reverse proxy (nginx), application server (custom image), database (PostgreSQL), and cache (Redis).
Essential Docker Compose Commands
# Start all services (detached mode) docker compose up -d # Build images before starting docker compose up -d --build # View logs docker compose logs docker compose logs -f web # Follow logs for web service # List running services docker compose ps # Stop all services docker compose down # Stop and remove volumes (caution: deletes data) docker compose down -v # Run one-off command docker compose run app npm test # Execute command in running container docker compose exec app bash # Build images without starting docker compose build # Pull latest images docker compose pull # Restart services docker compose restart web # View resource usage docker compose top
Environment Variables in Compose

Compose supports environment variables in three ways: directly in the YAML file, from an env_file, or from the shell environment. This makes it easy to configure different environments (dev, staging, prod).

# .env file (auto-loaded by compose) DB_PASSWORD=secret APP_VERSION=1.2.3 # docker-compose.yml services: app: image: myapp:${APP_VERSION:-latest} environment: - DATABASE_URL=postgresql://db:5432/mydb - API_KEY=${API_KEY} # From shell environment env_file: - .env.production - ./config/secrets.env # Run with custom environment variables export API_KEY=abc123 docker compose up -d
Extending Compose Files (Multiple Environments)

You can use multiple compose files to override configurations for different environments. This is a powerful pattern for development vs production setups.

# docker-compose.yml (base) services: app: image: myapp ports: - "3000:3000" # docker-compose.dev.yml (development overrides) services: app: build: . volumes: - .:/app environment: - NODE_ENV=development # docker-compose.prod.yml (production overrides) services: app: restart: always environment: - NODE_ENV=production # Run development docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d # Run production docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Docker Compose Best Practices
  • Use version 3.8+ - The latest compose file format supports all modern features.
  • Use depends_on for startup order - But remember Compose only waits for container start, not service readiness.
  • Add health checks to services - Allows Compose to know when services are actually ready.
  • Use named volumes for persistent data - Never use bind mounts for production databases.
  • Keep compose files in version control - This is your infrastructure as code.
  • Use .env file for secrets - Never hardcode passwords in compose files.
  • Set resource limits - Prevent containers from consuming all host resources.
  • Use restart policies - restart: unless-stopped for production services.
# Resource limits example services: app: image: myapp deploy: resources: limits: cpus: '0.5' memory: 512M reservations: cpus: '0.25' memory: 256M restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3
Frequently Asked Questions
What's the difference between docker run and docker compose?
docker run starts a single container with options. docker compose starts multiple containers defined in a YAML file, handles networking, volumes, and dependencies automatically. Use compose for multi-container apps.
How do I update a container in Compose without downtime?
Use docker compose up -d --no-deps --build <service>. For zero-downtime, use rolling updates with replicas and a load balancer, or use orchestration tools like Swarm/Kubernetes.
Can I use Compose in production?
Yes, for simple deployments. However, for high availability, rolling updates, and multi-host, consider Docker Swarm or Kubernetes. Compose works well for single-host production deployments.
How do I see logs for a specific service?
Use docker compose logs -f <service-name> to tail logs for that service. Use docker compose logs for all services.
What's the difference between depends_on and health checks?
depends_on waits for container to start (not ready). Health checks wait for the service to be actually functional. Use both: depends_on for start order, health checks for readiness.
How do I scale a service in Compose?
Use docker compose up -d --scale service=3. Compose will run 3 instances of that service. Note: not all services are designed to scale horizontally.
Why are my services not connecting to each other?
Services on the same default network can reach each other using service name as hostname. Check network configuration. Use docker compose exec app ping database to test connectivity.
How do I stop and remove everything (including volumes)?
Use docker compose down -v. This stops containers and removes networks AND volumes. Warning: This deletes persistent data. Use without -v to preserve volumes.
Previous: Storage Drivers Next: Docker Compose Commands

Docker Compose transforms complex multi-container setups into simple, reproducible configurations. Start with a basic compose file and gradually add features as you learn.