Skip to main content
DevopsIntermediate14 min readUpdated March 2025

Docker Compose

Docker Compose is a tool for defining and running multi-container Docker applications. With a single YAML file, you declare all your services, networks, and volumes — then spin the entire stack up or down with one command.

Why Docker Compose?

Real applications rarely consist of a single container. A typical web application might need: - A web server (Node.js, Python, Go) - A database (PostgreSQL, MySQL, MongoDB) - A cache layer (Redis) - A message queue (RabbitMQ, Kafka) - A reverse proxy (Nginx)

Managing all these containers individually with docker run commands is tedious and error-prone. Docker Compose solves this by letting you define the entire multi-container application in a single docker-compose.yml file and manage it with simple commands.

The docker-compose.yml Structure

The Compose file uses YAML syntax and has four top-level keys:

  • version — Compose file format version (use "3.9" for modern features)
  • services — Each container in your application (web, db, cache, etc.)
  • networks — Custom networks for service communication
  • volumes — Named volumes for persistent data storage

Full Stack Example: Web App + PostgreSQL + Redis

Here is a production-style docker-compose.yml for a full-stack application:

yaml
version: "3.9"

services:
  # ---- Web Application ----
  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://user:password@db:5432/myapp
      - REDIS_URL=redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    networks:
      - app-network
    restart: unless-stopped

  # ---- PostgreSQL Database ----
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: myapp
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-network

  # ---- Redis Cache ----
  cache:
    image: redis:7-alpine
    command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
    volumes:
      - redis_data:/data
    networks:
      - app-network

  # ---- Nginx Reverse Proxy ----
  nginx:
    image: nginx:alpine
    ports:
      - "80:80" -"443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - web
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

volumes:
  postgres_data:
  redis_data:

Docker Compose CLI Commands

The Compose CLI wraps Docker commands for your entire application stack:

bash
# Start all services (build if needed, run in background)
docker compose up -d

# Start and force rebuild images
docker compose up -d --build

# Stop all services (containers remain)
docker compose stop

# Stop and remove containers, networks
docker compose down

# Stop and remove containers, networks, AND volumes (data loss!)
docker compose down -v

# View logs for all services
docker compose logs -f

# View logs for a specific service
docker compose logs -f web

# Scale a service to N replicas
docker compose up -d --scale web=3

# Run a one-off command in a service container
docker compose exec web bash
docker compose exec db psql -U user -d myapp

# List running services
docker compose ps

# Pull latest images for all services
docker compose pull

Environment Variables and .env Files

Docker Compose supports environment variable substitution, making it easy to manage configuration across environments:

Using a .env file: Compose automatically reads a .env file in the same directory and substitutes variables in the compose file.

Best practice: Never commit secrets to version control. Use .env files locally and secrets management (Vault, AWS Secrets Manager) in production.

bash
# .env file (never commit to git!)
POSTGRES_USER=myuser
POSTGRES_PASSWORD=supersecret
POSTGRES_DB=myapp
APP_PORT=3000

# docker-compose.yml references variables with ${VARIABLE_NAME}
# services:
#   db:
#     environment:
#       POSTGRES_USER: ${POSTGRES_USER}
#       POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

# Override for different environments
docker compose --env-file .env.production up -d

# Multiple compose files (base + override)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Health Checks and Dependencies

The depends_on key controls startup order, but it only waits for the container to start — not for the service inside to be ready. Use healthchecks to wait for actual readiness:

- condition: service_started — Container is running (default) - condition: service_healthy — Container passes its healthcheck - condition: service_completed_successfully — For one-off init containers

Always define healthchecks for databases and critical services to prevent race conditions during startup.

Key Takeaways

  • Docker Compose manages multi-container applications with a single docker-compose.yml file.
  • Use depends_on with healthchecks to ensure services start in the correct order.
  • Named volumes persist data across container restarts; bind mounts share host files.
  • The .env file provides environment-specific configuration without hardcoding secrets.
  • docker compose up -d starts the entire stack; docker compose down tears it down cleanly.

Contact Us

Have a question or feedback? Fill out the form below or reach us directly at support@nvaitraining.com