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.
Why Use Docker Compose?
Docker Compose solves several critical problems in container development and deployment:
- Single Source of Truth: Keeps all configuration in one place instead of scattered across multiple command lines
- Reproducible Environments: Ensures everyone working on the project has the same setup
- Simplified Local Development: Makes it easy to run complex multi-service applications locally
- Service Isolation with Coordination: Containers remain isolated but can communicate easily
- Version Control: Configuration can be tracked in git alongside application code
- DRY (Don't Repeat Yourself): Avoid duplicating configuration across environments
When to Use Docker Compose
- Development environments: Perfect for local development setup
- Automated testing: Excellent for CI pipelines and integration tests
- Single host deployments: Suitable for small-scale production on a single server
- Demonstrations and tutorials: Ideal for sharing reproducible examples
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:
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
- version: Specifies the Compose file format version
- services: Defines the containers that make up your application
- image: Specifies the Docker image to use
- build: Instead of using a pre-built image, build from a Dockerfile
- ports: Maps host ports to container ports
- volumes: Mounts paths or named volumes
- environment: Sets environment variables
- depends_on: Expresses dependency between services
Essential Docker Compose Commands
Primary Commands
docker-compose up: Creates and starts all services defined in your docker-compose.yml file-dor--detach: Run containers in the background--build: Build or rebuild services before starting
docker-compose down: Stops and removes all containers, networks, and volumes defined in your compose file-vor--volumes: Remove named volumes
docker-compose ps: Lists all running containers for the defined servicesdocker-compose logs: View output from containers-for--follow: Follow log output--tail=10: Show only the last 10 lines of output
docker-compose exec: Run a command in a running container- Example:
docker-compose exec web bash
- Example:
Additional Commands
docker-compose build: Build or rebuild servicesdocker-compose pull: Pull service imagesdocker-compose restart: Restart servicesdocker-compose stop: Stop services without removing themdocker-compose start: Start stopped servicesdocker-compose run: Run a one-time command against a servicedocker-compose config: Validate and view the Compose filedocker-compose top: Display the running processes
Practical Example: MERN Stack Application
Let's set up a complete MERN (MongoDB, Express, React, Node.js) stack application using Docker Compose:
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
- Use version control: Keep your docker-compose.yml in version control alongside application code
- Use .env files: Store environment-specific variables in .env files (don't commit sensitive ones!)
- Organize with profiles: Use profiles for different groups of services
- Named volumes: Use named volumes instead of host mounts for data persistence (easier to manage)
- Descriptive service names: Use clear, descriptive service names that indicate their purpose
- Dependency management: Use depends_on to specify service dependencies correctly
- Consistent port mapping: Try to use the same port inside and outside the container when possible
- Health checks: Add health checks to your services for better reliability
- Stay DRY: Use YAML anchors and extensions to avoid repetition
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:
- Create a new directory for your project
- Create a
docker-compose.ymlfile - Configure two services: WordPress and MySQL
- Set up a volume for MySQL data persistence
- Map appropriate ports for WordPress
- Configure environment variables for database connection
- Run
docker-compose upand access WordPress athttp://localhost:8080
Exercise 2: Development Environment
Create a development environment for a Node.js application with MongoDB:
- Create a simple Express API that connects to MongoDB
- Create a Dockerfile for your Node.js application
- Create a
docker-compose.ymlfile with two services: api and mongo - Configure volume mounts for real-time code changes
- Set up environment variables using a
.envfile - Add MongoDB data persistence with a named volume
Exercise 3: Compose Command Practice
Using the environment from Exercise 2:
- Start the services in detached mode
- Check the status of your services
- View the logs for your API service
- Execute a command in the running MongoDB container
- Make a change to your API code and observe it update
- Stop the services without removing containers
- Restart the services
- 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:
- Create reproducible development environments
- Simplify local testing and CI/CD pipelines
- Manage complex application architectures
- Version and track infrastructure alongside code
In the next lecture, we'll dive deeper into multi-container applications and explore more advanced Docker Compose features.