Skip to content
Docker Compose Deep Dive — Services, Networks, Volumes & Production Deployments

Docker Compose Deep Dive — Services, Networks, Volumes & Production Deployments

DodaTech Updated Jun 20, 2026 6 min read

Docker Compose defines and runs multi-container Docker applications using a single YAML file. In this deep dive, you’ll learn how to orchestrate services, manage networks and volumes, implement healthchecks, use profiles for environment-specific configs, and understand when Compose fits versus full Kubernetes orchestration.

What you’ll learn: Multi-service Compose files, network and volume configuration, healthchecks, depends_on strategies, profiles, override files, production best practices, and Compose vs Kubernetes comparison. Why it matters: Compose powers local development, CI environments, and even single-host production setups for thousands of teams. Real-world use: DodaZIP’s compression service runs a Compose stack with a Python API, Redis cache, PostgreSQL database, and Nginx reverse proxy — all defined in one compose.yml.

Learning Path

    flowchart LR
  A["Docker Basics<br/>Containers & Images"] --> B["Docker Compose<br/>This Tutorial"]
  B --> C["Production Compose<br/>Overrides & Profiles"]
  C --> D["Kubernetes<br/>Orchestration"]
  style B fill:#f90,color:#fff,stroke-width:2px
  

Compose File Structure

A standard compose.yml has four top-level sections:

services:
  web:
    image: nginx:alpine
    ports:
      - "80:80"
    networks:
      - frontend
    volumes:
      - ./html:/usr/share/nginx/html

networks:
  frontend:
    driver: bridge

volumes:
  data:
    driver: local

Expected output: docker compose up starts Nginx serving your local ./html directory on port 80.

Services Deep Dive

Build Context and Dockerfile

services:
  api:
    build:
      context: .
      dockerfile: Dockerfile.api
      args:
        NODE_ENV: production
    image: myapp/api:latest

depends_on with Healthchecks

services:
  db:
    image: postgres:16-alpine
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5

  api:
    build: ./api
    depends_on:
      db:
        condition: service_healthy

The API waits for PostgreSQL to be truly ready, not just started.

Environment Variables

services:
  app:
    image: myapp:latest
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/mydb
      - DEBUG=true
    env_file:
      - .env.production

Networks Configuration

    flowchart TD
  subgraph "Frontend Network"
    N["Nginx<br/>Proxy"]
  end
  subgraph "Backend Network"
    A["API<br/>Container"]
    R["Redis<br/>Cache"]
  end
  subgraph "Database Network"
    P["PostgreSQL"]
  end
  N -->|"proxy_pass"| A
  A --> R
  A --> P
  
services:
  nginx:
    image: nginx:alpine
    networks:
      - frontend
      - backend
  api:
    build: ./api
    networks:
      - backend
      - dbnet
  db:
    image: postgres:16-alpine
    networks:
      - dbnet

networks:
  frontend:
  backend:
  dbnet:

Volumes: Named vs Bind Mounts

services:
  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./backups:/backups

volumes:
  pgdata:
  • Named volumes (pgdata) — Docker-managed, persisted across restarts
  • Bind mounts (./backups) — map host directory directly

Healthchecks and Restart Policies

services:
  app:
    build: ./app
    healthcheck:
      test: curl -f http://localhost:3000/health || exit 1
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    restart: unless-stopped

Profiles for Dev vs Production

services:
  app:
    image: myapp:latest
    profiles:
      - production
      - staging

  mailhog:
    image: mailhog/mailhog
    profiles:
      - development

  prometheus:
    image: prom/prometheus
    profiles:
      - monitoring

Start specific profiles:

docker compose --profile development up
docker compose --profile production up

Multi-Stage Builds in Compose

services:
  api:
    build:
      context: .
      target: production
      dockerfile: Dockerfile

The Dockerfile:

FROM node:20-alpine AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM base AS development
RUN npm install -g nodemon
CMD ["nodemon", "server.js"]

FROM base AS production
COPY . .
RUN npm run build
CMD ["node", "dist/server.js"]

