跳到主要内容
Cowers://KNOWLEDGE
全部文章
服务端与存储

🐳 Docker 完整操作手册

一份系统整理的 Docker 学习与实战笔记,涵盖基础概念、镜像、容器、数据卷、网络、Compose 编排、Dockerfile 编写及最佳实践,适合日常查阅与复习。

🐳 Docker 完整操作手册

一份系统整理的 Docker 学习与实战笔记,涵盖基础概念、镜像、容器、数据卷、网络、Compose 编排、Dockerfile 编写及最佳实践,适合日常查阅与复习。

目录


一、Docker 基础概念

1.1 容器 vs 虚拟机

对比项 虚拟机(VM) 容器(Container)
隔离级别 硬件级隔离,每个VM有独立OS内核 进程级隔离,共享宿主机内核
启动速度 分钟级 秒级甚至毫秒级
体积 通常几GB到几十GB 通常几MB到几百MB
性能损耗 较大(需虚拟化硬件) 接近原生
资源占用
隔离性/安全性 更强 相对较弱(共享内核)

一句话理解 虚拟机虚拟化的是硬件,每个VM都有自己完整的操作系统;容器虚拟化的是操作系统层,所有容器共享宿主机的内核,只是在文件系统、进程、网络等维度做了隔离,因此更轻量。

1.2 核心概念

  • 镜像(Image):只读模板,包含运行应用所需的文件系统、依赖库和配置,类似"面向对象"里的类。
  • 容器(Container):镜像的运行实例,是镜像的一个"可读写"层加上运行中的进程,类似"类的实例(对象)"。
  • 仓库(Registry / Repository):集中存储和分发镜像的服务,如 Docker Hub、阿里云容器镜像服务、私有 Harbor 仓库等。
  • Dockerfile:用来描述"如何一步步构建出一个镜像"的文本脚本。
  • Docker Compose:用一个 YAML 文件定义、编排多个容器(服务)的工具,一键启动整套应用。
  • 数据卷(Volume):用于持久化容器数据、脱离容器生命周期存在的存储机制。

1.3 Docker 架构

flowchart LR
    A["Docker Client<br/>(docker 命令行)"] -->|"REST API / Unix Socket"| B["Docker Daemon<br/>(dockerd)"]
    B --> C["镜像 Images"]
    B --> D["容器 Containers"]
    B --> E["数据卷 Volumes"]
    B --> F["网络 Networks"]
    B <-->|"pull / push"| G["镜像仓库 Registry<br/>(Docker Hub / 私有仓库)"]

Docker 采用 C/S(客户端-服务端)架构:docker 命令行只是一个客户端,真正干活的是后台常驻的 dockerd 守护进程,两者通过 REST API(通常经由本地 Unix Socket)通信。dockerd 再往下会调用 containerdrunc 等组件真正完成容器的创建与运行。

1.4 安装 Docker

CentOS / RHEL 系列

# 1. 卸载旧版本(如果有)
sudo yum remove -y docker docker-client docker-client-latest \
  docker-common docker-latest docker-latest-logrotate \
  docker-logrotate docker-engine
 
# 2. 安装依赖工具
sudo yum install -y yum-utils
 
# 3. 添加 Docker 官方仓库(国内网络环境下可替换为国内源,如阿里云:
#    https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo)
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
 
# 4. 安装 Docker Engine、CLI、containerd 及 compose/buildx 插件
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
 
# 5. 启动并设置开机自启
sudo systemctl start docker
sudo systemctl enable docker

Ubuntu / Debian 系列

# 1. 安装依赖
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
 
# 2. 添加官方 GPG 密钥与仓库
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
 
# 3. 安装
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
 
sudo systemctl enable --now docker

安装后检查

docker --version           # Docker 引擎版本
docker compose version     # Compose 插件版本
docker info                # 详细运行时信息(存储驱动、cgroup、镜像加速地址等)
docker run hello-world     # 跑一个测试容器,验证安装成功

配置免 sudo 使用(将当前用户加入 docker 组)

sudo usermod -aG docker $USER
newgrp docker    # 或重新登录使其生效

安全提示 docker 组成员实际上等价于 root 权限(可以挂载宿主机任意目录到容器里再逃逸出来),生产服务器上请谨慎分配该权限。

1.5 国内镜像加速配置

背景 由于 Docker Hub(docker.io)服务器在境外,国内直接 docker pull 经常出现速度慢、超时甚至连接失败的情况,配置"镜像加速器"可以显著改善这一体验。

需要特别注意:第三方公共镜像站的可用性变化非常频繁。 历史上广泛使用的中科大(USTC)、清华 TUNA、网易 163 等镜像站均已停止同步 Docker Hub;此外网上流传的很多加速地址随时可能失效或被关停,配置前请务必自行验证当前是否可用,不要盲目照抄某一篇教程。

通用配置方法(Linux):

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": [
    "https://docker.m.daocloud.io"
  ]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
 
# 验证是否生效
docker info | grep -A 5 "Registry Mirrors"

几种更稳定的思路(优先级从高到低推荐):

  1. 云厂商官方个人加速器(最推荐,免费且相对稳定):
    • 服务器在腾讯云上时,可直接使用 https://mirror.ccs.tencentyun.com(仅限腾讯云内网访问,外部无法使用)
    • 服务器在阿里云上时,登录"容器镜像服务(ACR)"控制台的"镜像工具"页面,可以获取账号专属的个人加速器地址(形如 https://<你的专属ID>.mirror.aliyuncs.com),仅限本账号 ECS 使用
  2. 第三方公共/商业镜像站:网上有一些第三方镜像加速服务可作为补充,通常可以同时配置 2~3 个让 Docker 自动择优使用。但涉及生产环境或私有/敏感镜像时不建议依赖未经审计的第三方站点,存在镜像被中间人篡改的风险。
  3. 自建镜像代理:长期有稳定加速需求的话,可以基于开源方案(Nginx 反代、Cloudflare Workers、或 CNCF distribution/distribution 项目)自建一套镜像缓存服务。
  4. 走已有代理工具:如果本机/服务器已经有可用的代理(如 Clash/Mihomo 之类),也可以直接给 Docker daemon 配置 HTTP 代理,见下方。

使用注意

  • 加速地址必须是 https:// 开头
  • registry-mirrors 只对 docker.io(Docker Hub)生效,不会加速 ghcr.ioquay.ioregistry.k8s.io 等其他仓库
  • 建议同时配置多个镜像源作为互备,并定期检查可用性

给 Docker daemon 配置 HTTP 代理(通过 systemd):

sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf <<-'EOF'
[Service]
Environment="HTTP_PROXY=http://127.0.0.1:7890"
Environment="HTTPS_PROXY=http://127.0.0.1:7890"
Environment="NO_PROXY=localhost,127.0.0.1,.internal"
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

二、镜像(Image)操作

2.1 镜像命名规则

[仓库地址[:端口]/]用户名或命名空间/镜像名[:标签]

例如:

  • nginx:1.25 → 省略仓库地址(默认 Docker Hub)、省略命名空间(默认 library,即官方镜像)
  • myname/myapp:v1 → Docker Hub 上某用户的镜像
  • registry.cn-hangzhou.aliyuncs.com/myspace/myapp:latest → 阿里云私有仓库中的镜像

不写标签时默认使用 latest,生产环境强烈建议显式指定版本号,避免镜像悄悄升级导致行为不一致。

2.2 搜索与拉取镜像

docker search nginx              # 在 Docker Hub 搜索镜像
docker pull nginx                # 拉取 latest 标签
docker pull nginx:1.25           # 拉取指定标签
docker pull nginx:1.25 --platform linux/amd64   # 指定架构(多架构镜像场景)

2.3 查看本地镜像

docker images                    # 等价于 docker image ls
docker images -a                 # 包含中间层镜像
docker image inspect nginx       # 查看镜像详细元数据(JSON)
docker history nginx             # 查看镜像的分层构建历史

2.4 删除镜像

docker rmi nginx:1.25                  # 删除指定镜像
docker rmi -f nginx:1.25               # 强制删除(即使有容器引用,谨慎使用)
docker rmi $(docker images -q)         # 删除本机全部镜像
docker image prune                     # 清理"悬空镜像"(<none>标签,通常是构建产生的中间产物)
docker image prune -a                  # 清理所有未被任何容器使用的镜像(释放空间效果更明显)

2.5 镜像标签(tag)

docker tag nginx:latest myrepo/nginx:v1

tag 只是给同一个镜像 ID 打上新的引用名,不会产生新的数据,常用于推送前重命名为仓库要求的格式。

2.6 镜像的导入导出(离线迁移)

# 导出镜像为 tar 包(保留镜像分层、历史、元数据)
docker save -o nginx.tar nginx:latest
docker save nginx:latest | gzip > nginx.tar.gz
 
# 导入镜像
docker load -i nginx.tar
docker load < nginx.tar.gz
# 导出容器文件系统快照为 tar(不含镜像历史/分层信息,体积通常更小)
docker export -o container.tar 容器ID或容器名
 
# 将容器快照导入为新镜像
docker import container.tar 新镜像名:标签

save/load 与 export/import 的区别

  • save/load 面向镜像,保留完整的分层结构与构建历史,适合镜像本身的备份迁移。
  • export/import 面向容器,只是把容器当前文件系统打平导出成一个单层,体积更小但丢失了历史信息,适合"把一个改动过的容器固化成新镜像"这种场景。

2.7 推送镜像到仓库

docker login                                          # 登录 Docker Hub
docker login registry.cn-hangzhou.aliyuncs.com        # 登录私有/云厂商仓库
 
docker tag myapp:latest registry.cn-hangzhou.aliyuncs.com/myspace/myapp:latest
docker push registry.cn-hangzhou.aliyuncs.com/myspace/myapp:latest

2.8 Dockerfile 详解

指令 作用
FROM 指定基础镜像,必须是第一条有效指令
WORKDIR 设置后续指令的工作目录(不存在会自动创建)
COPY 将构建上下文中的文件复制进镜像
ADD 类似 COPY,但额外支持自动解压 tar 包、直接写 URL(不推荐滥用,优先用 COPY)
RUN 构建阶段执行命令,每条 RUN 都会产生新的一层
CMD 容器启动时的默认命令,可以被 docker run 后面的参数整体覆盖
ENTRYPOINT 容器启动时执行的命令,不易被覆盖,常与 CMD 搭配作为默认参数
EXPOSE 声明容器监听的端口,仅起文档说明作用,不会自动做端口映射
ENV 设置环境变量,构建和运行阶段都生效
ARG 构建阶段参数,仅在 docker build 过程中有效,不会保留到最终容器
VOLUME 声明一个匿名卷挂载点
USER 指定后续指令及容器启动时使用的用户,避免默认以 root 运行
HEALTHCHECK 定义容器健康检查方式
LABEL 给镜像打元数据标签

CMD vs ENTRYPOINT

  • 只写 CMD ["a", "b"]:docker run image xxx 会用 xxx 整体替换 CMD。
  • 只写 ENTRYPOINT ["a", "b"]:docker run image xxx 中的 xxx 会作为参数追加在 ENTRYPOINT 后面。
  • 两者搭配 ENTRYPOINT ["python", "app.py"] + CMD ["--port", "8000"]:相当于给 ENTRYPOINT 提供了"默认参数",docker run 时可以只覆盖 CMD 部分。

示例:FastAPI 应用的 Dockerfile

FROM python:3.11-slim
 
WORKDIR /app
 
# 先只拷贝依赖清单,充分利用构建缓存:
# 只要 requirements.txt 不变,下面这层就不会重新执行 pip install
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
 
# 再拷贝其余代码(改动频率更高,放在后面)
COPY . .
 
EXPOSE 8000
 
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

示例:多阶段构建(以 Go 为例,最终镜像不含编译工具链,体积更小)

# ---- 构建阶段 ----
FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
 
# ---- 运行阶段 ----
FROM alpine:latest
COPY --from=builder /app/myapp /usr/local/bin/myapp
CMD ["myapp"]

.dockerignore 示例(与 .gitignore 语法类似,避免无关文件进入构建上下文,加快构建、减小体积):

__pycache__/
*.pyc
.git
.venv
.env
node_modules
*.log

2.9 构建镜像

docker build -t myapp:v1 .                          # 使用当前目录下的 Dockerfile 构建
docker build -t myapp:v1 -f Dockerfile.prod .        # 指定其他文件名的 Dockerfile
docker build --no-cache -t myapp:v1 .                # 不使用缓存,完全重新构建
docker build --build-arg VERSION=1.0 -t myapp:v1 .   # 传入 ARG 变量

三、容器(Container)操作

3.1 创建并运行容器

docker run [选项] 镜像名[:标签] [启动命令]

常用选项:

选项 说明
-d 后台运行(detached)
-it 交互式终端(-i 保持标准输入打开 + -t 分配伪终端)
--name 指定容器名称,便于后续引用
-p 主机端口:容器端口 端口映射
-v 主机路径:容器路径 挂载数据卷/目录
-e KEY=VALUE 设置环境变量
--env-file 从文件批量导入环境变量
--rm 容器停止后自动删除,适合临时性任务
--network 指定加入的网络
--restart 重启策略:no / on-failure / always / unless-stopped
--memory 内存使用上限
--cpus CPU 使用上限(核数)

示例:

# 后台启动一个 nginx,映射端口
docker run -d --name my_nginx -p 8080:80 nginx:1.25
 
# 启动 MySQL,挂载数据卷,设置开机自启
docker run -d \
  --name mysql8 \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -p 3306:3306 \
  -v mysql_data:/var/lib/mysql \
  --restart always \
  mysql:8.0
 
# 临时启动一个交互式容器,退出后自动清理
docker run -it --rm ubuntu:22.04 /bin/bash

3.2 查看容器

docker ps                    # 查看运行中的容器
docker ps -a                 # 查看所有容器(含已停止)
docker ps -q                 # 只输出容器 ID,常配合 xargs/$() 批量操作
docker inspect 容器名或ID     # 查看完整元数据(网络、挂载、环境变量等,JSON格式)
docker stats                 # 实时查看各容器 CPU/内存/网络占用
docker top 容器名             # 查看容器内正在运行的进程
docker port 容器名            # 查看该容器的端口映射情况

3.3 容器生命周期管理

docker start 容器名
docker stop 容器名                 # 优雅停止(先发SIGTERM,超时后SIGKILL)
docker restart 容器名
docker pause 容器名                # 暂停容器内所有进程
docker unpause 容器名
docker kill 容器名                 # 立即强制终止(SIGKILL)
 
docker rm 容器名                   # 删除已停止的容器
docker rm -f 容器名                # 强制删除(含运行中的容器)
docker rm $(docker ps -aq)         # 删除所有容器(⚠️慎用)

3.4 进入容器 / 执行命令

docker exec -it 容器名 /bin/bash    # 进入容器交互式 shell(容器需自带bash)
docker exec -it 容器名 sh           # 精简镜像(如alpine)通常没有bash,用sh
docker exec 容器名 ls /app          # 在容器内执行单条命令并返回结果
docker attach 容器名                # 直接连接到容器主进程的输入输出

exec vs attach exec 是在容器内新开一个进程执行命令,退出该 shell 不影响容器主进程,是日常调试的首选方式;attach接管容器主进程本身的标准输入输出,如果在里面按 Ctrl+C 或直接 exit,很可能导致容器主进程退出、容器随之停止,一般不推荐用于日常调试。

3.5 查看日志

docker logs 容器名
docker logs -f 容器名           # 实时滚动跟踪(follow)
docker logs --tail 100 容器名   # 只看最后100行
docker logs -t 容器名           # 附带时间戳
docker logs --since 30m 容器名  # 只看最近30分钟的日志

3.6 容器与主机间文件复制

docker cp 容器名:/app/config.yml ./config.yml     # 从容器复制到本机
docker cp ./config.yml 容器名:/app/config.yml      # 从本机复制到容器

3.7 容器资源限制

docker run -d --memory=512m --cpus=1.5 nginx     # 启动时限制
docker update --memory=1g 容器名                   # 对已运行容器动态调整
docker update --restart=always 容器名              # 动态修改重启策略

3.8 容器详情与元数据

docker inspect 容器名
docker inspect -f '{{.State.Status}}' 容器名        # 用 Go 模板只提取某个字段
docker inspect -f '{{.NetworkSettings.IPAddress}}' 容器名
docker diff 容器名                                  # 查看容器相对镜像新增/修改/删除了哪些文件

四、数据卷(Volume)操作

4.1 三种数据持久化方式对比

类型 说明 适用场景
Volume(数据卷) 由 Docker 管理,存储在宿主机 /var/lib/docker/volumes/ 下,与具体路径解耦 数据库等需要持久化且不关心具体存储位置的数据,官方推荐方式
Bind Mount(绑定挂载) 直接挂载宿主机上指定的任意路径 开发环境热更新代码、需要精确控制宿主机路径的配置文件
tmpfs 挂载到内存中,容器停止后数据即丢失 存放不希望落盘的敏感临时数据

4.2 数据卷命令

docker volume create my_volume
docker volume ls
docker volume inspect my_volume
docker volume rm my_volume
docker volume prune          # 清理所有未被任何容器使用的卷(⚠️会真正删除数据,谨慎执行)

4.3 挂载方式实践

# 具名卷(named volume)——推荐方式
docker run -d -v my_volume:/var/lib/mysql mysql:8.0
 
# 绑定挂载(bind mount)——指定宿主机绝对路径
docker run -d -v /home/user/mysql_data:/var/lib/mysql mysql:8.0
 
# 匿名卷(只写容器内路径,Docker自动生成一个随机名字的卷)
docker run -d -v /var/lib/mysql mysql:8.0
 
# --mount 语法:更明确、可读性更好,生产环境推荐
docker run -d --mount type=volume,source=my_volume,target=/var/lib/mysql mysql:8.0
docker run -d --mount type=bind,source=/home/user/mysql_data,target=/var/lib/mysql mysql:8.0
 
# 只读挂载(容器内无法修改该目录内容)
docker run -d -v my_volume:/var/lib/mysql:ro mysql:8.0

-v--mount 的区别 两者功能等价,但 -v 简写语法在绑定挂载指定的宿主机路径不存在时会自动帮你创建一个目录,容易掩盖路径写错的问题;--mount 语法更加显式、语义清晰,写错路径会直接报错,官方文档更推荐在生产环境使用 --mount

4.4 数据备份与恢复

# 借助一个临时容器,把数据卷打包备份到当前目录
docker run --rm -v my_volume:/data -v $(pwd):/backup alpine \
  tar czf /backup/backup.tar.gz -C /data .
 
# 恢复:反向解包回数据卷
docker run --rm -v my_volume:/data -v $(pwd):/backup alpine \
  tar xzf /backup/backup.tar.gz -C /data

针对 MySQL 容器的实用备份方式(逻辑备份,更常用):

# 导出
docker exec mysql8 mysqldump -u root -p密码 数据库名 > backup.sql
 
# 导入(注意用 -i 而非 -it,因为这里是标准输入重定向,不需要分配伪终端)
docker exec -i mysql8 mysql -u root -p密码 数据库名 < backup.sql

五、网络(Network)操作

5.1 网络驱动类型

驱动 说明
bridge 默认驱动,适合单机上多个容器间通信
host 容器直接共用宿主机网络栈,没有网络隔离,性能最好但端口容易冲突
none 容器没有网络接口,完全隔离
overlay 跨多台宿主机的容器通信(Docker Swarm 集群场景)
macvlan 让容器拥有独立的 MAC 地址,在网络上表现得像一台独立物理机

5.2 网络命令

docker network ls
docker network create my_network
docker network create --driver bridge --subnet 172.20.0.0/16 my_network
docker network inspect my_network
docker network rm my_network
docker network connect my_network 容器名        # 让已运行的容器加入某网络
docker network disconnect my_network 容器名
docker network prune                            # 清理未使用的网络

5.3 容器互联实践

docker network create app_net
 
docker run -d --name db --network app_net mysql:8.0
docker run -d --name web --network app_net -p 8000:8000 myapp

关键知识点自定义 bridge 网络中,Docker 内置了 DNS 解析,容器之间可以直接用容器名(或 compose 中的服务名)当作主机名互相访问,例如上面 web 容器内连接数据库时直接写 db:3306 即可,无需关心具体 IP。而默认的 bridge 网络(即不加 --network 参数时)不提供这种按名字互相发现的能力,这也是官方建议"始终为项目创建自定义网络"的原因。


六、Docker Compose 操作

6.1 什么是 Docker Compose

Compose 用一个 YAML 文件声明"这个项目由哪些服务(容器)组成、各自怎么配置、彼此如何连接",然后一条命令即可整体启动/停止/重建,是管理多容器项目(如 Web + 数据库 + 缓存)的标准工具。

V1 与 V2 的区别 旧版 docker-compose(带连字符,Python 编写的独立二进制)已停止官方维护;现在的标准是 Compose V2,以 docker compose(空格,无连字符)的形式作为 Docker CLI 的内置插件存在。日常使用优先使用 docker compose,两者命令用法基本一致。

6.2 compose.yaml 基本结构

services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DEBUG=1
    volumes:
      - ./app:/app
    depends_on:
      - db
    networks:
      - app_net
 
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
      MYSQL_DATABASE: mydb
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - app_net
    restart: always
 
volumes:
  mysql_data:
 
networks:
  app_net:

关于顶部的 version 字段 早期 Compose 文件习惯在开头写 version: "3.9",在 Compose V2 中该字段已被标记为过时(写了也只会提示警告,不影响运行),可以直接省略不写

6.3 常用命令

命令 说明
docker compose up -d 后台创建并启动所有服务
docker compose up -d --build 启动前强制重新构建镜像
docker compose down 停止并删除容器、网络(不删数据卷)
docker compose down -v 同时删除数据卷(⚠️数据会丢失)
docker compose ps 查看本项目各服务状态
docker compose logs -f 查看全部服务的实时日志
docker compose logs -f web 只看某个服务的日志
docker compose exec web bash 进入指定服务的容器
docker compose restart web 重启指定服务
docker compose build 仅构建镜像,不启动
docker compose stop / start 停止/启动(保留容器,不删除)
docker compose config 校验并打印最终解析后的完整配置
docker compose pull 拉取各服务声明的镜像
docker compose watch 监听本地文件变化,自动同步/重建(适合开发调试)

6.4 环境变量管理

.env 文件(与 compose.yaml 同目录时会被自动读取):

DB_PASSWORD=your_secure_password
APP_PORT=8000

compose.yaml 中通过 ${DB_PASSWORD} 引用;也可以用 env_file 显式指定某个服务加载哪个环境变量文件:

services:
  web:
    env_file:
      - .env

6.5 实战示例:FastAPI + MySQL + Redis

结合常见的 FastAPI + SQLAlchemy + MySQL 技术栈,一个相对完整、贴近生产实践的示例:

flowchart TD
    USER(("用户 / 浏览器")) -->|"8000端口"| APP
    subgraph net["Docker 网络: app_net"]
        APP["app 容器<br/>FastAPI :8000"]
        DB["db 容器<br/>MySQL :3306"]
        REDIS["redis 容器<br/>Redis :6379"]
        APP --> DB
        APP --> REDIS
    end
    DB -.-> VOL[("mysql_data 数据卷")]
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: app
    ports:
      - "8000:8000"
    env_file:
      - .env
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - ./app:/app
    networks:
      - app_net
    restart: unless-stopped
 
  db:
    image: mysql:8.0
    container_name: mysql_db
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - app_net
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
 
  redis:
    image: redis:7-alpine
    container_name: redis_cache
    networks:
      - app_net
    restart: unless-stopped
 
volumes:
  mysql_data:
 
networks:
  app_net:
    driver: bridge

一个常见的坑:depends_on 并不等于"等数据库就绪" 简单写 depends_on: [db] 只能保证 db 容器已经启动,并不代表 MySQL 内部已经初始化完毕、可以接受连接——这两者之间通常有几秒延迟。如果 app 容器启动时立刻尝试连库(比如执行 Alembic 迁移),很可能因为库还没准备好而报错。解决办法是像上面的例子一样,给 db 配置 healthcheck,并在 depends_on 中使用 condition: service_healthy 这种长格式写法,让 Compose 真正等到数据库"健康检查通过"后再启动依赖它的服务。

配合 Alembic 做数据库迁移的启动脚本(entrypoint.sh):

#!/bin/bash
set -e
alembic upgrade head
exec uvicorn main:app --host 0.0.0.0 --port 8000

对应 Dockerfile 中:

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

这样每次容器启动时都会先执行一次 alembic upgrade head 保证数据库结构是最新的,再启动应用本身,是 FastAPI + Alembic 项目部署中很常见的模式。

6.6 Compose 进阶技巧

# 叠加多个 compose 文件(常用于区分开发/生产环境配置)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
 
# 水平扩容某个服务(需注意端口冲突,通常要配合负载均衡使用)
docker compose up -d --scale web=3

七、最佳实践与常见问题

7.1 清理命令(释放磁盘空间)

docker system df                       # 查看 Docker 各类资源占用的磁盘空间
docker system prune                    # 清理:已停止的容器 + 未使用的网络 + 悬空镜像 + 构建缓存
docker system prune -a                 # 连同"未被任何容器使用的镜像"一起清理(会更彻底)
docker system prune -a --volumes       # ⚠️连同未使用的数据卷一起清理,会真正丢失数据,极其谨慎使用
 
docker container prune                 # 只清理已停止的容器
docker image prune -a                  # 只清理无用镜像
docker volume prune                    # 只清理无用数据卷
docker network prune                   # 只清理无用网络
docker builder prune                   # 清理构建缓存

7.2 常见问题排查

现象 排查思路
容器启动后立刻退出 docker logs 容器名 看报错;很多是 CMD/ENTRYPOINT 执行的进程本身崩溃退出了
端口冲突,启动报错 port is already allocated sudo lsof -i:端口netstat -tulnp | grep 端口 查是谁占用了
磁盘空间不足 docker system df 看占用分布,再用对应的 prune 命令清理
挂载数据卷后容器内报权限错误 通常是容器内进程用户与宿主机文件属主/权限不一致,检查 USER 指令或宿主机目录权限
容器内无法解析域名 检查网络配置,必要时手动指定 DNS:docker run --dns 8.8.8.8 ...
镜像拉取超时/失败 检查是否需要配置镜像加速器(见 1.5 国内镜像加速配置)
Compose 项目里服务之间连不通 确认两个服务在同一个 networks 下,并且用服务名而不是 localhost 互相访问

7.3 安全建议

  • 容器内进程尽量不用 root 运行,通过 Dockerfile 的 USER 指令指定非特权用户
  • 不要把密码、密钥等敏感信息直接硬编码进 Dockerfile 或镜像里,用环境变量 / .env / secrets 管理
  • 定期更新基础镜像版本,修复已知漏洞;可以考虑 docker scout 一类的工具做镜像扫描
  • 优先使用官方镜像或经过审计的可信镜像源
  • 通过 --memory--cpus 等参数限制容器资源占用,防止单个容器耗尽宿主机资源
  • 不要把宿主机的 /var/run/docker.sock 挂载进不受信任的容器,拿到这个 socket 基本等于拿到宿主机 root 权限(容器逃逸风险)

7.4 Dockerfile 最佳实践

  • 用多阶段构建(multi-stage build)剔除编译工具链等构建期依赖,减小最终镜像体积
  • 尽量合并 RUN 指令(用 && 连接),减少镜像层数
  • 善用 .dockerignore,避免 .gitnode_modules、虚拟环境等无关内容进入构建上下文
  • 优先选择体积更小的基础镜像(如 alpineslim 系列)
  • 生产环境固定具体版本号,不使用 latest 标签,保证可复现
  • 把不常变化的指令(如安装依赖)放在前面,把常变化的指令(如拷贝业务代码)放在后面,最大化利用构建缓存

八、命令速查表

分类 命令 说明
镜像 docker pull <image> 拉取镜像
镜像 docker images 查看本地镜像
镜像 docker rmi <image> 删除镜像
镜像 docker build -t <name> . 构建镜像
镜像 docker tag <src> <dst> 打标签
镜像 docker save/load 导出/导入镜像
镜像 docker push/login 推送/登录仓库
容器 docker run -d -p -v --name <image> 创建并运行容器
容器 docker ps -a 查看所有容器
容器 docker start/stop/restart <c> 启停容器
容器 docker rm -f <c> 删除容器
容器 docker exec -it <c> bash 进入容器
容器 docker logs -f <c> 查看日志
容器 docker cp <src> <dst> 复制文件
容器 docker inspect <c> 查看详情
容器 docker stats 实时资源占用
数据卷 docker volume create/ls/rm 管理数据卷
数据卷 -v vol:/path--mount 挂载数据卷
网络 docker network create/ls/rm 管理网络
网络 docker network connect 容器加入网络
Compose docker compose up -d 启动全部服务
Compose docker compose down [-v] 停止并清理
Compose docker compose logs -f [服务名] 查看日志
Compose docker compose exec <服务> bash 进入某服务容器
清理 docker system prune [-a] [--volumes] 清理无用资源
清理 docker system df 查看空间占用

备注 国内镜像加速部分的具体第三方地址时效性较强,使用前请自行验证可用性;云厂商个人专属加速地址(阿里云/腾讯云等)通常是相对最稳定的选择。