If you're a developer who's been using Docker containers but finding yourself typing long docker run
commands or managing multiple containers manually, it's time to level up your workflow with Docker Compose. This guide will walk you through the basics of Docker Compose and show you how to simplify your development environment setup.
What is Docker Compose?
Docker Compose is a tool that helps you define and share multi-container 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.
Why Should You Use Docker Compose?
- Simplified Configuration: Define your entire application stack in a single file
- Reproducible Environments: Share your application setup with other developers
- Easy Testing: Create isolated testing environments quickly
- Version Control: Track changes to your application environment alongside your code
Getting Started
First, ensure you have both Docker and Docker Compose installed. Docker Desktop for Windows and Mac comes with Compose, but Linux users might need to install it separately.
The compose.yml File
The heart of Docker Compose is the compose.yml
file (or docker-compose.yml
for backward compatibility). Here's a simple example:
services:
web:
build: .
ports:
- "3000:3000"
volumes:
- .:/app
environment:
- NODE_ENV=development
db:
image: postgres:13
environment:
- POSTGRES_USER=myuser
- POSTGRES_PASSWORD=mypassword
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Let's break down each configuration element:
-
services
: The top-level key that defines all your application's containers/services- Each indented key under
services
(likeweb
anddb
) is a service name you choose - Service names are used for network aliases and container prefixes
- Each indented key under
-
build
: Specifies the build configuration for creating a container image-
.
means build from Dockerfile in the current directory - Can also specify a path to a directory containing a Dockerfile
- Can be expanded to include build context and arguments:
build: context: ./dir dockerfile: Dockerfile.dev args: ENV: development
-
-
ports
: Maps container ports to host ports- Format is
"HOST_PORT:CONTAINER_PORT"
- Example:
"3000:3000"
maps container port 3000 to host port 3000 - Can specify multiple port mappings
- Can use port ranges:
"3000-3005:3000-3005"
- Format is
-
volumes
: Defines mounted volumes for data persistence and file sharing- Bind mounts:
./host/path:/container/path
maps a host directory to container - Named volumes:
volume_name:/container/path
uses a Docker-managed volume - Anonymous volumes:
/container/path
creates an unnamed Docker-managed volume - Can specify read-only volumes:
./host/path:/container/path:ro
- Bind mounts:
-
environment
: Sets environment variables inside the container- Can be a list:
- KEY=value
- Or a map:
environment: NODE_ENV: development API_KEY: secret
- Can be a list:
-
image
: Specifies a pre-built Docker image to use- Can be from Docker Hub:
postgres:13
- Or private registry:
registry.example.com/image:tag
- Used when you don't need to build an image locally
- Can be from Docker Hub:
-
Named volumes declaration:
- The top-level
volumes
key declares named volumes used in services - These volumes persist even when containers are removed
- Can specify volume drivers and options:
volumes: postgres_data: driver: local driver_opts: type: none device: /path/on/host o: bind
- The top-level
Common additional configurations you might need:
services:
web:
# Restart policy
restart: always # or 'no', 'on-failure', 'unless-stopped'
# Dependencies
depends_on:
- db
- redis
# Custom container name
container_name: myapp_web
# Additional hosts entries
extra_hosts:
- "host.docker.internal:host-gateway"
# Resource limits
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
# Health check
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000"]
interval: 30s
timeout: 10s
retries: 3
These configurations allow you to define complex, multi-container applications in a declarative way. The compose file serves as both documentation and executable configuration, making it easier to share and maintain your application's infrastructure requirements.
Essential Commands
Here are the Docker Compose commands you'll use frequently:
# Start your services
docker compose up
# Run in detached mode
docker compose up -d
# Stop services (keeps containers)
docker compose stop
# Stop and remove containers, networks
docker compose down
# Stop and remove containers, networks, volumes
docker compose down -v
# View logs
docker compose logs
# View logs for specific service
docker compose logs service_name
# Follow logs
docker compose logs -f
# Rebuild services
docker compose build
# Execute command in a running container
docker compose exec service_name command
# Example: docker compose exec web npm test
# Run a one-off command
docker compose run --rm service_name command
# Example: docker compose run --rm web npm install
# List running containers
docker compose ps
# Check service status
docker compose top
# Pull latest images
docker compose pull
Real-World Example: MERN Stack
Let's look at a practical example using a MERN (MongoDB, Express, React, Node.js) stack:
services:
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3000:3000"
volumes:
- ./frontend:/app
- /app/node_modules
environment:
- REACT_APP_API_URL=http://localhost:5000
depends_on:
- backend
backend:
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "5000:5000"
volumes:
- ./backend:/app
- /app/node_modules
environment:
- MONGODB_URI=mongodb://mongodb:27017/myapp
depends_on:
- mongodb
mongodb:
image: mongo:latest
ports:
- "27017:27017"
volumes:
- mongodb_data:/data/db
volumes:
mongodb_data:
Best Practices
- Use Version Control: Always include your compose file in version control
-
Environment Variables: Use
.env
files for sensitive data:
services:
web:
env_file:
- .env
- Named Volumes: Use named volumes instead of bind mounts for databases:
volumes:
- mongodb_data:/data/db # Good
- ./data:/data/db # Not recommended for databases
-
Dependencies: Use
depends_on
to manage service startup order:
services:
web:
depends_on:
- db
- redis
Common Issues and Solutions
1. Port Conflicts
If you see "port is already allocated" errors:
- Check for running containers using the same ports
- Use
docker compose ps
to list running containers - Change the port mapping in your compose file
2. Volume Permissions
If you encounter permission issues:
- Ensure proper ownership of mounted volumes
- Consider using
user
directive in your service definition:
services:
web:
user: "1000:1000"
Conclusion
Docker Compose is an invaluable tool for modern development workflows. It simplifies container management, makes development environments reproducible, and helps teams work more efficiently. Start with simple configurations and gradually add more features as your needs grow.
Remember: the goal is to make development easier, not more complicated. Don't over-engineer your Compose files – keep them simple and focused on your immediate needs.
Top comments (0)