docker-compose.override.yml

Compose automatically merges compose.override.yml if present:

# compose.override.yml (development)
services:
  app:
    ports:
      - "3000:3000"
    volumes:
      - .:/app
    environment:
      - DEBUG=true

Your base compose.yml stays production-ready; override only for local dev.

Production Best Practices

services:
  app:
    image: myapp:${TAG:-latest}
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: "0.5"
          memory: "512M"
        reservations:
          cpus: "0.25"
          memory: "256M"
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

Docker Compose vs Kubernetes

FeatureDocker ComposeKubernetes
Setup complexityMinimalHigh
Multi-hostNo (single host)Yes
Auto-scalingManual replicasHorizontal Pod Autoscaler
Self-healingRestart onlyFull reconciliation
Service discoveryDNS within ComposeDNS + API
Rolling updatesYesAdvanced strategies
Best forDev/CI/single-hostMulti-host production

Common Errors

  1. Port conflicts — “port is already allocated” means another container or host process uses the same port. Change the host port: "8080:80" instead of "80:80".
  2. depends_on without healthcheck — Container A starts before B but B isn’t ready yet. Use condition: service_healthy for true readiness.
  3. Bind mount permission denied — The container user (often root) can’t access host files owned by your user. Set user IDs in the Compose file or fix host permissions.
  4. Network not found — If you declare a network as external that doesn’t exist, Compose fails. Create it first: docker network create mynet.
  5. Environment variable interpolation${VAR} in Compose files references the host shell environment. If VAR is unset, Compose warns. Set defaults: ${VAR:-default}.
  6. Volume data loss on docker compose down -v — The -v flag removes named volumes. Always back up databases before this command.
  7. Compose file version confusion — The version: field is obsolete since Docker Compose V2. Omit it and use the modern format.
  8. Relative build context paths — The context: path is relative to the Compose file location, not the working directory. Use absolute paths for CI pipelines.

Practice Questions

1. What’s the difference between depends_on with and without condition: service_healthy? Without condition, Compose waits only for the container to start. With condition: service_healthy, it waits for the healthcheck to pass, ensuring the service is actually ready.

2. How do you share data between containers in Compose? Use named volumes mounted into multiple containers, or use a common network with shared volume mounts.

3. What happens when you run docker compose --profile monitoring up? Only services with the monitoring profile (and services with no profile) start. Services with other profiles are excluded.

4. How does Compose resolve service hostnames? Compose creates a DNS network where each service is reachable by its service name. The api service is reachable from web at http://api:3000.

5. Challenge: Multi-tier application stack Create a Compose file with: a React frontend (Nginx), a Node.js API, a PostgreSQL database with healthcheck, Redis cache, and a monitoring profile that adds Prometheus and Grafana. The API should wait for both DB and Redis to be healthy.

Mini Project: Compose-Based CI Pipeline

services:
  test:
    build: ./app
    command: npm test
    environment:
      - DATABASE_URL=postgres://test:test@db:5432/testdb
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test
      POSTGRES_DB: testdb
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U test"]
      interval: 2s
      timeout: 2s
      retries: 10

Run docker compose up test --abort-on-container-exit — tests pass or fail deterministically.

FAQ

Should I use Docker Compose in production?
For single-host production deployments, yes. For multi-host clusters with auto-scaling, use Kubernetes. Compose scales to about 10–15 services per host effectively.
How do I migrate from Compose to Kubernetes?
Use kompose to auto-generate Kubernetes manifests from your Compose file, then manually adjust for scaling, secrets, and ingress.
Can Compose restart individual services without stopping others?
Yes: docker compose restart api restarts only the api service while other services keep running.
How do I debug a failing container in Compose?
Check logs with docker compose logs api, exec into the running container with docker compose exec api sh, or inspect the container with docker compose ps.
What’s the difference between docker compose and docker-compose?
docker compose (V2) is the modern plugin integrated into Docker CLI. docker-compose (V1) is the legacy standalone Python tool. Use V2.

Related Tutorials

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro