前言

在容器化开发中,一个真实项目往往不是单个容器能搞定的——你至少需要一个应用服务和一个数据库,有时还会加上 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
V2docker 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. 1.compose.yaml(推荐,最新规范)
  2. 2.compose.yml
  3. 3.docker-compose.yaml
  4. 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 ComposeKubernetes
适用规模单机、几个到十几个服务多节点集群、成百上千个服务
复杂度低,一份 YAML 搞定高,需要学习 Pod/Deployment/Service 等概念
自动伸缩不支持原生支持
跨节点调度不支持核心能力
典型场景本地开发、小型项目部署生产环境、微服务架构

简单判断标准: 如果你的所有服务跑在一台机器上,Docker Compose 就够了。当你开始考虑多台机器、高可用、自动伸缩时,就该看看 Kubernetes 了。


本文所有代码和配置均基于实际可运行的项目,docker compose up -d --build 一键验证。建议动手实操,遇到问题善用 docker compose logs 排查。

更多推荐