Docker Compose Services
Introduction
When building modern applications, you'll often need to run multiple containers that work together. For example, you might have a container for your web application, another for your database, and perhaps a third for a cache like Redis. Docker Compose makes managing these multi-container applications straightforward, and services are at the heart of this functionality.
In this guide, we'll explore how to define services in Docker Compose, understand the available configuration options, and see real-world examples of how services are used in development environments.
What Are Docker Compose Services?
In Docker Compose, a service is a container in production. Services define how containers should behave in your application environment, including:
- What Docker image to use
- Environment variables
- Network connections
- Volume mounts
- Resource constraints
- Restart policies
- And much more
Let's look at the basic structure of defining a service in a docker-compose.yml
file:
version: '3.8'
services:
# Service name
web:
# Service configuration goes here
image: nginx:latest
ports:
- "80:80"
In this simple example, we've defined a service named web
that uses the nginx:latest
image and maps port 80 from the container to port 80 on the host.
Creating Your First Docker Compose Services File
Let's create a complete example of a docker-compose.yml
file with multiple services:
version: '3.8'
services:
# Web application service
web:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./website:/usr/share/nginx/html
depends_on:
- api
# API service
api:
build: ./api
ports:
- "3000:3000"
environment:
- DB_HOST=database
- DB_USER=myuser
- DB_PASSWORD=mypassword
- DB_NAME=mydb
depends_on:
- database
# Database service
database:
image: postgres:13
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=myuser
- POSTGRES_PASSWORD=mypassword
- POSTGRES_DB=mydb
volumes:
postgres_data:
In this file, we've defined three services:
web
: An Nginx web server for serving static contentapi
: A custom API service built from a local Dockerfiledatabase
: A PostgreSQL database for data storage
Key Service Configuration Options
Let's explore the most important configuration options for services:
Image or Build
Every service needs a container image. You can specify an existing image or build a custom one:
services:
# Using an existing image
web:
image: nginx:latest
# Building a custom image
api:
build: ./api
# Building with more options
app:
build:
context: ./app
dockerfile: Dockerfile.dev
args:
NODE_ENV: development
Ports
To expose ports from a container to the host:
services:
web:
image: nginx:latest
ports:
- "80:80" # HOST:CONTAINER format
- "443:443"
- "8080" # Will assign a random host port to container port 8080
Environment Variables
To set environment variables in a container:
services:
api:
image: my-api:latest
environment:
- NODE_ENV=production
- API_KEY=secret_value
- DEBUG=false
# Alternative syntax
# environment:
# NODE_ENV: production
# API_KEY: secret_value
# DEBUG: "false"
For better security, you can use a .env
file:
services:
api:
image: my-api:latest
env_file:
- ./config.env
Volumes
To mount files or directories from the host to the container:
services:
web:
image: nginx:latest
volumes:
- ./website:/usr/share/nginx/html # Host path:Container path
- ./nginx.conf:/etc/nginx/nginx.conf:ro # Read-only volume
- logs:/var/log/nginx # Named volume
Networks
To control how containers connect to each other:
services:
web:
image: nginx:latest
networks:
- frontend
api:
image: my-api:latest
networks:
- frontend
- backend
database:
image: postgres:latest
networks:
- backend
networks:
frontend:
backend:
Depends On
To control the startup order of services:
services:
web:
image: nginx:latest
depends_on:
- api
api:
image: my-api:latest
depends_on:
- database
database:
image: postgres:latest
Note: depends_on
only waits for the container to start, not for the service inside the container to be ready.
Service Resource Configuration
You can also configure resource constraints for your services:
services:
api:
image: my-api:latest
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
Restart Policies
To define how containers should restart after a failure:
services:
web:
image: nginx:latest
restart: always # Options: "no", "on-failure", "always", "unless-stopped"
Practical Example: Full-Stack Web Application
Let's build a complete example of a typical full-stack web application with React frontend, Node.js backend, and MongoDB database:
version: '3.8'
services:
# Frontend service
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3000:3000"
volumes:
- ./frontend:/app
- /app/node_modules
environment:
- REACT_APP_API_URL=http://localhost:4000
depends_on:
- backend
# Backend service
backend:
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "4000:4000"
volumes:
- ./backend:/app
- /app/node_modules
environment:
- PORT=4000
- MONGO_URI=mongodb://db:27017/myapp
- NODE_ENV=development
depends_on:
- db
# Database service
db:
image: mongo:4.4
volumes:
- mongo_data:/data/db
ports:
- "27017:27017"
volumes:
mongo_data:
Common Service Patterns
Let's look at some common service patterns you might encounter in real-world scenarios:
1. Service with Healthcheck
services:
api:
image: my-api:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
2. Service with Custom Command and Entrypoint
services:
worker:
image: my-worker:latest
entrypoint: /bin/sh
command: -c "echo 'Starting worker' && python worker.py"
3. Service with Logging Configuration
services:
web:
image: nginx:latest
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
Working with Multiple Compose Files
For complex projects, you might want to split your configuration across multiple files:
# Base configuration
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Base file (docker-compose.yml
):
version: '3.8'
services:
web:
image: myapp:latest
build: .
Production overrides (docker-compose.prod.yml
):
version: '3.8'
services:
web:
environment:
- NODE_ENV=production
restart: always
Service Visualization
Understanding the relationships between services can be helpful. Here's a visualization of a multi-service application:
Summary
Docker Compose services provide a powerful way to define multi-container applications. In this guide, we've covered:
- Basic service definition syntax
- Key configuration options like image, ports, volumes, and environment variables
- Resource constraints and restart policies
- Real-world examples of full-stack applications
- Common service patterns and configurations
With these concepts, you can now build sophisticated multi-container applications that are easy to deploy and manage across different environments.
Additional Resources and Exercises
Exercises
-
Basic Service Configuration:
- Create a Docker Compose file with a simple web service using the
nginx
image. - Map port 8080 on your host to port 80 in the container.
- Mount a local directory containing an
index.html
file to/usr/share/nginx/html
.
- Create a Docker Compose file with a simple web service using the
-
Multi-Service Application:
- Build a Docker Compose file with three services: a web frontend, an API, and a database.
- Configure environment variables to connect the API to the database.
- Use depends_on to ensure proper startup order.
-
Service Scaling:
- Create a Docker Compose file with a web service that can be scaled.
- Practice scaling the service using the command:
docker-compose up --scale web=3
.
Additional Resources
Happy containerizing!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)