Introduction
在现代应用开发中,单独部署单个服务的情况越来越少。相反,我们将应用拆分为多个松散耦合的服务,每个服务负责特定的业务功能。例如,一个典型的 Web 应用可能包含前端应用、后端 API、数据库、缓存层和消息队列。这些服务需要协同工作,同时保持独立性和可扩展性。
Docker Compose 是 Docker 官方提供的工具,用于定义和运行多容器 Docker 应用。通过一个 YAML 配置文件,你可以定义应用的所有服务、网络和卷,然后使用单个命令启动整个应用堆栈。这极大地简化了开发、测试和生产环境中的容器编排工作。
本教程将带你从零开始学习 Docker Compose,包括基础概念、实际配置示例、最佳实践以及常见问题的解决方案。完成本教程后,你将能够自信地使用 Docker Compose 管理复杂的多容器应用。
Why Use Docker Compose?
简化多容器管理
在没有 Docker Compose 的情况下,启动多个容器需要分别运行每个容器,并手动管理它们之间的依赖关系和网络配置。这不仅繁琐,而且容易出错。Docker Compose 允许你在一个配置文件中声明所有服务及其关系,然后通过简单的命令统一管理整个应用堆栈。
环境一致性
通过 docker-compose.yml 文件,团队成员可以确保使用完全相同的服务配置。开发环境、测试环境和生产环境可以使用同一份配置文件,避免因环境差异导致的各种问题。这对于持续集成和持续部署(CI/CD)流程尤为重要。
快速启动和销毁
Docker Compose 提供了便捷的命令来启动、停止和重建服务。你可以在几秒钟内启动整个应用堆栈,或者完全清理所有资源。这对于开发迭代和测试场景非常有价值。
易于协作
Docker Compose 配置文件是文本文件,可以轻松纳入版本控制系统(如 Git)。团队成员可以共享配置、审查变更、跟踪历史记录,促进协作和代码复用。
Prerequisites
在开始本教程之前,请确保你的系统满足以下要求:
- Docker Engine:确保 Docker 已正确安装并运行。你可以运行
docker --version和docker-compose --version来验证安装。 - 基础命令行知识:熟悉终端或命令提示符的基本操作。
- 文本编辑器:用于创建和编辑配置文件,如 VS Code、vim 或 nano。
- 理解 Docker 基础:了解 Docker 镜像、容器和基本命令。
Step 1: Installing Docker Compose
Docker Compose 通常随 Docker Desktop 一起安装。如果你使用的是 Linux 系统或单独的 Docker 安装,可能需要单独安装 Docker Compose。
Verify Installation
运行以下命令检查 Docker Compose 是否已安装:
# 检查 Docker Compose 版本
docker-compose --version
# 或者使用 v2 命令
docker compose version
如果看到版本信息(如 Docker Compose version v2.24.0),说明已安装。如果未安装,请继续阅读安装说明。
Linux Installation
对于 Ubuntu 或 Debian 系统:
# 下载 Docker Compose 二进制文件
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# 添加执行权限
sudo chmod +x /usr/local/bin/docker-compose
# 验证安装
docker-compose --version
对于 CentOS 或 RHEL 系统:
# 安装依赖包
sudo yum install -y curl
# 下载并安装
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
macOS and Windows
在 macOS 和 Windows 上,Docker Compose 已包含在 Docker Desktop 中。确保 Docker Desktop 是最新版本即可。
Step 2: Creating Your First docker-compose.yml
让我们创建一个简单的多容器应用示例。我们将部署一个包含 Web 服务器(nginx)和一个简单的 API 服务(Node.js)的应用。
Basic Structure
创建一个新目录并进入:
mkdir my-docker-app && cd my-docker-app
创建 docker-compose.yml 文件:
version: '3.8'
services:
web:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html
networks:
- app-network
api:
image: node:20-alpine
working_dir: /app
command: node -e "require('http').createServer((req, res) => { res.end('API Response'); }).listen(3000)"
networks:
- app-network
expose:
- "3000"
networks:
app-network:
driver: bridge
这个配置文件定义了两个服务:web 和 api。让我们逐行解析:
version: '3.8'指定 Docker Compose 文件格式版本。services部分定义所有容器服务。web服务使用 nginx:alpine 镜像,将宿主机的 8080 端口映射到容器的 80 端口。api服务使用 Node.js 镜像,创建一个简单的 HTTP 服务器。networks创建自定义网络,使容器能够相互通信。
Launch the Application
使用以下命令启动应用:
# 启动所有服务(在后台运行)
docker-compose up -d
# 查看运行状态
docker-compose ps
# 查看日志
docker-compose logs -f
访问 http://localhost:8080 应该能看到 nginx 默认页面。同时,api 服务在内部网络中的 3000 端口运行。
Step 3: Managing Services
掌握服务的生命周期管理是使用 Docker Compose 的关键技能。
Starting and Stopping
# 启动服务(后台模式)
docker-compose up -d
# 启动服务并重新构建镜像
docker-compose up -d --build
# 停止所有服务
docker-compose down
# 停止并删除卷(数据将被删除)
docker-compose down -v
Viewing Status and Logs
# 查看运行中的服务状态
docker-compose ps
# 查看所有服务的日志
docker-compose logs
# 实时查看日志
docker-compose logs -f
# 查看特定服务的日志
docker-compose logs web
# 查看最近 100 行日志
docker-compose logs --tail 100
Restarting and Recreating
# 重启单个服务
docker-compose restart api
# 重建并重启所有服务
docker-compose up -d --force-recreate
# 删除并重新创建所有服务
docker-compose down && docker-compose up -d
Step 4: Environment Variables and Configuration
环境变量是配置应用程序的灵活方式,Docker Compose 支持多种方式设置环境变量。
Using Environment Files
创建 .env 文件来存储敏感配置:
# .env 文件
DATABASE_PASSWORD=my_secret_password
API_KEY=your_api_key_here
NODE_ENV=production
在 docker-compose.yml 中引用这些变量:
version: '3.8'
services:
database:
image: postgres:15-alpine
environment:
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
POSTGRES_USER: app_user
POSTGRES_DB: myapp
volumes:
- postgres_data:/var/lib/postgresql/data
backend:
image: myapp/backend:latest
environment:
- DATABASE_URL=postgresql://app_user:${DATABASE_PASSWORD}@database:5432/myapp
- NODE_ENV=${NODE_ENV}
depends_on:
- database
volumes:
postgres_data:
Environment-Specific Configurations
使用多个 Compose 文件来管理不同环境的配置:
# docker-compose.yml(基础配置)
version: '3.8'
services:
app:
image: myapp:latest
ports:
- "8080:8080"
# docker-compose.override.yml(开发环境覆盖)
version: '3.8'
services:
app:
build: .
volumes:
- ./src:/app/src
environment:
- DEBUG=true
# docker-compose.prod.yml(生产环境)
version: '3.8'
services:
app:
image: myapp:latest
deploy:
replicas: 3
resources:
limits:
cpus: '0.5'
memory: 512M
启动不同环境:
# 开发环境
docker-compose up -d
# 生产环境
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Step 5: Multi-Container Setup with Database
让我们创建一个更完整的示例,包含 Web 应用、API 服务和 PostgreSQL 数据库。
Complete Example
创建以下目录结构:
myapp/
├── docker-compose.yml
├── backend/
│ ├── Dockerfile
│ └── index.js
└── frontend/
└── Dockerfile
docker-compose.yml:
version: '3.8'
services:
postgres:
image: postgres:15-alpine
container_name: postgres_db
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: securepassword
POSTGRES_DB: myapp
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- app-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d myapp"]
interval: 10s
timeout: 5s
retries: 5
backend:
build: ./backend
container_name: backend_api
environment:
DATABASE_URL: postgresql://appuser:securepassword@postgres:5432/myapp
NODE_ENV: development
PORT: 3000
depends_on:
postgres:
condition: service_healthy
networks:
- app-network
expose:
- "3000"
restart: unless-stopped
frontend:
build: ./frontend
container_name: frontend_app
ports:
- "3000:3000"
depends_on:
- backend
networks:
- app-network
networks:
app-network:
driver: bridge
volumes:
postgres_data:
backend/index.js:
const express = require('express');
const { Pool } = require('pg');
const app = express();
const pool = new Pool({
connectionString: process.env.DATABASE_URL
});
app.get('/', async (req, res) => {
try {
const result = await pool.query('SELECT NOW()');
res.json({
message: 'Welcome to the API',
databaseTime: result.rows[0].now
});
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Database query failed' });
}
});
app.get('/health', (req, res) => {
res.json({ status: 'healthy' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, '0.0.0.0', () => {
console.log(`Server running on port ${PORT}`);
});
Health Checks
健康检查确保服务在完全就绪后才接收流量:
services:
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
Step 6: Networking Between Services
Docker Compose 自动为所有服务创建一个默认网络,允许它们通过服务名称相互通信。
Custom Networks
version: '3.8'
services:
frontend:
image: nginx:alpine
networks:
- frontend-network
- backend-network
backend:
image: node:20-alpine
networks:
- backend-network
networks:
frontend-network:
driver: bridge
backend-network:
driver: bridge
External Networks
使用已存在的外部网络:
networks:
default:
external:
name: my-existing-network
Step 7: Data Persistence with Volumes
数据卷(Volumes)用于持久化容器生成的数据,即使容器被删除,数据仍然保留。
Named Volumes
version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: myapp
volumes:
- mysql_data:/var/lib/mysql
restart: unless-stopped
volumes:
mysql_data:
Bind Mounts
绑定挂载允许你将宿主机的目录直接挂载到容器中:
services:
node:
image: node:20-alpine
working_dir: /app
volumes:
- ./src:/app:ro # 只读挂载
- ./config:/app/config
command: npm run dev
tmpfs Mounts
tmpfs 挂载将数据存储在内存中,容器重启后数据消失:
services:
cache:
image: redis:7-alpine
tmpfs:
- /data:size=100M
Best Practices
1. Use Specific Image Tags
避免使用 latest 标签,这会导致不可预测的行为。始终指定具体版本:
# 不推荐
image: nginx
# 推荐
image: nginx:1.25-alpine
2. Implement Health Checks
为所有长时间运行的服务添加健康检查:
services:
api:
image: myapi:latest
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
3. Use .dockerignore
创建 .dockerignore 文件排除不必要的文件:
node_modules
npm-debug.log
.git
.env
*.md
4. Limit Container Resources
为生产环境设置资源限制:
services:
app:
image: myapp:latest
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
5. Separate Development and Production
使用多阶段构建和不同的 Compose 文件:
# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM node:20-alpine AS production
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["node", "index.js"]
6. Never Store Secrets in Images
使用 Docker Secrets 或环境变量管理敏感信息:
services:
app:
image: myapp:latest
secrets:
- db_password
environment:
- DATABASE_PASSWORD_FILE=/run/secrets/db_password
secrets:
db_password:
file: ./secrets/db_password.txt
Troubleshooting Common Issues
Issue 1: Ports Already in Use
如果启动服务时端口被占用,你会看到类似 “port is already allocated” 的错误。
解决方案:
# 查看占用端口的进程
lsof -i :8080
# 或使用 docker-compose 修改端口映射
# 编辑 docker-compose.yml 中的 ports 部分
修改 docker-compose.yml:
services:
web:
image: nginx:alpine
ports:
- "8081:80" # 改为其他端口
Issue 2: Container Exits Immediately
容器启动后立即退出,通常是因为主进程结束。
解决方案:
# 查看容器日志了解退出原因
docker-compose logs service_name
# 检查命令是否正确
# 确保使用持久化命令
command: tail -f /dev/null # 开发调试用
Issue 3: Database Connection Refused
应用无法连接到数据库,通常是因为依赖关系或网络问题。
解决方案:
# 检查服务是否健康
docker-compose ps
# 查看数据库日志
docker-compose logs postgres
# 验证网络连接
docker-compose exec backend ping postgres
# 确保使用正确的连接字符串
# 使用服务名而非 localhost
DATABASE_URL=postgresql://user:pass@postgres:5432/db
Issue 4: Volume Permission Denied
挂载卷时遇到权限问题。
解决方案:
# 修改目录权限
sudo chown -R $USER:$USER ./data
# 或在容器内使用 root 用户
user: root
# 然后在容器内修改权限
docker-compose exec app chown -R appuser /data
Issue 5: Images Not Updating
修改代码后容器没有反映最新更改。
解决方案:
# 强制重新构建并启动
docker-compose up -d --build
# 或清除 Docker 构建缓存
docker-compose build --no-cache
# 完全清理并重新创建
docker-compose down && docker-compose up -d --build
Conclusion
Docker Compose 是管理多容器 Docker 应用的强大工具,它通过声明式配置简化了容器编排过程。在本教程中,我们涵盖了 Docker Compose 的核心概念、安装方法、配置文件编写、服务管理、环境变量配置、网络设置和数据持久化等关键主题。
通过实践这些示例和最佳实践,你应该能够自信地使用 Docker Compose 部署和管理复杂的应用堆栈。记住以下几点:
- 始终使用具体的镜像标签而非
latest - 为关键服务添加健康检查
- 合理规划网络和卷结构
- 使用环境变量管理配置
- 区分开发环境和生产环境的配置
Related Tutorials: