Docker Compose 从入门到实战:一份完整的容器编排指南
前言
在容器化开发中,一个真实项目往往不是单个容器能搞定的——你至少需要一个应用服务和一个数据库,有时还会加上 Redis、Nginx、消息队列等多个组件。手动逐个启动容器、配置网络、处理依赖顺序,不仅繁琐而且极易出错。
Docker Compose 正是为了解决这个问题而生。它允许你用一份 YAML 文件声明式地描述整个应用架构,然后一条命令启动所有服务。本文将从概念到实战,系统梳理 Docker Compose 的核心知识,并通过一个 FastAPI + MySQL 的完整案例,带你掌握日常开发中最常用的编排技巧。
目录
- 一、Docker Compose 是什么
- 二、核心概念速览
- 三、docker-compose.yml 语法详解
- 四、实战案例:FastAPI + MySQL 一键编排
- 4.1 项目结构
- 4.2 Dockerfile 编写
- 4.3 init.sql 初始化脚本
- 4.4 .env 环境变量文件
- 4.5 docker-compose.yml 完整配置与逐行解析
- 五、启动与验证
- 六、常用命令速查
- 七、Compose 网络机制详解
- 八、数据持久化与 Volume
- 九、进阶技巧
- 9.1 多环境配置(override 文件)
- 9.2 Profiles 按需启动
- 9.3 服务扩缩容
- 9.4 日志管理
- 十、常见问题与排错
- 十一、总结
一、Docker Compose 是什么
Docker Compose 是 Docker 官方提供的多容器编排工具。它的核心能力是:用一个 docker-compose.yml(或 compose.yml)文件,以声明式的方式定义多个容器服务,然后通过一条命令统一启动、停止、重建整个应用栈。
适用场景:
- 本地开发环境搭建(应用 + 数据库 + 缓存)
- 自动化测试环境
- 单机部署的简单生产场景
- CI/CD 流水线中的集成测试
不适用的场景:
- 多节点集群编排(这属于 Docker Swarm 或 Kubernetes 的领域)
- 需要自动伸缩、滚动更新的复杂生产环境
版本演进:
| 阶段 | 工具名 | 说明 |
|---|---|---|
| V1(已废弃) | docker-compose(Python 实现) | 独立安装的二进制,命令用连字符 docker-compose |
| V2 | docker compose(Go 实现) | Docker CLI 插件形式,命令用空格 docker compose,性能更好,功能更丰富 |
目前推荐使用 V2 版本,即
docker compose(空格分隔)。Docker Desktop 新版本已默认集成 V2。
二、核心概念速览
在深入语法之前,先理清 Compose 中的几个关键概念:
Services(服务) 一个服务对应一个容器的配置模板。比如 mysql 服务、app 服务。同一个服务可以通过 scale 扩展出多个容器实例。
Volumes(数据卷) 用于持久化数据。容器可以随时重建,但卷里的数据不会丢失。典型场景就是数据库的数据目录。
Networks(网络) Compose 会自动为整个项目创建一个桥接网络,同一个项目内的所有服务默认处于同一网络,可以直接通过服务名互相访问。
项目(Project) 一组关联服务的集合。默认以 docker-compose.yml 所在目录的名字作为项目名。项目名会影响容器名、网络名、卷名的前缀。
三、docker-compose.yml 语法详解
3.1 文件命名
Compose 支持以下文件名(按优先级从高到低):
- 1.
compose.yaml(推荐,最新规范) - 2.
compose.yml - 3.
docker-compose.yaml - 4.
docker-compose.yml
也可以用 -f 参数指定任意文件名:
docker compose -f my-config.yml up
3.2 顶层结构
services: # 必须。定义所有服务
service1:
...
service2:
...
networks: # 可选。自定义网络
my_network:
...
volumes: # 可选。声明命名卷
my_volume:
...
configs: # 可选。声明配置对象
my_config:
...
secrets: # 可选。声明敏感数据
my_secret:
...
3.3 Service 常用字段速查
services:
my_service:
# ---- 镜像 / 构建 ----
image: nginx:1.25 # 直接使用镜像
build: # 或者从 Dockerfile 构建
context: . # 构建上下文路径
dockerfile: ./Dockerfile # Dockerfile 路径
args: # 构建参数
ENV: production
target: builder # 多阶段构建的目标阶段
# ---- 容器运行配置 ----
container_name: my_container # 指定容器名(不能 scale)
restart: unless-stopped # 重启策略
working_dir: /app # 工作目录
command: ["python", "main.py"] # 覆盖 CMD
entrypoint: ["./entrypoint.sh"] # 覆盖 ENTRYPOINT
user: "1000:1000" # 运行用户
# ---- 环境变量 ----
environment: # 直接写
MYSQL_HOST: mysql
DEBUG: "true"
env_file: # 或从文件加载
- .env
# ---- 端口映射 ----
ports:
- "8080:80" # 宿主机:容器
- "443:443"
- "127.0.0.1:3306:3306" # 限定绑定地址
# ---- 数据卷 ----
volumes:
- ./data:/app/data # 绑定挂载
- mysql_data:/var/lib/mysql # 命名卷
- ./config.yml:/etc/app/config.yml:ro # 只读挂载
# ---- 网络 ----
networks:
- my_network
hostname: my_host # 容器内 hostname
# ---- 依赖关系 ----
depends_on:
mysql:
condition: service_healthy # 等健康检查通过
redis:
condition: service_started # 等容器启动(默认)
# ---- 健康检查 ----
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 30s
# ---- 资源限制 ----
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
memory: 256M
# ---- 日志配置 ----
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
四、实战案例:FastAPI + MySQL 一键编排
下面通过一个完整的 FastAPI + MySQL 项目,把前面的知识点串联起来。
4.1 项目结构
project-root/
├── .env # 环境变量(敏感信息,不进 git)
├── docker-compose.yml # Compose 编排文件
├── requirements.txt # Python 依赖
├── __002__docker/
│ ├── Dockerfile # 应用镜像构建文件
│ └── init.sql # 数据库初始化脚本
├── __001__fastapi/
│ └── server.py # FastAPI 应用代码
└── ...
4.2 Dockerfile 编写
# ---- 基础镜像 ----
# python:3.11-slim 比完整版小很多,适合生产
FROM python:3.11-slim
# ---- 工作目录 ----
WORKDIR /app
# ---- 环境变量 ----
# PYTHONPATH: 让 Python 能找到 /app 下的模块
# PYTHONDONTWRITEBYTECODE: 不生成 .pyc 文件,减少镜像体积
# PYTHONUNBUFFERED: 日志直接输出到 stdout,不缓冲
# LANG/LC_ALL: 防止 Python 处理中文时出错
ENV PYTHONPATH=/app \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
LANG=C.UTF-8 \
LC_ALL=C.UTF-8
# ---- 安装依赖 ----
# 先复制 requirements.txt 再安装,利用 Docker 层缓存
# 只要 requirements.txt 没变,这一层就不会重新构建
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# ---- 复制源码 ----
COPY . .
# ---- 切换到应用目录 ----
WORKDIR /app/__001__fastapi
# ---- 暴露端口(文档性质,实际映射由 compose 控制)----
EXPOSE 8003
# ---- 启动命令 ----
CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8003"]
关于
EXPOSE的说明:EXPOSE只是一个文档声明,告诉使用者这个镜像监听哪个端口。真正把端口暴露到宿主机,需要在docker-compose.yml的ports里配置。两者互不影响。
4.3 init.sql 初始化脚本
-- =============================================================================
-- MySQL 初始化脚本
--
-- 执行时机:首次启动 MySQL 容器且数据目录为空时,由官方镜像自动执行。
-- 挂载方式:./init.sql -> /docker-entrypoint-initdb.d/init.sql
--
-- 重要提醒:
-- 1. 如果 mysql_data 卷已存在,此脚本不会重复执行。
-- 需要重新执行时:docker compose down -v 删除卷后再启动。
-- 2. 初始化脚本默认连接字符集为 latin1,必须先声明 utf8mb4,
-- 否则中文数据会乱码。
-- =============================================================================
-- 设置字符集(必须在第一条 SQL 之前)
SET NAMES utf8mb4;
SET CHARACTER SET utf8mb4;
-- 建库(与 .env 中 MYSQL_DATABASE_NAME 配合,双重保障幂等性)
CREATE DATABASE IF NOT EXISTS docker_compose_demo
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_unicode_ci;
USE docker_compose_demo;
-- 对话记录表
CREATE TABLE IF NOT EXISTS chat_messages (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
role VARCHAR(32) NOT NULL COMMENT 'user 或 assistant',
content TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '消息正文',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (id),
KEY idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='大模型对话历史';
-- 示例数据(方便首次打开页面就有历史可看)
INSERT INTO chat_messages (role, content) VALUES
('user', 'Docker Compose 是什么?'),
('assistant', 'Docker Compose 是用一个 YAML 文件定义并一键启动多个容器的工具,适合 app + 数据库这类多服务场景。');
4.4 .env 环境变量文件
# =============================================================================
# 环境变量配置文件
#
# 作用:集中管理所有敏感配置,避免硬编码在 docker-compose.yml 或代码中。
#
# 加载方式:
# 1. docker-compose.yml 中的 ${VAR} 会自动替换(Compose 自动读取 .env)
# 2. app 服务的 env_file: .env 会将所有变量注入容器内部
#
# 安全提示:此文件不要提交到 Git,应在 .gitignore 中排除。
# =============================================================================
# MySQL 配置
MYSQL_USER=root
MYSQL_PASSWORD=your_secure_password_here
MYSQL_DATABASE_NAME=docker_compose_demo
MYSQL_PORT=3306
# 应用配置
APP_PORT=8003
# 大模型 API Key(按需配置)
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxx
4.5 docker-compose.yml 完整配置与逐行解析
下面是完整的 Compose 文件,我将逐段拆解每一处配置的含义和设计考量。
# =============================================================================
# Docker Compose 编排文件
#
# 架构说明:
#
# 宿主机浏览器 ──> localhost:8003 ──> app (FastAPI, 端口 8003)
# │
# │ 通过 Docker 内部网络
# │ 连接 mysql:3306
# ▼
# mysql (MySQL 8.0, 端口 3306)
# │
# │ 持久化存储
# ▼
# mysql_data (Docker 命名卷)
#
# 使用方式:
# 启动: docker compose up -d --build
# 停止: docker compose down
# 查看日志:docker compose logs -f
# 删除数据卷(重置数据库):docker compose down -v
# =============================================================================
services:
# ---------------------------------------------------------------------------
# 服务一:MySQL 8.0 数据库
# ---------------------------------------------------------------------------
mysql:
image: mysql:8.0
# container_name 指定容器名称,方便用 docker exec 等命令操作
# 注意:设置了 container_name 后,该服务不能用 docker compose up --scale 扩容
container_name: compose-demo-mysql
# unless-stopped:异常退出自动重启,除非你手动 docker compose stop
# 其他选项:no / always / on-failure[:max-retries]
restart: unless-stopped
# 环境变量:${VAR} 语法从 .env 文件自动读取
# MYSQL_ROOT_PASSWORD:MySQL root 用户的密码(必填)
# MYSQL_DATABASE:首次启动时自动创建的数据库名
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE_NAME}
# 端口映射:宿主机端口:容器端口
# 这里宿主机和容器都用同一个端口,由 .env 中 MYSQL_PORT 控制
# 映射后,宿主机可以通过 localhost:3306 连接容器内的 MySQL
ports:
- "${MYSQL_PORT}:${MYSQL_PORT}"
# command 覆盖镜像默认的启动命令,在 mysqld 启动参数中追加字符集配置
# 这是防止中文乱码的第一道防线(服务端级别)
# 第二道防线在 init.sql 中的 SET NAMES utf8mb4
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --init-connect=SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci
volumes:
# 命名卷挂载:将 MySQL 数据目录映射到 Docker 管理的持久化卷
# 即使删除容器(docker compose down),数据仍然保留在 mysql_data 卷中
# 只有执行 docker compose down -v 才会删除数据卷
- mysql_data:/var/lib/mysql
# 绑定挂载:将本地 init.sql 挂载到 MySQL 的初始化目录
# :ro 表示只读,容器不能修改这个文件
# 官方 MySQL 镜像约定:首次启动时自动执行此目录下的 .sql / .sh 文件
- ./__002__docker/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
# 健康检查:确认 MySQL 真正可用,而不只是容器进程在运行
# 这是 depends_on: condition: service_healthy 的前提
healthcheck:
# 使用 mysqladmin ping 检测 MySQL 是否能响应连接
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u${MYSQL_USER}", "-p${MYSQL_PASSWORD}"]
# 每 5 秒检查一次
interval: 5s
# 单次检查超时时间
timeout: 5s
# 连续失败 12 次后标记为 unhealthy
retries: 12
# 容器启动后给 20 秒的宽限期再开始检查(MySQL 初始化需要时间)
start_period: 20s
# ---------------------------------------------------------------------------
# 服务二:FastAPI 应程序
# ---------------------------------------------------------------------------
app:
# build:不直接拉取镜像,而是根据本地 Dockerfile 构建
build:
context: . # 构建上下文(项目根目录)
dockerfile: __002__docker/Dockerfile # Dockerfile 的相对路径
container_name: compose-demo-app
restart: unless-stopped
# 应用端口映射:宿主机 8003 -> 容器 8003
ports:
- "8003:8003"
# env_file:将 .env 文件中的所有变量注入到容器内部
# 这样 FastAPI 代码里可以通过 os.environ.get("MYSQL_PASSWORD") 读取
env_file:
- .env
# 注意:environment 中的变量会覆盖 env_file 中的同名变量
# 这里特别覆盖 MYSQL_HOST 为 mysql(服务名),原因如下:
#
# 在 Docker Compose 创建的网络中,每个服务名就是一个 DNS 主机名。
# 容器内部的 localhost 指向容器自己,而不是宿主机或其他容器。
# 所以 app 连接 MySQL 时,必须用 "mysql" 作为主机名,
# 即连接 mysql:3306,而不是 localhost:3306。
environment:
MYSQL_HOST: mysql
# depends_on:定义服务启动顺序
# condition: service_healthy 表示不仅要等 mysql 容器启动,
# 还要等它的健康检查通过后,才启动 app 服务。
# 这避免了 FastAPI 启动后立即连接数据库失败的问题。
depends_on:
mysql:
condition: service_healthy
# =============================================================================
# 顶层 volumes 声明
# 必须在此声明,上面 mysql 服务中引用的 mysql_data 卷才会被创建。
# 默认使用 local 驱动,数据存储在 Docker 的数据目录中。
# =============================================================================
volumes:
mysql_data:
五、启动与验证
5.1 启动服务
# 前台启动(可以看到实时日志,适合调试)
docker compose up --build
# 后台启动(生产/日常使用)
docker compose up -d --build
执行后,Compose 会按照以下顺序工作:
1. 读取 docker-compose.yml 和 .env
2. 创建网络(默认:项目名_default)
3. 创建 mysql_data 命名卷(如果不存在)
4. 启动 mysql 容器
5. 等待 mysql 健康检查通过
6. 构建 app 镜像(执行 Dockerfile)
7. 启动 app 容器
8. 完成
5.2 验证服务状态
# 查看运行中的容器
docker compose ps
# 输出示例:
# NAME STATUS PORTS
# compose-demo-mysql Up (healthy) 0.0.0.0:3306->3306/tcp
# compose-demo-app Up 0.0.0.0:8003->8003/tcp
5.3 查看日志
# 查看所有服务的日志
docker compose logs -f
# 只看某个服务的日志
docker compose logs -f app
docker compose logs -f mysql
# 查看最后 100 行
docker compose logs --tail=100
5.4 访问应用
浏览器打开 http://localhost:8003,如果页面正常显示,说明整个编排成功。
5.5 进入容器排查问题
# 进入 app 容器的 bash
docker exec -it compose-demo-app bash
# 进入 mysql 容器并登录数据库
docker exec -it compose-demo-mysql mysql -uroot -p
# 在 app 容器内测试 MySQL 连通性
docker exec -it compose-demo-app python -c "
import os
host = os.environ.get('MYSQL_HOST', 'localhost')
print(f'MYSQL_HOST={host}')
"
六、常用命令速查
6.1 生命周期管理
# 创建并启动所有服务(后台运行)
docker compose up -d
# 创建并启动,同时重新构建镜像
docker compose up -d --build
# 只启动指定服务
docker compose up -d mysql
# 停止所有服务(保留容器)
docker compose stop
# 停止并删除容器、网络
docker compose down
# 停止并删除容器、网络、数据卷(⚠️ 数据会丢失)
docker compose down -v
# 停止并删除,同时移除镜像
docker compose down --rmi all
6.2 状态查看
# 查看服务状态
docker compose ps
# 查看所有容器(包括已停止的)
docker compose ps -a
# 查看资源使用情况
docker compose top
# 查看某个服务的进程
docker compose top mysql
6.3 日志调试
# 实时跟踪所有服务日志
docker compose logs -f
# 只看某个服务
docker compose logs -f app
# 带时间戳
docker compose logs -f -t
# 查看最后 N 行
docker compose logs --tail=50 app
6.4 执行命令
# 在运行中的容器内执行命令
docker compose exec app bash
docker compose exec mysql mysql -uroot -p
# 一次性执行(不进入交互模式)
docker compose exec app python manage.py migrate
6.5 镜像管理
# 重新构建镜像(不启动)
docker compose build
# 不使用缓存重新构建
docker compose build --no-cache
# 拉取最新镜像
docker compose pull
七、Compose 网络机制详解
7.1 默认网络行为
当你执行 docker compose up 时,Compose 会自动创建一个桥接网络,命名规则为:
<项目名>_default
项目名默认取 docker-compose.yml 所在目录的名字。例如项目目录叫 my-project,则网络名为 my-project_default。
同一项目内的所有服务默认都加入这个网络,无需额外配置。
7.2 服务间通信
在 Compose 网络内部,每个服务名就是一个 DNS 主机名。这意味着:
app 容器连接 MySQL:
✅ 正确:mysql:3306(使用服务名)
❌ 错误:localhost:3306(localhost 指向 app 容器自己)
❌ 错误:172.18.0.2:3306(IP 可能变化,不可靠)
这就是为什么在 docker-compose.yml 的 app 服务中,需要覆盖环境变量:
environment:
MYSQL_HOST: mysql # 使用服务名,不是 localhost
7.3 端口映射与网络的关系
外部网络(宿主机)
│
│ ports 映射
▼
Docker 网络(bridge)
│
├── app:8003 ←── 宿主机 localhost:8003 可访问
└── mysql:3306 ←── 宿主机 localhost:3306 可访问
│
└── app 容器可直接通过 mysql:3306 访问(无需 ports 映射)
注意:ports 映射是让宿主机外部访问容器用的。在 Compose 网络内部,服务之间可以直接通过服务名 + 容器端口通信,不需要 ports 映射。
即使 MySQL 不配置 ports,app 容器依然可以通过 mysql:3306 访问数据库。只有宿主机需要连接数据库时(比如用 Navicat),才需要 ports 映射。
7.4 自定义网络
如果需要更复杂的网络拓扑(比如隔离某些服务),可以自定义网络:
services:
app:
networks:
- frontend
- backend
mysql:
networks:
- backend # mysql 只在 backend 网络,外部无法直接访问
nginx:
networks:
- frontend # nginx 只在 frontend 网络
networks:
frontend:
driver: bridge
backend:
driver: bridge
这种配置下,nginx 和 mysql 之间无法直接通信,必须通过 app 中转,实现了网络层面的隔离。
八、数据持久化与 Volume
8.1 Volume 的类型
Docker 支持两种挂载方式,在 Compose 中都很常用:
volumes:
# 1. 命名卷(Named Volume)—— Docker 管理
- mysql_data:/var/lib/mysql
# 2. 绑定挂载(Bind Mount)—— 映射宿主机目录
- ./__002__docker/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
| 对比项 | 命名卷 | 绑定挂载 |
|---|---|---|
| 数据存储位置 | Docker 数据目录(如 /var/lib/docker/volumes/) | 宿主机指定路径 |
| 是否需要手动创建路径 | 否,Docker 自动创建 | 是,宿主机路径必须存在 |
| 适合场景 | 数据库数据、应用运行时数据 | 配置文件、源代码、开发热重载 |
| 可移植性 | 好(跟着 Docker 走) | 差(依赖宿主机路径) |
| 性能 | 好 | macOS/Windows 下稍慢(文件系统同步) |
8.2 数据卷的生命周期
docker compose up -d
│
├── mysql_data 卷不存在 → 创建卷 → 执行 init.sql → 启动 MySQL
│
└── mysql_data 卷已存在 → 跳过 init.sql → 直接启动 MySQL
docker compose down
│
└── 删除容器和网络,但 保留 mysql_data 卷
docker compose down -v
│
└── 删除容器、网络,且 删除 mysql_data 卷(⚠️ 数据丢失)
实战建议: 当你需要修改
init.sql并重新执行时,正确的做法是:docker compose down -v # 删除旧卷 docker compose up -d --build # 重新创建(会再次执行 init.sql)
8.3 查看和管理卷
# 列出所有卷
docker volume ls
# 查看卷详情
docker volume inspect <项目名>_mysql_data
# 手动删除卷
docker volume rm <项目名>_mysql_data
# 清理所有未使用的卷(⚠️ 谨慎使用)
docker volume prune
九、进阶技巧
9.1 多环境配置(override 文件)
实际项目中,开发、测试、生产环境的配置往往不同。Compose 支持多文件合并来实现环境差异化:
docker-compose.yml(基础配置):
services:
app:
build: .
env_file:
- .env
environment:
MYSQL_HOST: mysql
mysql:
image: mysql:8.0
volumes:
- mysql_data:/var/lib/mysql
docker-compose.override.yml(开发环境覆盖,自动加载):
services:
app:
# 开发环境挂载源码,支持热重载
volumes:
- ./__001__fastapi:/app/__001__fastapi
command: ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8003", "--reload"]
environment:
DEBUG: "true"
mysql:
ports:
- "3306:3306" # 开发环境暴露端口方便调试
docker-compose.prod.yml(生产环境覆盖,需手动指定):
services:
app:
restart: always
deploy:
resources:
limits:
cpus: "2.0"
memory: 1G
mysql:
restart: always
# 生产环境不暴露数据库端口到宿主机
ports: []
使用方式:
# 开发环境(自动合并 docker-compose.yml + docker-compose.override.yml)
docker compose up -d
# 生产环境(手动指定)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
9.2 Profiles 按需启动
有时一些服务只在特定场景下需要(比如调试工具、管理面板)。用 profiles 可以实现按需启动:
services:
app:
image: my-app:latest
# 不设置 profiles,默认始终启动
mysql:
image: mysql:8.0
# 不设置 profiles,默认始终启动
adminer:
image: adminer:latest
ports:
- "8080:8080"
profiles:
- debug # 只在指定 debug profile 时启动
# 默认启动(只启动 app 和 mysql)
docker compose up -d
# 启动所有服务(包括 adminer)
docker compose --profile debug up -d
9.3 服务扩缩容
对于无状态的应用服务,可以快速扩容:
# 将 app 服务扩展到 3 个实例
docker compose up -d --scale app=3
注意: 使用 --scale 时,不能设置 container_name(因为名字必须唯一),也不能用固定的宿主机端口映射(会冲突)。需要改用端口范围或不映射宿主机端口。
services:
app:
build: .
# 不要设置 container_name
# 不要用固定端口映射,改用随机端口
ports:
- "8003" # 只指定容器端口,宿主机端口随机分配
9.4 日志管理
默认情况下,容器日志会无限增长。建议配置日志轮转:
services:
app:
logging:
driver: json-file
options:
max-size: "10m" # 单个日志文件最大 10MB
max-file: "3" # 最多保留 3 个文件
也可以全局配置,在 /etc/docker/daemon.json 中设置:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "20m",
"max-file": "5"
}
}
十、常见问题与排错
10.1 app 连接 MySQL 失败
症状: FastAPI 启动报 Connection refused 或 Can't connect to MySQL server。
排查步骤:
# 1. 确认 mysql 容器是否健康
docker compose ps
# 如果 mysql 状态不是 healthy,查看日志
docker compose logs mysql
# 2. 确认 app 容器内 MYSQL_HOST 是否正确
docker compose exec app env | grep MYSQL
# 应该看到 MYSQL_HOST=mysql,而不是 localhost
# 3. 在 app 容器内测试网络连通性
docker compose exec app bash
# 安装 ping(slim 镜像可能没有)
apt-get update && apt-get install -y iputils-ping
ping mysql
常见原因:
| 原因 | 解决方案 |
|---|---|
| MYSQL_HOST 设为 localhost | 改为 mysql(服务名) |
| MySQL 还没就绪 app 就启动了 | 使用 depends_on + condition: service_healthy |
| 密码错误 | 检查 .env 中的 MYSQL_PASSWORD |
| 数据库名错误 | 检查 .env 中的 MYSQL_DATABASE_NAME |
10.2 中文乱码
症状: 数据库中存储的中文显示为 ??? 或乱码。
解决方案(三层防护,缺一不可):
# docker-compose.yml 中 MySQL 启动参数
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
-- init.sql 开头
SET NAMES utf8mb4;
SET CHARACTER SET utf8mb4;
# Python 连接数据库时指定编码
import pymysql
conn = pymysql.connect(
host='mysql',
port=3306,
user='root',
password='xxx',
database='docker_compose_demo',
charset='utf8mb4'
)
10.3 init.sql 没有执行
症状: 启动后数据库是空的,表没有被创建。
原因: MySQL 官方镜像的初始化脚本只在数据目录为空时执行。如果 mysql_data 卷已存在(之前启动过),就不会重复执行。
解决方案:
# 删除数据卷后重新启动
docker compose down -v
docker compose up -d --build
10.4 镜像构建失败
# 查看详细构建日志
docker compose build --no-cache
# 常见原因:
# 1. Dockerfile 中 COPY 的文件路径不对(注意 context 的设置)
# 2. pip install 网络超时(可配置国内镜像源)
# 3. requirements.txt 中有不存在的包名
10.5 端口被占用
症状:Error: Bind for 0.0.0.0:3306 failed: port is already allocated
# 查看哪个进程占用了端口
lsof -i :3306
# 或
netstat -tlnp | grep 3306
# 解决方案:
# 1. 停止占用端口的进程
# 2. 修改 .env 中的端口映射
# 3. 修改 docker-compose.yml 中的 ports 配置
十一、总结
这份 Compose 文件做了什么
一条命令 docker compose up -d --build,自动完成:
1. 创建专用网络,所有服务互通
2. 启动 MySQL 8.0,设置 utf8mb4 字符集
3. 创建命名卷 mysql_data,持久化数据库
4. 执行 init.sql,自动建库建表灌数据
5. 健康检查确认 MySQL 就绪
6. 构建 FastAPI 镜像(安装依赖、复制代码)
7. 启动 FastAPI,通过服务名 mysql 连接数据库
8. 映射 8003 端口,浏览器即可访问
核心设计要点回顾
环境变量集中管理
└── .env 文件存放敏感信息,docker-compose.yml 用 ${VAR} 引用
容器间通信
└── 服务名即 DNS 主机名,app 连接 mysql:3306 而非 localhost:3306
启动顺序控制
└── depends_on + service_healthy,确保数据库就绪后再启动应用
数据持久化
└── 命名卷挂载数据库目录,容器重建不丢数据
初始化自动化
└── 挂载 init.sql 到 /docker-entrypoint-initdb.d/,首次启动自动执行
什么时候用 Docker Compose,什么时候用 K8s
| 维度 | Docker Compose | Kubernetes |
|---|---|---|
| 适用规模 | 单机、几个到十几个服务 | 多节点集群、成百上千个服务 |
| 复杂度 | 低,一份 YAML 搞定 | 高,需要学习 Pod/Deployment/Service 等概念 |
| 自动伸缩 | 不支持 | 原生支持 |
| 跨节点调度 | 不支持 | 核心能力 |
| 典型场景 | 本地开发、小型项目部署 | 生产环境、微服务架构 |
简单判断标准: 如果你的所有服务跑在一台机器上,Docker Compose 就够了。当你开始考虑多台机器、高可用、自动伸缩时,就该看看 Kubernetes 了。
本文所有代码和配置均基于实际可运行的项目,
docker compose up -d --build一键验证。建议动手实操,遇到问题善用docker compose logs排查。
更多推荐

所有评论(0)