主题
第四阶段:Docker Compose
4.1 Compose 基础
4.1.1 什么是 Docker Compose?
Docker Compose 是一个用于定义和运行多容器 Docker 应用的工具。通过一个 YAML 文件(docker-compose.yml)描述多个服务的配置,然后用一条命令即可创建并启动所有服务。
核心思想:"Define and run multi-container applications" — 用声明式配置取代手动命令。
没有 Compose 的世界: 有 Compose 的世界:
# 启动 MySQL docker compose up -d
docker run -d \ # 一条命令搞定一切!
--name mysql \
--network app-net \
-e MYSQL_ROOT_PASSWORD=123456 \ docker-compose.yml:
-v mysql-data:/var/lib/mysql \ ┌─────────────────────────┐
mysql:8.0 │ services: │
│ web: │
# 启动 Redis │ image: nginx │
docker run -d \ │ ports: ["80:80"] │
--name redis \ │ mysql: │
--network app-net \ │ image: mysql:8.0 │
redis:7-alpine │ environment: ... │
│ redis: │
# 启动 Web 应用 │ image: redis:7 │
docker run -d \ └─────────────────────────┘
--name web \
--network app-net \
-p 80:80 \
-e DB_HOST=mysql \
my-web-app为什么需要 Docker Compose?
| 痛点 | Compose 的解决方案 |
|---|---|
多个 docker run 命令难记难管理 | 所有配置写在一个 YAML 文件中 |
| 容器间网络配置繁琐 | 自动创建网络,服务名即主机名 |
| 启动顺序难控制 | depends_on + healthcheck |
| 环境变量散落在各处 | 统一管理,支持 .env 文件 |
| 开发/测试/生产环境不一致 | 配置文件覆盖、profiles |
4.1.2 Docker Compose 安装与版本
Docker Compose 有两个版本:
| 对比 | docker-compose(V1) | docker compose(V2) |
|---|---|---|
| 安装方式 | 独立 Python 包 | Docker CLI 插件 |
| 命令格式 | docker-compose up | docker compose up(无横杠) |
| 性能 | Python 实现,较慢 | Go 实现,更快 |
| 维护状态 | 已停止维护 | 当前推荐 |
bash
# ===== 检查是否已安装(Docker Engine 24.0+ 已内置 Compose V2)=====
docker compose version
# Docker Compose version v2.24.0
# ===== 如果未安装 Compose V2 =====
# 方法一:通过 Docker Engine 安装(推荐,安装 Docker 时已自动安装)
sudo apt-get install docker-compose-plugin
# 方法二:手动安装插件
COMPOSE_VERSION=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep '"tag_name"' | cut -d'"' -f4)
sudo curl -L "https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" \
-o /usr/local/lib/docker/cli-plugins/docker-compose
sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
# ===== 验证 =====
docker compose version⚠️ 注意:本笔记全部使用 V2 命令格式
docker compose(无横杠)。如果你使用的是旧版,将docker compose替换为docker-compose即可。
4.1.3 docker-compose.yml 文件结构与语法
docker-compose.yml 是一个 YAML 格式的配置文件,包含三个顶级元素:
yaml
# docker-compose.yml 整体结构
# 版本声明(Compose V2 中已可省略,但写上更清晰)
# version: "3.8" # Compose V2 不再强制要求
# ===== 1. 服务定义(核心)=====
services:
web: # 服务名(也是容器在网络中的主机名)
image: nginx:1.25-alpine
ports:
- "80:80"
# ... 更多配置
api:
build: ./api
ports:
- "3000:3000"
depends_on:
- db
# ... 更多配置
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secret
volumes:
- db-data:/var/lib/mysql
# ... 更多配置
# ===== 2. 网络定义 =====
networks:
frontend:
driver: bridge
backend:
driver: bridge
# ===== 3. 数据卷定义 =====
volumes:
db-data:
driver: local三大顶级元素关系图:
┌─────────────────────────────────────────────────────────┐
│ docker-compose.yml │
│ │
│ ┌─────────────── services ─────────────────┐ │
│ │ │ │
│ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │ web │◄──►│ api │◄──►│ db │ │ │
│ │ └──┬──┘ └──┬──┘ └──┬──┘ │ │
│ │ │ │ │ │ │
│ └─────┼──────────┼──────────┼───────────────┘ │
│ │ │ │ │
│ ┌─────▼──────────▼──────────▼───────────────┐ │
│ │ networks │ │
│ │ ┌──────────┐ ┌───────────┐ │ │
│ │ │ frontend │ │ backend │ │ │
│ │ └──────────┘ └───────────┘ │ │
│ └───────────────────────────────────────────┘ │
│ │
│ ┌───────────── volumes ─────────────────────┐ │
│ │ ┌───────────┐ ┌───────────┐ │ │
│ │ │ db-data │ │ uploads │ │ │
│ │ └───────────┘ └───────────┘ │ │
│ └───────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘4.1.4 services 配置详解
services 是 Compose 的核心,定义了要运行的每一个容器。以下是所有常用配置项:
yaml
services:
my-service:
# ========== 镜像来源(二选一)==========
image: nginx:1.25-alpine # 直接使用现有镜像
build: # 从 Dockerfile 构建
context: ./app # 构建上下文路径
dockerfile: Dockerfile.prod # 指定 Dockerfile(默认 Dockerfile)
args: # 构建参数(对应 Dockerfile 的 ARG)
APP_VERSION: "1.0"
target: production # 多阶段构建的目标阶段
# ========== 端口映射 ==========
ports:
- "8080:80" # 宿主机:容器(短格式)
- "443:443"
- "127.0.0.1:3000:3000" # 绑定特定 IP
- "9090-9091:8080-8081" # 端口范围
# 长格式(更明确)
ports:
- target: 80 # 容器端口
published: 8080 # 宿主机端口
protocol: tcp # 协议
mode: host # host 模式
# ========== 环境变量 ==========
environment: # 直接定义
NODE_ENV: production
DB_HOST: mysql
DB_PORT: 3306
# 或数组格式
environment:
- NODE_ENV=production
- DB_HOST=mysql
env_file: # 从文件加载
- .env
- ./config/app.env
# ========== 数据卷挂载 ==========
volumes:
- db-data:/var/lib/mysql # 命名卷(Named Volume)
- ./src:/app/src # 绑定挂载(Bind Mount)
- ./nginx.conf:/etc/nginx/nginx.conf:ro # 只读挂载
- /tmp/cache:/tmp/cache # 绝对路径绑定
# 长格式
volumes:
- type: volume
source: db-data
target: /var/lib/mysql
- type: bind
source: ./src
target: /app/src
read_only: true
# ========== 网络 ==========
networks:
- frontend
- backend
networks:
backend:
aliases: # 网络别名
- database
- db-primary
# ========== 依赖关系 ==========
depends_on:
- db
- redis
# 带条件的依赖(推荐)
depends_on:
db:
condition: service_healthy # 等待 db 健康检查通过
redis:
condition: service_started # 等待 redis 启动
# ========== 资源限制 ==========
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
cpus: "0.25"
memory: 128M
# ========== 重启策略 ==========
restart: unless-stopped
# no | always | on-failure | unless-stopped
# ========== 健康检查 ==========
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
# ========== 容器名称 ==========
container_name: my-custom-name # 指定容器名(不建议在多实例场景使用)
# ========== 工作目录 & 命令 ==========
working_dir: /app
command: ["python", "app.py"] # 覆盖 CMD
entrypoint: ["./entrypoint.sh"] # 覆盖 ENTRYPOINT
# ========== 其他 ==========
stdin_open: true # 等同于 docker run -i
tty: true # 等同于 docker run -t
hostname: my-host # 设置主机名
extra_hosts: # 添加 hosts 记录
- "host.docker.internal:host-gateway"
logging: # 日志配置
driver: json-file
options:
max-size: "10m"
max-file: "3"4.1.5 networks 配置详解
yaml
# Compose 默认行为:自动创建一个名为 <项目名>_default 的 bridge 网络
# 所有服务都连接到这个默认网络,可通过服务名互相访问
# ===== 自定义网络 =====
networks:
# 简单定义
frontend:
backend:
# 详细配置
app-network:
driver: bridge # 网络驱动(默认 bridge)
driver_opts: # 驱动选项
com.docker.network.bridge.name: app-br0
ipam: # IP 地址管理
driver: default
config:
- subnet: 172.28.0.0/16 # 自定义子网
gateway: 172.28.0.1
# 使用已有的外部网络
existing-network:
external: true # 引用已存在的网络
name: my-existing-network # 外部网络的名称
# ===== 服务中使用网络 =====
services:
web:
image: nginx
networks:
- frontend # 只连接前端网络
api:
image: my-api
networks:
- frontend # 同时连接前后端网络
- backend
db:
image: mysql
networks:
- backend # 只连接后端网络
# web 服务无法直接访问 db(不在同一网络)网络隔离示意:
frontend 网络 backend 网络
┌────────────────────┐ ┌────────────────────┐
│ │ │ │
│ ┌─────┐ ┌─────┐ │ │ ┌─────┐ ┌─────┐ │
│ │ web │ │ api │──┼────┼─│ api │ │ db │ │
│ └─────┘ └─────┘ │ │ └─────┘ └─────┘ │
│ │ │ ┌───────┐ │
│ web 可访问 api │ │ │ redis │ │
│ web 不可访问 db │ │ └───────┘ │
│ │ │ │
└────────────────────┘ └────────────────────┘4.1.6 volumes 配置详解
yaml
# ===== 顶级 volumes 定义 =====
volumes:
# 简单定义(Docker 自动管理)
db-data:
redis-data:
# 详细配置
app-uploads:
driver: local
driver_opts:
type: none
o: bind
device: /data/uploads # 映射到宿主机指定路径
# 使用已有的外部卷
existing-volume:
external: true
name: my-existing-volume
# ===== 服务中使用卷 =====
services:
db:
image: mysql:8.0
volumes:
- db-data:/var/lib/mysql # 命名卷:数据持久化
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro # 绑定挂载:初始化脚本4.1.7 常用命令
bash
# ==================== 启动与停止 ====================
# 启动所有服务(后台模式)
docker compose up -d
# 启动并强制重建镜像
docker compose up -d --build
# 只启动指定服务
docker compose up -d web api
# 停止并移除容器、网络
docker compose down
# 停止并移除容器、网络、卷、镜像
docker compose down -v --rmi all
# 仅停止服务(不移除)
docker compose stop
docker compose stop web # 只停止指定服务
# 启动已停止的服务
docker compose start
# 重启服务
docker compose restart
docker compose restart api
# ==================== 查看状态 ====================
# 查看服务状态
docker compose ps
docker compose ps -a # 包含已停止的
# 查看服务日志
docker compose logs # 所有服务日志
docker compose logs -f # 实时跟踪
docker compose logs -f web api # 指定服务
docker compose logs --tail 50 web # 最近 50 行
# 查看服务资源使用
docker compose top # 查看进程
# ==================== 执行命令 ====================
# 在运行中的服务内执行命令
docker compose exec web bash # 进入容器
docker compose exec db mysql -u root -p # 连接数据库
docker compose exec api npm test # 运行测试
# 一次性运行命令(启动新容器执行后删除)
docker compose run --rm api npm install
docker compose run --rm web sh -c "echo hello"
# ==================== 构建相关 ====================
# 构建/重建镜像
docker compose build
docker compose build --no-cache # 不使用缓存
docker compose build api # 只构建指定服务
# 拉取镜像
docker compose pull
# ==================== 配置与调试 ====================
# 验证并查看最终合并后的配置
docker compose config
# 查看服务的镜像
docker compose images
# 查看服务端口映射
docker compose port web 804.2 Compose 进阶
4.2.1 环境变量与 .env 文件
Docker Compose 支持多种方式管理环境变量,按优先级从高到低:
优先级排序(高 → 低):
1. 命令行中的环境变量 docker compose run -e VAR=value
2. Shell 环境中的变量 export VAR=value && docker compose up
3. docker-compose.yml 的 environment 字段
4. env_file 指定的文件
5. Dockerfile 中的 ENV 指令
6. 项目根目录的 .env 文件(仅用于 Compose 文件变量替换).env 文件(Compose 变量替换)
bash
# .env 文件 — 位于 docker-compose.yml 同级目录
# 主要用于 Compose 文件自身的变量替换(${ } 语法)
# 项目配置
COMPOSE_PROJECT_NAME=myapp
COMPOSE_FILE=docker-compose.yml
# 版本管理
MYSQL_VERSION=8.0
REDIS_VERSION=7-alpine
NGINX_VERSION=1.25-alpine
# 端口
WEB_PORT=8080
API_PORT=3000
DB_PORT=3306
# 密码(注意:不要提交到 Git)
MYSQL_ROOT_PASSWORD=my-secret-password
MYSQL_DATABASE=myappyaml
# docker-compose.yml 中引用 .env 变量
services:
web:
image: nginx:${NGINX_VERSION} # 使用 .env 中的变量
ports:
- "${WEB_PORT}:80"
db:
image: mysql:${MYSQL_VERSION}
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
ports:
- "${DB_PORT}:3306"env_file(传递给容器的环境变量)
bash
# config/app.env — 传递给容器内部的环境变量
NODE_ENV=production
API_BASE_URL=https://api.example.com
LOG_LEVEL=info
SECRET_KEY=my-app-secret-keyyaml
services:
api:
build: ./api
env_file:
- ./config/app.env # 容器内可访问这些变量
- ./config/db.env
environment:
OVERRIDE_VAR: value # 同名变量会覆盖 env_file 中的变量替换语法
yaml
services:
web:
image: nginx:${NGINX_VERSION:-1.25} # 默认值:如果未定义则用 1.25
ports:
- "${WEB_PORT:?WEB_PORT must be set}:80" # 必填:未定义则报错
environment:
DEBUG: ${DEBUG:-false} # 默认 false
HOST: ${HOST:-0.0.0.0}4.2.2 服务依赖与启动顺序
基础 depends_on
yaml
services:
web:
build: ./web
depends_on:
- api # web 在 api 之后启动
api:
build: ./api
depends_on:
- db # api 在 db 之后启动
- redis
db:
image: mysql:8.0
redis:
image: redis:7-alpine
# 启动顺序:db + redis(并行)→ api → web
# 停止顺序:反过来 web → api → db + redis⚠️ 重要限制:
depends_on只保证启动顺序,不保证服务就绪。MySQL 容器启动了不代表 MySQL 服务可以接受连接!
depends_on + healthcheck(推荐方案)
yaml
services:
web:
build: ./web
depends_on:
api:
condition: service_healthy # 等 api 健康检查通过再启动
api:
build: ./api
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
depends_on:
db:
condition: service_healthy # 等 db 健康检查通过
redis:
condition: service_started # redis 启动即可
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secret
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s # MySQL 初始化需要时间
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5带 healthcheck 的启动流程:
时间 ──────────────────────────────────────────────────▶
db: [启动]──────[初始化中...]──────[healthy ✅]
redis: [启动]───[healthy ✅]
api: [启动]──────[healthy ✅]
web: [启动]常见服务的 healthcheck 模板
yaml
# MySQL
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p$$MYSQL_ROOT_PASSWORD"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
# PostgreSQL
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 10
# Redis
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
# MongoDB
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 10
# Elasticsearch
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"]
interval: 30s
timeout: 10s
retries: 10
# HTTP 服务通用
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s4.2.3 多配置文件覆盖
Docker Compose 支持多个配置文件的合并,实现不同环境的差异化配置。
配置文件合并机制:
docker-compose.yml → 基础配置(通用)
↓ 合并
docker-compose.override.yml → 覆盖配置(默认自动加载)
↓ 合并
docker-compose.prod.yml → 生产环境覆盖(手动指定)基础配置
yaml
# docker-compose.yml — 基础通用配置
services:
web:
build: ./web
ports:
- "80:80"
api:
build: ./api
environment:
NODE_ENV: production
depends_on:
- db
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: myapp
volumes:
- db-data:/var/lib/mysql
volumes:
db-data:开发环境覆盖
yaml
# docker-compose.override.yml — 开发环境(自动加载,无需 -f 参数)
services:
web:
volumes:
- ./web/src:/app/src # 挂载源码,热重载
environment:
DEBUG: "true"
api:
volumes:
- ./api/src:/app/src # 挂载源码
environment:
NODE_ENV: development
DEBUG: "true"
command: ["npm", "run", "dev"] # 覆盖为开发模式启动
ports:
- "3000:3000"
- "9229:9229" # 调试端口
db:
ports:
- "3306:3306" # 开发环境暴露数据库端口生产环境覆盖
yaml
# docker-compose.prod.yml — 生产环境
services:
web:
restart: always
deploy:
resources:
limits:
memory: 256M
api:
restart: always
environment:
NODE_ENV: production
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
replicas: 2 # 启动 2 个实例
db:
restart: always
# 生产环境不暴露端口
deploy:
resources:
limits:
memory: 1G使用方式
bash
# 开发环境(自动加载 override)
docker compose up -d
# 等同于:docker compose -f docker-compose.yml -f docker-compose.override.yml up -d
# 生产环境(手动指定,跳过 override)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# 查看合并后的最终配置
docker compose -f docker-compose.yml -f docker-compose.prod.yml config
# 使用环境变量指定配置文件
export COMPOSE_FILE=docker-compose.yml:docker-compose.prod.yml
docker compose up -d4.2.4 Compose Profiles
Profiles 允许你将服务分组,按需启动不同的服务集合。
yaml
services:
# ===== 核心服务(无 profile,始终启动)=====
web:
image: nginx
ports:
- "80:80"
api:
build: ./api
ports:
- "3000:3000"
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secret
# ===== 调试工具(debug profile)=====
adminer:
image: adminer
ports:
- "8080:8080"
profiles:
- debug # 只在 debug profile 下启动
mailhog:
image: mailhog/mailhog
ports:
- "1025:1025"
- "8025:8025"
profiles:
- debug
# ===== 监控工具(monitoring profile)=====
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
profiles:
- monitoring
grafana:
image: grafana/grafana
ports:
- "3001:3000"
profiles:
- monitoring
# ===== 测试工具(test profile)=====
test-runner:
build: ./api
command: ["npm", "test"]
profiles:
- test
depends_on:
- dbbash
# 只启动核心服务(无 profile 的服务)
docker compose up -d
# 启动核心服务 + 调试工具
docker compose --profile debug up -d
# 启动核心服务 + 监控
docker compose --profile monitoring up -d
# 启动多个 profile
docker compose --profile debug --profile monitoring up -d
# 运行测试
docker compose --profile test run --rm test-runner
# 使用环境变量
export COMPOSE_PROFILES=debug
docker compose up -d4.2.5 服务扩缩容
bash
# 将 api 服务扩展为 3 个实例
docker compose up -d --scale api=3
# 查看扩展后的实例
docker compose ps
# NAME SERVICE STATUS PORTS
# app-api-1 api Up 0.0.0.0:3000->3000/tcp
# app-api-2 api Up 0.0.0.0:3001->3000/tcp
# app-api-3 api Up 0.0.0.0:3002->3000/tcp
# app-web-1 web Up 0.0.0.0:80->80/tcp
# 缩容
docker compose up -d --scale api=1⚠️ 扩容注意事项:
- 不能使用
container_name(多实例名称冲突)- 不能使用固定的宿主机端口映射,应使用端口范围或让 Docker 自动分配
- 通常需要配合负载均衡器(如 Nginx)使用
yaml
# 支持扩容的配置写法
services:
api:
build: ./api
# ❌ 不能用固定端口
# ports:
# - "3000:3000"
# ✅ 不映射端口,通过 Nginx 代理
expose:
- "3000"
# ❌ 不能指定容器名
# container_name: my-api
nginx:
image: nginx:1.25-alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- apinginx
# nginx.conf — 负载均衡配置
upstream api_servers {
# Docker Compose 的 DNS 轮询会自动解析所有 api 实例
server api:3000;
}
server {
listen 80;
location / {
proxy_pass http://api_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}4.2.6 Compose Watch(开发热重载)
Compose V2.22+ 引入了 watch 功能,提供更智能的开发体验:
yaml
services:
api:
build: ./api
ports:
- "3000:3000"
develop:
watch:
# 源码变更 → 同步到容器(热重载)
- action: sync
path: ./api/src
target: /app/src
# 依赖文件变更 → 重建镜像并重启
- action: rebuild
path: ./api/package.json
# 配置文件变更 → 重启服务
- action: sync+restart
path: ./api/config
target: /app/configbash
# 启动 watch 模式
docker compose watch
# 或后台启动
docker compose up -d
docker compose watch &4.3 实战项目
Demo 1:最小 Compose 入门 — Nginx + 静态页面
yaml
# demo1-nginx/docker-compose.yml
services:
web:
image: nginx:1.25-alpine
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html:robash
# 创建项目
mkdir -p demo1-nginx/html && cd demo1-nginx
# 创建静态页面
cat > html/index.html <<'EOF'
<!DOCTYPE html>
<html>
<head>
<title>Docker Compose Demo</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background: linear-gradient(135deg, #0093E9 0%, #80D0C7 100%);
color: white;
}
.container { text-align: center; }
h1 { font-size: 2.5em; }
</style>
</head>
<body>
<div class="container">
<h1>🐳 Docker Compose is Working!</h1>
<p>Served by Nginx in a Docker container</p>
</div>
</body>
</html>
EOF
# 启动
docker compose up -d
# 测试
curl http://localhost:8080
# 查看状态
docker compose ps
# 查看日志
docker compose logs -f
# 停止
docker compose downDemo 2:Python Flask + Redis 计数器
demo2-flask-redis/
├── docker-compose.yml
├── app/
│ ├── app.py
│ ├── requirements.txt
│ └── Dockerfileyaml
# demo2-flask-redis/docker-compose.yml
services:
web:
build: ./app
ports:
- "5000:5000"
environment:
REDIS_HOST: redis
REDIS_PORT: 6379
depends_on:
redis:
condition: service_healthy
restart: unless-stopped
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
redis-data:python
# demo2-flask-redis/app/app.py
import os
import socket
from flask import Flask, jsonify
import redis
app = Flask(__name__)
redis_client = redis.Redis(
host=os.getenv("REDIS_HOST", "localhost"),
port=int(os.getenv("REDIS_PORT", 6379)),
decode_responses=True,
)
@app.route("/")
def index():
count = redis_client.incr("visit_count")
return jsonify({
"message": "Hello from Docker Compose!",
"visit_count": count,
"hostname": socket.gethostname(),
})
@app.route("/health")
def health():
try:
redis_client.ping()
return jsonify({"status": "healthy", "redis": "connected"}), 200
except redis.ConnectionError:
return jsonify({"status": "unhealthy", "redis": "disconnected"}), 503
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)# demo2-flask-redis/app/requirements.txt
flask==3.0.0
redis==5.0.0
gunicorn==21.2.0dockerfile
# demo2-flask-redis/app/Dockerfile
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN useradd -r -u 1000 appuser && chown -R appuser:appuser /app
USER appuser
EXPOSE 5000
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:5000/health')"
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:5000", "--workers", "2"]bash
# 启动
cd demo2-flask-redis
docker compose up -d --build
# 测试(多次访问,观察计数器递增)
curl http://localhost:5000
# {"hostname":"abc123","message":"Hello from Docker Compose!","visit_count":1}
curl http://localhost:5000
# {"hostname":"abc123","message":"Hello from Docker Compose!","visit_count":2}
# 进入 Redis 查看数据
docker compose exec redis redis-cli
> GET visit_count
> "2"
> exit
# 重启服务后数据不丢失(Redis 持久化到 Volume)
docker compose restart
curl http://localhost:5000
# {"visit_count":3} ← 继续递增,数据未丢失
# 清理
docker compose down # 保留 volume
docker compose down -v # 清除 volume(数据丢失)Demo 3:完整 Web 应用栈 — Nginx + Node.js API + MySQL + Redis
这是学习计划中要求的综合实战项目。
demo3-fullstack/
├── docker-compose.yml
├── .env
├── nginx/
│ └── nginx.conf
├── api/
│ ├── src/
│ │ └── index.js
│ ├── package.json
│ └── Dockerfile
└── db/
└── init.sqlbash
# .env
COMPOSE_PROJECT_NAME=fullstack-app
# Versions
MYSQL_VERSION=8.0
REDIS_VERSION=7-alpine
NGINX_VERSION=1.25-alpine
NODE_VERSION=20-alpine
# Ports
WEB_PORT=80
API_PORT=3000
# Database
MYSQL_ROOT_PASSWORD=rootpassword123
MYSQL_DATABASE=fullstack_db
MYSQL_USER=appuser
MYSQL_PASSWORD=apppassword123yaml
# demo3-fullstack/docker-compose.yml
services:
# ===== Nginx 反向代理 =====
nginx:
image: nginx:${NGINX_VERSION}
ports:
- "${WEB_PORT}:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
api:
condition: service_healthy
restart: unless-stopped
networks:
- frontend
# ===== Node.js API =====
api:
build:
context: ./api
dockerfile: Dockerfile
expose:
- "3000"
environment:
NODE_ENV: production
DB_HOST: mysql
DB_PORT: 3306
DB_USER: ${MYSQL_USER}
DB_PASSWORD: ${MYSQL_PASSWORD}
DB_NAME: ${MYSQL_DATABASE}
REDIS_HOST: redis
REDIS_PORT: 6379
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
restart: unless-stopped
networks:
- frontend
- backend
# ===== MySQL 数据库 =====
mysql:
image: mysql:${MYSQL_VERSION}
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- mysql-data:/var/lib/mysql
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
restart: unless-stopped
networks:
- backend
# ===== Redis 缓存 =====
redis:
image: redis:${REDIS_VERSION}
command: redis-server --appendonly yes
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- backend
networks:
frontend:
backend:
volumes:
mysql-data:
redis-data:nginx
# demo3-fullstack/nginx/nginx.conf
upstream api_backend {
server api:3000;
}
server {
listen 80;
server_name localhost;
# API 请求代理
location /api/ {
proxy_pass http://api_backend/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 健康检查
location /health {
proxy_pass http://api_backend/health;
}
# 默认首页
location / {
default_type text/html;
return 200 '<!DOCTYPE html>
<html>
<head>
<title>Fullstack App</title>
<style>
body { font-family: sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
h1 { color: #0066cc; }
pre { background: #f4f4f4; padding: 15px; border-radius: 5px; overflow-x: auto; }
button { padding: 10px 20px; font-size: 16px; cursor: pointer;
background: #0066cc; color: white; border: none; border-radius: 5px; margin: 5px; }
button:hover { background: #0052a3; }
#result { margin-top: 20px; }
</style>
</head>
<body>
<h1>Docker Compose Fullstack Demo</h1>
<p>Nginx + Node.js + MySQL + Redis</p>
<button onclick="fetchHealth()">Health Check</button>
<button onclick="fetchUsers()">Get Users</button>
<button onclick="addUser()">Add Random User</button>
<button onclick="fetchStats()">Get Stats</button>
<div id="result"><pre>Click a button to test the API...</pre></div>
<script>
async function fetchData(url) {
try {
const res = await fetch(url);
const data = await res.json();
document.getElementById("result").innerHTML = "<pre>" + JSON.stringify(data, null, 2) + "</pre>";
} catch(e) {
document.getElementById("result").innerHTML = "<pre>Error: " + e.message + "</pre>";
}
}
function fetchHealth() { fetchData("/api/health"); }
function fetchUsers() { fetchData("/api/users"); }
function fetchStats() { fetchData("/api/stats"); }
async function addUser() {
const names = ["Alice","Bob","Charlie","Diana","Eve","Frank","Grace","Henry"];
const name = names[Math.floor(Math.random() * names.length)];
const res = await fetch("/api/users", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ name: name, email: name.toLowerCase() + Math.floor(Math.random()*1000) + "@example.com" })
});
const data = await res.json();
document.getElementById("result").innerHTML = "<pre>" + JSON.stringify(data, null, 2) + "</pre>";
}
</script>
</body>
</html>';
}
}sql
-- demo3-fullstack/db/init.sql
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO users (name, email) VALUES
('Admin', 'admin@example.com'),
('Test User', 'test@example.com');json
// demo3-fullstack/api/package.json
{
"name": "fullstack-api",
"version": "1.0.0",
"main": "src/index.js",
"dependencies": {
"express": "^4.18.2",
"mysql2": "^3.6.5",
"redis": "^4.6.11"
}
}javascript
// demo3-fullstack/api/src/index.js
const express = require("express");
const mysql = require("mysql2/promise");
const { createClient } = require("redis");
const app = express();
app.use(express.json());
const PORT = process.env.PORT || 3000;
const pool = mysql.createPool({
host: process.env.DB_HOST || "localhost",
port: parseInt(process.env.DB_PORT || "3306"),
user: process.env.DB_USER || "root",
password: process.env.DB_PASSWORD || "",
database: process.env.DB_NAME || "test",
waitForConnections: true,
connectionLimit: 10,
});
const redisClient = createClient({
url: `redis://${process.env.REDIS_HOST || "localhost"}:${process.env.REDIS_PORT || 6379}`,
});
redisClient.on("error", (err) => console.error("Redis error:", err));
(async () => {
await redisClient.connect();
console.log("Connected to Redis");
})();
app.get("/health", async (req, res) => {
try {
await pool.query("SELECT 1");
await redisClient.ping();
res.json({
status: "healthy",
services: { mysql: "connected", redis: "connected" },
timestamp: new Date().toISOString(),
});
} catch (err) {
res.status(503).json({
status: "unhealthy",
error: err.message,
});
}
});
app.get("/users", async (req, res) => {
try {
const cached = await redisClient.get("users:all");
if (cached) {
return res.json({ source: "cache", users: JSON.parse(cached) });
}
const [rows] = await pool.query("SELECT * FROM users ORDER BY created_at DESC");
await redisClient.setEx("users:all", 30, JSON.stringify(rows));
res.json({ source: "database", users: rows });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.post("/users", async (req, res) => {
try {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ error: "name and email are required" });
}
const [result] = await pool.query(
"INSERT INTO users (name, email) VALUES (?, ?)",
[name, email]
);
await redisClient.del("users:all");
res.status(201).json({ id: result.insertId, name, email });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.get("/stats", async (req, res) => {
try {
const visits = await redisClient.incr("api:visits");
const [userCount] = await pool.query("SELECT COUNT(*) as count FROM users");
res.json({
api_visits: visits,
total_users: userCount[0].count,
uptime: process.uptime().toFixed(0) + "s",
});
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.listen(PORT, "0.0.0.0", () => {
console.log(`API server running on port ${PORT}`);
});
process.on("SIGTERM", async () => {
console.log("Shutting down gracefully...");
await redisClient.quit();
await pool.end();
process.exit(0);
});dockerfile
# demo3-fullstack/api/Dockerfile
FROM node:20-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm install --omit=dev && npm cache clean --force
COPY . .
USER node
EXPOSE 3000
CMD ["node", "src/index.js"]bash
# ===== 启动完整应用栈 =====
cd demo3-fullstack
# 构建并启动
docker compose up -d --build
# 查看服务状态
docker compose ps
# 等待所有服务健康(观察 STATUS 列)
watch -n 2 docker compose ps
# ===== 测试 =====
# 访问首页
curl http://localhost
# 健康检查
curl http://localhost/api/health
# 获取用户列表
curl http://localhost/api/users
# 添加用户
curl -X POST http://localhost/api/users \
-H "Content-Type: application/json" \
-d '{"name": "Docker Fan", "email": "docker@example.com"}'
# 再次获取(观察 source 从 database 变为 cache)
curl http://localhost/api/users
# → {"source":"cache", ...} ← Redis 缓存命中!
# 查看统计
curl http://localhost/api/stats
# ===== 运维操作 =====
# 查看日志
docker compose logs -f api
# 进入 MySQL 交互
docker compose exec mysql mysql -u appuser -papppassword123 fullstack_db
# mysql> SELECT * FROM users;
# mysql> exit
# 进入 Redis 交互
docker compose exec redis redis-cli
# > KEYS *
# > GET users:all
# > exit
# ===== 清理 =====
docker compose down # 保留数据卷
docker compose down -v # 清除所有数据Demo 4:开发环境 vs 生产环境多配置
demo4-multi-env/
├── docker-compose.yml # 基础配置
├── docker-compose.override.yml # 开发环境(自动加载)
├── docker-compose.prod.yml # 生产环境
├── .env # 环境变量
└── api/
├── src/index.js
├── package.json
└── Dockerfileyaml
# docker-compose.yml — 基础配置
services:
api:
build: ./api
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: appuser
POSTGRES_PASSWORD: ${DB_PASSWORD:-password}
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d myapp"]
interval: 10s
timeout: 5s
retries: 10
volumes:
db-data:yaml
# docker-compose.override.yml — 开发环境
services:
api:
ports:
- "3000:3000"
- "9229:9229" # Node.js 调试端口
volumes:
- ./api/src:/app/src # 热重载
environment:
NODE_ENV: development
DEBUG: "true"
command: ["node", "--watch", "--inspect=0.0.0.0:9229", "src/index.js"]
db:
ports:
- "5432:5432" # 暴露数据库端口便于本地工具连接
adminer: # 开发环境独有的数据库管理工具
image: adminer
ports:
- "8080:8080"
depends_on:
- dbyaml
# docker-compose.prod.yml — 生产环境
services:
api:
ports:
- "3000:3000"
environment:
NODE_ENV: production
restart: always
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
db:
restart: always
# 生产不暴露端口
deploy:
resources:
limits:
memory: 1Gbash
# 开发环境:自动加载 override
docker compose up -d
# 包含:api(带调试端口+热重载)+ db(暴露端口)+ adminer
# 生产环境:跳过 override,加载 prod
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# 包含:api(生产模式+资源限制)+ db(不暴露端口)
# 不包含 adminer
# 验证配置
docker compose config # 查看开发配置
docker compose -f docker-compose.yml -f docker-compose.prod.yml config # 查看生产配置Demo 5:使用 Profiles 管理可选服务
yaml
# demo5-profiles/docker-compose.yml
services:
# 核心服务 — 始终启动
app:
image: nginx:alpine
ports:
- "80:80"
# 数据库管理工具 — debug profile
adminer:
image: adminer
ports:
- "8080:8080"
profiles: ["debug"]
# 监控 — monitoring profile
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
profiles: ["monitoring"]
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
profiles: ["monitoring"]
# 日志 — logging profile
loki:
image: grafana/loki:latest
ports:
- "3100:3100"
profiles: ["logging"]bash
# 只启动核心
docker compose up -d
# 核心 + 调试
docker compose --profile debug up -d
# 核心 + 监控 + 日志
docker compose --profile monitoring --profile logging up -d
# 查看运行的服务
docker compose ps
# 停止特定 profile 的服务
docker compose --profile debug down4.4 常见问题 QA
Q1: docker compose up 和 docker compose run 有什么区别?
| 对比 | docker compose up | docker compose run |
|---|---|---|
| 作用 | 启动整个应用(所有服务) | 在一个服务中运行一次性命令 |
| 依赖 | 启动所有依赖 | 默认启动依赖(可用 --no-deps 跳过) |
| 端口 | 映射 YAML 中定义的端口 | 默认不映射端口(可用 -p 指定) |
| 容器生命周期 | 保持运行 | 命令完成后退出 |
| 使用场景 | 启动应用 | 运行迁移、测试、一次性脚本 |
bash
# up:启动整个应用
docker compose up -d
# run:运行一次性命令
docker compose run --rm api npm test # 运行测试
docker compose run --rm api npm run migrate # 数据库迁移
docker compose run --rm api sh # 进入 shell
docker compose run --rm --no-deps api node -e "console.log('hello')" # 不启动依赖Q2: docker compose exec 和 docker compose run 有什么区别?
| 对比 | docker compose exec | docker compose run |
|---|---|---|
| 目标 | 在已运行的容器中执行命令 | 启动新容器执行命令 |
| 前提 | 服务必须已经在运行 | 无需服务运行 |
| 容器 | 复用现有容器 | 创建新容器 |
| 环境 | 共享运行中容器的环境 | 独立的新容器环境 |
bash
# exec:在运行中的容器执行(服务必须已启动)
docker compose exec db mysql -u root -p
docker compose exec api bash
# run:启动新容器执行(即使服务未启动也可以)
docker compose run --rm api npm install
docker compose run --rm db mysql -u root -pQ3: 容器启动后报错 Connection refused(连接被拒绝)
原因:依赖的服务(如数据库)还没完全启动就尝试连接。
解决方案:
yaml
# 方案一:使用 depends_on + healthcheck(推荐)
services:
api:
depends_on:
db:
condition: service_healthy # 等 db 健康才启动 api
db:
image: mysql:8.0
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30sbash
# 方案二:应用层重试连接
# 在应用代码中加入连接重试逻辑(更健壮)javascript
// Node.js 连接重试示例
async function connectWithRetry(maxRetries = 10, delay = 3000) {
for (let i = 0; i < maxRetries; i++) {
try {
await pool.query("SELECT 1");
console.log("Database connected!");
return;
} catch (err) {
console.log(`DB connection attempt ${i + 1}/${maxRetries} failed, retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error("Failed to connect to database after max retries");
}Q4: docker compose down 后数据丢失了
原因:使用了 docker compose down -v,-v 参数会删除命名卷。
bash
# ===== 数据保留行为 =====
# 保留卷(数据不丢失)
docker compose down
# 删除卷(⚠️ 数据丢失!)
docker compose down -v
# 删除卷和镜像
docker compose down -v --rmi all
# ===== 最佳实践 =====
# 生产环境:永远不要用 -v
# 开发环境:需要重置数据时才用 -v
# 查看卷
docker volume ls | grep <project-name>
# 手动备份卷数据
docker run --rm \
-v fullstack-app_mysql-data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/mysql-backup.tar.gz -C /data .Q5: 如何让容器访问宿主机的服务(如本地数据库)?
yaml
services:
api:
image: my-api
extra_hosts:
# 添加特殊主机名,指向宿主机
- "host.docker.internal:host-gateway"
environment:
# 使用 host.docker.internal 替代 localhost
DB_HOST: host.docker.internal
DB_PORT: 5432bash
# Linux 上 host.docker.internal 需要 Docker 20.10+
# 或者使用 network_mode: host(容器共享宿主机网络栈)Q6: docker compose up --build 和 docker compose build + docker compose up 有区别吗?
bash
# 方式一:分开执行
docker compose build # 构建镜像
docker compose up -d # 启动服务(使用已构建的镜像)
# 方式二:一步到位
docker compose up -d --build # 构建 + 启动
# 区别不大,但 --build 更方便
# 注意:如果没有代码变更,build 会使用缓存,不会重新构建
# 强制不使用缓存重建
docker compose build --no-cache
docker compose up -dQ7: 多个 Compose 项目端口冲突怎么办?
bash
# 错误:两个项目都映射了 80 端口
# 项目 A:ports: - "80:80"
# 项目 B:ports: - "80:80"
# → Bind for 0.0.0.0:80 failed: port is already allocated
# ===== 解决方案 =====
# 方案一:使用不同端口
# 项目 A:ports: - "8080:80"
# 项目 B:ports: - "8081:80"
# 方案二:使用 .env 文件管理端口
# 项目 A 的 .env: WEB_PORT=8080
# 项目 B 的 .env: WEB_PORT=8081
# 方案三:查看端口占用
docker compose ps --format "table {{.Name}}\t{{.Ports}}"
# 或
docker ps --format "table {{.Names}}\t{{.Ports}}" | grep "0.0.0.0"Q8: 如何在 Compose 中使用已有的外部网络/卷?
yaml
# 使用已存在的外部网络(跨 Compose 项目通信)
networks:
shared-network:
external: true # 引用外部网络
name: my-shared-network # 外部网络名称
# 使用已存在的外部卷
volumes:
shared-data:
external: true
name: my-shared-volumebash
# 先创建外部网络/卷
docker network create my-shared-network
docker volume create my-shared-volume
# 然后在 Compose 中使用
docker compose up -dQ9: Compose 文件中的相对路径是相对于什么?
yaml
services:
api:
build:
context: ./api # 相对于 docker-compose.yml 文件的位置
volumes:
- ./src:/app/src # 相对于 docker-compose.yml 文件的位置
env_file:
- ./config/.env # 相对于 docker-compose.yml 文件的位置
# ⚠️ 所有相对路径都是相对于 docker-compose.yml 文件所在目录
# 而不是执行 docker compose 命令时的目录
# 如果从其他目录执行:
# cd /somewhere/else
# docker compose -f /path/to/docker-compose.yml up
# → 路径仍然相对于 /path/to/Q10: 如何查看 Compose 生成的资源名称?
bash
# Compose 默认的命名规则:<项目名>_<资源名>
# 项目名默认是 docker-compose.yml 所在的目录名
# 可通过 .env 中的 COMPOSE_PROJECT_NAME 或 -p 参数覆盖
# 查看所有资源
docker compose ps # 容器
docker network ls | grep <project> # 网络
docker volume ls | grep <project> # 卷
# 自定义项目名
docker compose -p myproject up -d
# 或在 .env 中设置
# COMPOSE_PROJECT_NAME=myproject
# 命名示例:
# 目录名为 demo3-fullstack,则:
# 容器:demo3-fullstack-api-1, demo3-fullstack-mysql-1
# 网络:demo3-fullstack_frontend, demo3-fullstack_backend
# 卷: demo3-fullstack_mysql-dataQ11: docker compose 命令要在哪个目录执行?
bash
# 默认情况:在 docker-compose.yml 所在的目录执行
cd /path/to/project
docker compose up -d
# 在其他目录执行:使用 -f 指定文件路径
docker compose -f /path/to/docker-compose.yml up -d
# 使用环境变量指定
export COMPOSE_FILE=/path/to/docker-compose.yml
docker compose up -dQ12: 如何优雅地更新单个服务(不影响其他服务)?
bash
# 只重建并重启指定服务
docker compose up -d --build api
# 不重建,仅重启
docker compose restart api
# 拉取最新镜像并重启
docker compose pull nginx
docker compose up -d nginx
# 强制重建(即使没有代码变更)
docker compose up -d --force-recreate api
# 或者
docker compose build --no-cache api
docker compose up -d api4.5 Compose 命令速查表
┌──────────────────────────────────────────────────────────────────┐
│ Docker Compose 命令速查表 │
├──────────────────┬───────────────────────────────────────────────┤
│ │ │
│ 启动与停止 │ docker compose up -d 后台启动 │
│ │ docker compose up -d --build 构建并启动 │
│ │ docker compose down 停止并清理 │
│ │ docker compose down -v 清理含数据卷 │
│ │ docker compose stop 仅停止 │
│ │ docker compose start 启动已停止服务 │
│ │ docker compose restart 重启 │
│ │ │
├──────────────────┼───────────────────────────────────────────────┤
│ │ │
│ 查看状态 │ docker compose ps 服务状态 │
│ │ docker compose logs -f 实时日志 │
│ │ docker compose top 查看进程 │
│ │ docker compose images 查看镜像 │
│ │ docker compose port svc 80 查看端口映射 │
│ │ │
├──────────────────┼───────────────────────────────────────────────┤
│ │ │
│ 执行命令 │ docker compose exec svc cmd 在容器中执行 │
│ │ docker compose run --rm svc 一次性运行 │
│ │ │
├──────────────────┼───────────────────────────────────────────────┤
│ │ │
│ 构建管理 │ docker compose build 构建镜像 │
│ │ docker compose build --no-cache 不用缓存 │
│ │ docker compose pull 拉取镜像 │
│ │ │
├──────────────────┼───────────────────────────────────────────────┤
│ │ │
│ 扩缩容 │ docker compose up --scale svc=3 扩展实例 │
│ │ │
├──────────────────┼───────────────────────────────────────────────┤
│ │ │
│ 配置与调试 │ docker compose config 查看最终配置 │
│ │ docker compose -f a.yml -f b.yml up 多文件 │
│ │ docker compose --profile x up 按 profile │
│ │ │
├──────────────────┼───────────────────────────────────────────────┤
│ │ │
│ 开发模式 │ docker compose watch 文件监听 │
│ │ docker compose up --watch 启动+监听 │
│ │ │
└──────────────────┴───────────────────────────────────────────────┘4.6 docker-compose.yml 模板
通用 Web 应用模板(快速开始)
yaml
# 适用于大多数 Web 应用的通用模板
services:
app:
build: .
ports:
- "${APP_PORT:-8080}:8080"
environment:
DATABASE_URL: postgres://${DB_USER:-postgres}:${DB_PASSWORD:-postgres}@db:5432/${DB_NAME:-app}
REDIS_URL: redis://redis:6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${DB_USER:-postgres}
POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}
POSTGRES_DB: ${DB_NAME:-app}
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres}"]
interval: 10s
timeout: 5s
retries: 10
restart: unless-stopped
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
db-data:
redis-data:WordPress 部署模板
yaml
services:
wordpress:
image: wordpress:latest
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: ${WP_DB_PASSWORD:-wordpress}
WORDPRESS_DB_NAME: wordpress
volumes:
- wp-content:/var/www/html/wp-content
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword}
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: ${WP_DB_PASSWORD:-wordpress}
volumes:
- db-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
restart: unless-stopped
volumes:
wp-content:
db-data:ELK 日志栈模板
yaml
services:
elasticsearch:
image: elasticsearch:8.12.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
volumes:
- es-data:/usr/share/elasticsearch/data
ports:
- "9200:9200"
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"]
interval: 30s
timeout: 10s
retries: 10
restart: unless-stopped
logstash:
image: logstash:8.12.0
volumes:
- ./logstash/pipeline:/usr/share/logstash/pipeline:ro
depends_on:
elasticsearch:
condition: service_healthy
restart: unless-stopped
kibana:
image: kibana:8.12.0
ports:
- "5601:5601"
environment:
ELASTICSEARCH_HOSTS: http://elasticsearch:9200
depends_on:
elasticsearch:
condition: service_healthy
restart: unless-stopped
volumes:
es-data:📝 学习建议:Docker Compose 是多容器管理的核心工具,建议按以下顺序学习:
- 先完成 Demo 1 和 Demo 2,理解基本语法和工作流
- 深入完成 Demo 3(完整 Web 应用栈),这是最贴近实际项目的练习
- 学习多配置文件和 Profiles(Demo 4 和 Demo 5),为生产部署做准备
- 尝试将自己的项目用 Compose 容器化
掌握 Compose 后,你就能在本地一条命令搭建起完整的开发环境,大幅提升开发效率。