Skip to main content

Docker Compose:

 Docker Compose: docker-compose.yml and Multi-Container Applications

📅 Published: Feb 2026
⏱️ Estimated Reading Time: 18 minutes
🏷️ Tags: Docker Compose, Multi-Container, docker-compose.yml, Container Orchestration, DevOps


Introduction: The Multi-Container Challenge

A modern application is rarely a single container. A typical web application might include:

  • A web server container (Nginx)

  • An application container (Node.js, Python, Ruby)

  • A database container (PostgreSQL, MySQL)

  • A cache container (Redis)

  • A background worker container

  • A message queue container

Running these containers individually with docker run commands becomes unmanageable quickly. You need to remember the correct order to start them. You need to configure networks so they can find each other. You need to mount volumes for persistent data. You need to document all these commands.

Docker Compose solves this problem. It lets you define your entire multi-container application in a single YAML file. With one command, you can start everything.

This guide covers the essential features of Docker Compose that you will use daily.


Part 1: What is Docker Compose?

The Simple Definition

Docker Compose is a tool for defining and running multi-container Docker applications. You define your application's services, networks, and volumes in a docker-compose.yml file. Then, with a single command, you create and start all the services.

Think of Docker Compose as the conductor of an orchestra. Each container is a musician. The compose file is the sheet music. The docker-compose up command is the conductor raising the baton.

What Compose Does

  • Defines services: What containers to run and how to configure them

  • Creates networks: Automatically sets up a network so services can communicate

  • Manages volumes: Creates and attaches volumes for persistent data

  • Handles dependencies: Starts services in the correct order

  • Orchestrates lifecycle: Start, stop, rebuild everything with one command


Part 2: docker-compose.yml Basics

The Structure

docker-compose.yml file has three main sections:

yaml
version: '3.8'  # Compose file format version

services:       # Define your containers
  web:
    # configuration for web service
  database:
    # configuration for database service

networks:       # Define custom networks (optional)
  app-network:

volumes:        # Define named volumes (optional)
  db-data:

Version

The version field specifies the Compose file format. Use version '3.8' for current projects. Version 3 files work with both Compose and Docker Swarm.

VersionFeaturesWhen to Use
3.8+Most features, Swarm compatibleNew projects
2.xMore features than v1Legacy projects
1.xBasic featuresAvoid

Part 3: Defining Services

Basic Service Definition

The simplest service definition needs only an image name:

yaml
version: '3.8'

services:
  web:
    image: nginx:alpine
  database:
    image: postgres:15

Service Configuration Options

Image and Build

yaml
services:
  # Use an existing image
  web:
    image: nginx:alpine
  
  # Build from Dockerfile
  app:
    build: ./app  # Path to Dockerfile
    build:
      context: ./app
      dockerfile: Dockerfile.prod
      args:
        NODE_ENV: production

Container Name

yaml
services:
  web:
    container_name: my-web-server
    image: nginx

Without container_name, Compose generates names like project_web_1.

Ports

yaml
services:
  web:
    ports:
      - "80:80"           # host:container
      - "443:443"
      - "8080:80"         # map host 8080 to container 80
      - "127.0.0.1:3000:3000"  # bind to specific host IP

Environment Variables

yaml
services:
  database:
    environment:
      - POSTGRES_PASSWORD=secret
      - POSTGRES_DB=myapp
      - POSTGRES_USER=myuser
  
  app:
    environment:
      NODE_ENV: production
      DATABASE_URL: postgresql://myuser:secret@database:5432/myapp

Environment File

yaml
services:
  app:
    env_file:
      - .env
      - .env.production

Volumes

yaml
services:
  database:
    volumes:
      - db-data:/var/lib/postgresql/data  # named volume
      - ./backup:/backup                   # bind mount
      - ./config/postgres.conf:/etc/postgresql/postgresql.conf:ro  # read-only

volumes:
  db-data:

Networks

yaml
services:
  web:
    networks:
      - frontend
      - backend
  
  database:
    networks:
      - backend

networks:
  frontend:
  backend:

Depends On

yaml
services:
  app:
    build: ./app
    depends_on:
      - database
      - redis
    # Waits for database and redis to start before starting app
  
  database:
    image: postgres
  
  redis:
    image: redis

Note: depends_on only waits for containers to start, not for services to be ready. For databases, you may need additional health checks.

Restart Policy

yaml
services:
  web:
    restart: always        # always restart
  app:
    restart: on-failure    # restart on error
  worker:
    restart: unless-stopped  # restart unless manually stopped

Command Override

yaml
services:
  app:
    image: node:18
    command: npm run start
    # Overrides the default CMD from Dockerfile
  
  database:
    image: postgres
    command: postgres -c shared_buffers=256MB

Health Check

yaml
services:
  web:
    image: nginx
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

Part 4: Complete Example

WordPress with MySQL

Here's a complete docker-compose.yml for WordPress:

yaml
version: '3.8'

services:
  database:
    image: mysql:8.0
    container_name: wordpress-db
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
    volumes:
      - db-data:/var/lib/mysql
    networks:
      - wordpress-network

  wordpress:
    image: wordpress:latest
    container_name: wordpress-app
    restart: unless-stopped
    depends_on:
      - database
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: database:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wordpress-data:/var/www/html
    networks:
      - wordpress-network

volumes:
  db-data:
  wordpress-data:

networks:
  wordpress-network:
    driver: bridge

Run it:

bash
docker-compose up -d

Access WordPress at http://localhost:8080

Node.js Application with PostgreSQL and Redis

yaml
version: '3.8'

services:
  postgres:
    image: postgres:15
    container_name: myapp-postgres
    environment:
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: myapp
    volumes:
      - postgres-data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U myuser"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:alpine
    container_name: myapp-redis
    volumes:
      - redis-data:/data
    ports:
      - "6379:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  app:
    build: ./app
    container_name: myapp-app
    restart: always
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    environment:
      DATABASE_URL: postgresql://myuser:secret@postgres:5432/myapp
      REDIS_URL: redis://redis:6379
      NODE_ENV: production
    ports:
      - "3000:3000"
    volumes:
      - ./app:/app
      - /app/node_modules
    command: npm start

volumes:
  postgres-data:
  redis-data:

Part 5: Docker Compose Commands

Basic Commands

bash
# Start all services (detached mode)
docker-compose up -d

# Start and see logs
docker-compose up

# Stop all services
docker-compose down

# Stop and remove volumes (destroys data!)
docker-compose down -v

# List running services
docker-compose ps

# View logs
docker-compose logs
docker-compose logs -f  # Follow logs
docker-compose logs web  # Logs for specific service

Building and Rebuilding

bash
# Build images before starting
docker-compose up --build

# Rebuild specific service
docker-compose build web

# Force rebuild without cache
docker-compose build --no-cache

Running Commands in Containers

bash
# Run one-off command
docker-compose run app npm run migrate

# Open shell in service
docker-compose exec app /bin/bash

# Run command with environment
docker-compose run -e NODE_ENV=test app npm test

Managing Services

bash
# Start specific service
docker-compose start web

# Stop specific service
docker-compose stop web

# Restart specific service
docker-compose restart web

# Pause all services
docker-compose pause

# Unpause
docker-compose unpause

Scaling Services

bash
# Run 3 instances of web service
docker-compose up -d --scale web=3

# Requires web service to have no port conflicts
# Usually used with load balancer

Part 6: Advanced Features

Profiles

Profiles allow you to selectively start services:

yaml
version: '3.8'

services:
  web:
    image: nginx
    profiles: ["production", "staging"]

  app:
    image: myapp
    profiles: ["production", "staging", "development"]

  admin:
    image: admin-tool
    profiles: ["admin"]

  debug:
    image: debug-tool
    profiles: ["development"]
bash
# Start production services
docker-compose --profile production up -d

# Start development services
docker-compose --profile development up -d

# Start multiple profiles
docker-compose --profile production --profile admin up -d

Variable Substitution

Compose files support environment variables:

yaml
version: '3.8'

services:
  database:
    image: postgres:${POSTGRES_VERSION:-15}
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
bash
# Set variables
export DB_PASSWORD=secret
export POSTGRES_VERSION=16
docker-compose up

# Or use .env file
echo "DB_PASSWORD=secret" > .env
echo "POSTGRES_VERSION=16" >> .env
docker-compose up

Extending Services

You can extend service definitions:

yaml
# docker-compose.yml
version: '3.8'

services:
  base-app:
    image: node:18
    environment:
      NODE_ENV: production
    restart: always

  web:
    extends:
      service: base-app
    command: npm run start:web
    ports:
      - "3000:3000"

  worker:
    extends:
      service: base-app
    command: npm run start:worker

Dependency Conditions

Advanced dependency conditions:

yaml
services:
  app:
    depends_on:
      database:
        condition: service_healthy
      redis:
        condition: service_started

  database:
    image: postgres
    healthcheck:
      test: ["CMD", "pg_isready"]
      interval: 10s

  redis:
    image: redis

Part 7: Real-World Examples

Example 1: LAMP Stack (Linux, Apache, MySQL, PHP)

yaml
version: '3.8'

services:
  mysql:
    image: mysql:8.0
    container_name: lamp-mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: mydb
      MYSQL_USER: user
      MYSQL_PASSWORD: password
    volumes:
      - mysql-data:/var/lib/mysql
    networks:
      - lamp-network

  php:
    build: ./php
    container_name: lamp-php
    volumes:
      - ./www:/var/www/html
    networks:
      - lamp-network
    depends_on:
      - mysql

  apache:
    image: httpd:latest
    container_name: lamp-apache
    ports:
      - "80:80"
    volumes:
      - ./www:/usr/local/apache2/htdocs
      - ./apache-config:/usr/local/apache2/conf
    networks:
      - lamp-network
    depends_on:
      - php

volumes:
  mysql-data:

networks:
  lamp-network:

Example 2: MEVN Stack (MongoDB, Express, Vue, Node.js)

yaml
version: '3.8'

services:
  mongodb:
    image: mongo:6
    container_name: mevn-mongo
    volumes:
      - mongo-data:/data/db
    ports:
      - "27017:27017"
    networks:
      - mevn-network

  backend:
    build: ./backend
    container_name: mevn-backend
    environment:
      - MONGODB_URI=mongodb://mongodb:27017/mevn
      - JWT_SECRET=${JWT_SECRET}
    ports:
      - "5000:5000"
    volumes:
      - ./backend:/app
      - /app/node_modules
    networks:
      - mevn-network
    depends_on:
      - mongodb

  frontend:
    build: ./frontend
    container_name: mevn-frontend
    ports:
      - "8080:8080"
    volumes:
      - ./frontend:/app
      - /app/node_modules
    networks:
      - mevn-network
    depends_on:
      - backend

volumes:
  mongo-data:

networks:
  mevn-network:

Example 3: ELK Stack (Elasticsearch, Logstash, Kibana)

yaml
version: '3.8'

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
    container_name: elk-elasticsearch
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - ES_JAVA_OPTS=-Xms512m -Xmx512m
    volumes:
      - elasticsearch-data:/usr/share/elasticsearch/data
    ports:
      - "9200:9200"
    networks:
      - elk-network

  logstash:
    image: docker.elastic.co/logstash/logstash:8.11.0
    container_name: elk-logstash
    volumes:
      - ./logstash/pipeline:/usr/share/logstash/pipeline
    ports:
      - "5000:5000"
    networks:
      - elk-network
    depends_on:
      - elasticsearch

  kibana:
    image: docker.elastic.co/kibana/kibana:8.11.0
    container_name: elk-kibana
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    ports:
      - "5601:5601"
    networks:
      - elk-network
    depends_on:
      - elasticsearch

volumes:
  elasticsearch-data:

networks:
  elk-network:

Part 8: Best Practices

Project Structure

Organize your Compose projects consistently:

text
myapp/
├── docker-compose.yml
├── .env
├── .env.example
├── Dockerfile
├── app/
│   └── (application code)
├── nginx/
│   └── nginx.conf
└── postgres/
    └── init.sql

Use .env Files

Never hardcode secrets in docker-compose.yml:

yaml
# docker-compose.yml
services:
  database:
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
bash
# .env (never commit this!)
DB_PASSWORD=supersecret
bash
# .env.example (commit this)
DB_PASSWORD=changeme

Name Your Containers

Explicit names make management easier:

yaml
services:
  database:
    container_name: myapp-postgres

Use Health Checks

Ensure services are ready before depending services start:

