Environment Management

Docker Compose & Multi-Service Apps

Define and run multi-container applications — app, database, cache, and workers — with a single YAML file.

What Docker Compose Is

Real applications rarely run as a single service. A typical web app needs a server, a database, a cache, and maybe a background worker. Docker Compose lets you define all of these services and run them together with a single command: docker compose up.

Instead of manually starting four containers with four separate commands and configuring how they connect, you write one docker-compose.yml file that describes everything.

The docker-compose.yml Structure

yaml
version: '3.9'

services:
  # Your Next.js application
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://postgres:password@db:5432/myapp
      - REDIS_URL=redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started

  # PostgreSQL database
  db:
    image: postgres:16-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  # Redis cache
  cache:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

volumes:
  postgres_data:
  redis_data:

Key Concepts

Services — Each service is a container. Each gets its own image, configuration, and network identity.

Volumes — Named volumes persist data across container restarts. Without a volume, your database loses all data when the container stops.

Networks — By default, all services in a Compose file share a network. Services communicate using their service name as the hostname. Notice the database URL uses db as the host — that is the service name.

depends_on — Ensures services start in order and can wait for health checks before considering a dependency ready.

Development Workflow

For local development, mount your source code as a volume for hot-reloading:

yaml
services:
  web:
    build:
      context: .
      target: development
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development
    command: npm run dev

This mounts your local files into the container, so code changes are reflected immediately without rebuilding.

Essential Compose Commands

bash
# Start all services (build if needed)
docker compose up

# Start in background
docker compose up -d

# Stop all services
docker compose down

# Stop and remove volumes (resets databases)
docker compose down -v

# View logs for all services
docker compose logs -f

# View logs for one service
docker compose logs -f web

# Run a one-off command in a service container
docker compose exec web sh

# Rebuild images before starting
docker compose up --build

Compose Profiles

Use profiles to define optional services that don't run by default:

yaml
services:
  web:
    build: .

  monitoring:
    image: grafana/grafana
    profiles:
      - monitoring

  adminer:
    image: adminer
    profiles:
      - dev-tools

Start with optional services: docker compose --profile monitoring up

Key Takeaways

  • Docker Compose defines multi-service applications in a single YAML file — run everything with one command
  • Services communicate by service name (not localhost) over Docker's internal network
  • Named volumes persist data across container restarts — without them, your database resets every time
  • Volume mounts in development enable hot-reloading without rebuilding the image
  • Compose is for development and small deployments; Kubernetes is for large-scale production orchestration

Example

yaml
# docker-compose.yml for a full-stack app
version: '3.9'
services:
  web:
    build: .
    ports: ["3000:3000"]
    environment:
      DATABASE_URL: postgresql://postgres:password@db:5432/myapp
      REDIS_URL: redis://cache:6379
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      retries: 5

  cache:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

volumes:
  postgres_data:
  redis_data:
Try it yourself — YAML

Docker, AWS, Vercel, Netlify, GitHub, GitHub Actions are trademarks of Docker, Inc., Amazon.com, Inc., Vercel, Inc., Netlify, Inc., Microsoft Corporation. DevForge Academy is not affiliated with, endorsed by, or sponsored by these companies. Referenced for educational purposes only. See full disclaimers