Docker Compose Basics

An introduction to managing multi-container applications with Docker Compose

What is Docker Compose?

Docker Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application's services, networks, and volumes. Then, with a single command, you create and start all the services from your configuration.

The Orchestra Analogy

Think of Docker Compose as the conductor of an orchestra:

  • Individual containers are like musicians with different instruments
  • The docker-compose.yml file is the musical score that everyone follows
  • The compose command is the conductor, directing when and how each musician should play
  • The resulting application is the harmonious symphony when all parts work together

Just as a conductor coordinates many musicians to create music that no single instrument could produce alone, Docker Compose orchestrates multiple containers to create an application ecosystem greater than any single container.

flowchart TD A[docker-compose.yml] --> B[docker-compose up] B --> C[Docker Engine] C --> D{Creates and Manages} D --> E[Container 1\nWeb App] D --> F[Container 2\nDatabase] D --> G[Container 3\nCache] E <--> F E <--> G

Why Use Docker Compose?

Docker Compose solves several critical problems in container development and deployment:

When to Use Docker Compose

Real-World Scenarios

Consider these practical applications:

  • MERN Stack: MongoDB + Express.js + React + Node.js
  • WordPress: WordPress application + MySQL database
  • Microservices: Multiple independent services that work together
  • E-commerce platform: Web app + database + Redis cache + Elasticsearch

The Docker Compose File

The heart of Docker Compose is the docker-compose.yml file that defines your application's services, networks, volumes, and more. This YAML file serves as the blueprint for your multi-container application.

Basic Structure of docker-compose.yml

version: '3.8'

services:
  service-name-1:
    # configuration for first service
    
  service-name-2:
    # configuration for second service

volumes:
  # named volumes defined here

networks:
  # custom networks defined here

Anatomy of a Service Definition

Each service in your Compose file represents a container. Here's a breakdown of common configuration options:

classDiagram class Service { image: string build: object ports: array volumes: array environment: array/object depends_on: array restart: string networks: array }

Example: Simple Web Application with Database

version: '3.8'

services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./website:/usr/share/nginx/html
    depends_on:
      - api

  api:
    build: ./api
    ports:
      - "3000:3000"
    environment:
      DB_HOST: database
      DB_USER: myuser
      DB_PASSWORD: mypassword
      DB_NAME: myapp
    depends_on:
      - database

  database:
    image: mysql:8.0
    volumes:
      - db_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: myapp
      MYSQL_USER: myuser
      MYSQL_PASSWORD: mypassword
    ports:
      - "3306:3306"

volumes:
  db_data:

Key Components Explained

Essential Docker Compose Commands

Docker Compose Lifecycle Creation Phase docker-compose up docker-compose create docker-compose run Management Phase docker-compose start docker-compose stop docker-compose restart Observation Phase docker-compose ps docker-compose logs docker-compose top docker-compose down

Primary Commands

Additional Commands

Practical Example: MERN Stack Application

Let's set up a complete MERN (MongoDB, Express, React, Node.js) stack application using Docker Compose:

graph TD A[Client Browser] --> B[React Frontend Container] B --> C[Node.js/Express API Container] C --> D[MongoDB Container] E[Developer] --> F[docker-compose.yml] F -->|docker-compose up| B F -->|docker-compose up| C F -->|docker-compose up| D

Project Structure

mern-docker/
├── docker-compose.yml
├── frontend/
│   ├── Dockerfile
│   ├── package.json
│   └── src/
├── backend/
│   ├── Dockerfile
│   ├── package.json
│   └── src/
└── .env

docker-compose.yml

version: '3.8'

services:
  frontend:
    build: ./frontend
    ports:
      - "3000:3000"
    volumes:
      - ./frontend:/app
      - /app/node_modules
    environment:
      - REACT_APP_API_URL=http://localhost:4000/api
    depends_on:
      - backend
    stdin_open: true  # Needed for React interactive mode

  backend:
    build: ./backend
    ports:
      - "4000:4000"
    volumes:
      - ./backend:/app
      - /app/node_modules
    environment:
      - MONGODB_URI=mongodb://mongodb:27017/mernapp
      - PORT=4000
    depends_on:
      - mongodb

  mongodb:
    image: mongo:6.0
    ports:
      - "27017:27017"
    volumes:
      - mongodb_data:/data/db
    environment:
      - MONGO_INITDB_DATABASE=mernapp

volumes:
  mongodb_data:

frontend/Dockerfile

FROM node:18-alpine

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 3000

CMD ["npm", "start"]

backend/Dockerfile

FROM node:18-alpine

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 4000

CMD ["npm", "run", "dev"]

Running the Application

# Start all services
docker-compose up

# See running containers
docker-compose ps

# View logs
docker-compose logs -f backend

With this setup, you have a complete development environment for a MERN stack application. The frontend React application communicates with the backend Express API, which in turn connects to MongoDB for data storage.

Environment Variables and Configuration

Docker Compose provides several ways to manage environment variables and configuration:

Using a .env File

Create a .env file in the same directory as your docker-compose.yml file:

# .env file
MONGO_VERSION=6.0
DB_NAME=mernapp
API_PORT=4000
FRONTEND_PORT=3000

Then reference these variables in docker-compose.yml:

version: '3.8'

services:
  frontend:
    build: ./frontend
    ports:
      - "${FRONTEND_PORT}:3000"
    # ...

  backend:
    build: ./backend
    ports:
      - "${API_PORT}:4000"
    # ...

  mongodb:
    image: mongo:${MONGO_VERSION}
    # ...

Using the "env_file" Option

You can also specify an environment file for a specific service:

services:
  backend:
    build: ./backend
    env_file:
      - ./backend/.env.dev

Variable Substitution in Compose Files

Docker Compose allows variable substitution using the ${VARIABLE} syntax:

services:
  backend:
    image: ${REGISTRY}/backend:${TAG}
    environment:
      DEBUG: ${DEBUG:-false}  # Use "false" if DEBUG is not set

Compose File Versions

Docker Compose has evolved over time, with different versions offering different features:

Version Docker Engine Key Features
1 (Legacy) 1.9.0+ Basic functionality
2.x 1.10.0+ Named networks, volume definitions
3.x 1.13.0+ Swarm mode, deploy configuration
3.8 19.03.0+ Config additions, improvements

For most modern development, using version '3.8' is recommended as it offers a good balance of features and compatibility.

Docker Compose Best Practices

Health Checks Example

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

Hands-on Exercises

Exercise 1: Basic Compose Setup

Create a simple Docker Compose configuration for a WordPress site with MySQL:

  1. Create a new directory for your project
  2. Create a docker-compose.yml file
  3. Configure two services: WordPress and MySQL
  4. Set up a volume for MySQL data persistence
  5. Map appropriate ports for WordPress
  6. Configure environment variables for database connection
  7. Run docker-compose up and access WordPress at http://localhost:8080

Exercise 2: Development Environment

Create a development environment for a Node.js application with MongoDB:

  1. Create a simple Express API that connects to MongoDB
  2. Create a Dockerfile for your Node.js application
  3. Create a docker-compose.yml file with two services: api and mongo
  4. Configure volume mounts for real-time code changes
  5. Set up environment variables using a .env file
  6. Add MongoDB data persistence with a named volume

Exercise 3: Compose Command Practice

Using the environment from Exercise 2:

  1. Start the services in detached mode
  2. Check the status of your services
  3. View the logs for your API service
  4. Execute a command in the running MongoDB container
  5. Make a change to your API code and observe it update
  6. Stop the services without removing containers
  7. Restart the services
  8. Bring everything down and remove volumes

Additional Resources

Summary

Docker Compose is a powerful tool that simplifies the development, testing, and deployment of multi-container applications. By defining your services in a single YAML file, you can:

In the next lecture, we'll dive deeper into multi-container applications and explore more advanced Docker Compose features.