yaml
services:
  database:
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "user"]
      interval: 10s

  app:
    depends_on:
      database:
        condition: service_healthy

Version Control

  • Commit docker-compose.yml

  • Commit .env.example

  • Never commit .env

  • Never commit local volumes or build artifacts

Resource Limits

Prevent one service from consuming all resources:

yaml
services:
  worker:
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M

Summary

ConceptPurposeKey Command
ServicesDefine containersservices: section
NetworksEnable communicationnetworks: section
VolumesPersistent datavolumes: section
UpStart everythingdocker-compose up -d
DownStop everythingdocker-compose down
LogsView outputdocker-compose logs -f
ExecRun commandsdocker-compose exec app bash

Docker Compose transforms complex multi-container applications into simple, reproducible, version-controlled configurations.


Practice Questions

  1. What are the three main sections of a docker-compose.yml file?

  2. How do you ensure a database service starts before an application that depends on it?

  3. How do you pass environment variables to services without hardcoding them in the compose file?

  4. What command starts all services in detached mode?

  5. How do you view logs for a specific service?


Learn More

Practice Docker Compose with hands-on exercises in our interactive labs:
https://devops.trainwithsky.com/

Comments

Popular posts from this blog

Introduction to Terraform – The Future of Infrastructure as Code

  Introduction to Terraform – The Future of Infrastructure as Code In today’s fast-paced DevOps world, managing infrastructure manually is outdated . This is where Terraform comes in—a powerful Infrastructure as Code (IaC) tool that allows you to define, provision, and manage cloud infrastructure efficiently . Whether you're working with AWS, Azure, Google Cloud, or on-premises servers , Terraform provides a declarative, automation-first approach to infrastructure deployment. Shape Your Future with AI & Infinite Knowledge...!! Read In-Depth Tech & Self-Improvement Blogs http://www.skyinfinitetech.com Watch Life-Changing Videos on YouTube https://www.youtube.com/@SkyInfinite-Learning Transform Your Skills, Business & Productivity – Join Us Today! In today’s digital-first world, agility and automation are no longer optional—they’re essential. Companies across the globe are rapidly shifting their operations to the cloud to keep up with the pace of innovatio...

📊 Monitoring & Logging in Kubernetes – Tools like Prometheus, Grafana, and Fluentd

  Monitoring & Logging in Kubernetes – Tools like Prometheus, Grafana, and Fluentd Monitoring and logging are essential for maintaining a healthy and well-performing Kubernetes cluster. In this guide, we’ll cover why monitoring is important, key monitoring tools like Prometheus and Grafana, and logging tools like Fluentd to help you gain visibility into your cluster’s performance and logs. Shape Your Future with AI & Infinite Knowledge...!! Want to Generate Text-to-Voice, Images & Videos? http://www.ai.skyinfinitetech.com Read In-Depth Tech & Self-Improvement Blogs http://www.skyinfinitetech.com Watch Life-Changing Videos on YouTube https://www.youtube.com/@SkyInfinite-Learning Transform Your Skills, Business & Productivity – Join Us Today! 🚀 Introduction In today’s fast-paced cloud-native environment, Kubernetes has emerged as the de-facto container orchestration platform. But deploying and managing applications in Kubernetes is just half the ba...

🔒 Kubernetes Security – RBAC, Network Policies, and Secrets Management

  Kubernetes Security – RBAC, Network Policies, and Secrets Management Security is a critical aspect of managing Kubernetes clusters. In this guide, we'll cover essential security mechanisms like Role-Based Access Control (RBAC) , Network Policies , and Secrets Management to help you secure your Kubernetes environment effectively. Shape Your Future with AI & Infinite Knowledge...!! Want to Generate Text-to-Voice, Images & Videos? http://www.ai.skyinfinitetech.com Read In-Depth Tech & Self-Improvement Blogs http://www.skyinfinitetech.com Watch Life-Changing Videos on YouTube https://www.youtube.com/@SkyInfinite-Learning Transform Your Skills, Business & Productivity – Join Us Today! 🚀 Introduction: Why Kubernetes Security Is Non-Negotiable As Kubernetes becomes the backbone of modern cloud-native infrastructure, security is no longer optional—it’s mission-critical . With multiple moving parts like containers, pods, services, nodes, and more, Kuberne...