Skip to content

Docker 容器化

Docker 将应用及其依赖打包成镜像(image),运行时实例化为容器(container),实现”一次构建,到处运行”。对 AI 工程而言,Docker 解决了”在我机器上能跑”的经典难题——CUDA 版本、驱动、Python 库版本差异都能封装在镜像内。

本页涵盖镜像分层、Dockerfile 指令、多阶段构建、GPU 容器、网络、数据卷、镜像仓库和安全实践。多容器编排见 Docker Compose 编排。

Docker 镜像由多个**只读层(layer)**组成,每条 Dockerfile 指令生成一层。容器运行时在最上层叠加一个可写层。

💡 分层缓存:如果某层指令没变,Docker 构建时直接复用缓存。因此 Dockerfile 中应把变化频率低的指令放前面(如安装系统依赖),把频繁变化的放后面(如 COPY 源码),以加速构建。

指令作用示例
FROM指定基础镜像FROM nvidia/cuda:12.1.0-runtime-ubuntu22.04
WORKDIR设置工作目录WORKDIR /app
COPY复制本地文件到镜像COPY requirements.txt .
RUN构建时执行命令(生成新层)RUN pip install -r requirements.txt
ENV设置环境变量ENV PYTHONUNBUFFERED=1
EXPOSE声明容器端口EXPOSE 8000
CMD容器默认启动命令(可被覆盖)CMD ["python", "server.py"]
ENTRYPOINT固定入口命令(不易覆盖)ENTRYPOINT ["python"]
ARG构建时变量(仅构建阶段可用)ARG PY_VERSION=3.11
HEALTHCHECK容器健康检查HEALTHCHECK CMD curl -f http://localhost/
USER指定运行用户USER appuser
# COPY:纯复制(推荐,行为可预测)
COPY requirements.txt .
COPY ./src /app/src
# ADD:额外支持自动解压 tar、远程 URL(不推荐,行为隐晦)
ADD archive.tar.gz /opt/ # 自动解压
ADD https://example.com/file.txt /tmp/ # 下载远程文件

💡 优先用 COPY,只有需要自动解压本地 tar 包时才考虑 ADD。

# 方式 1:CMD 可被 docker run 后的参数覆盖
CMD ["python", "server.py"]
# docker run myimage python debug.py ← 覆盖了 CMD
# 方式 2:ENTRYPOINT + CMD 组合(推荐)
ENTRYPOINT ["python"]
CMD ["server.py"]
# docker run myimage ← 执行 python server.py
# docker run myimage debug.py ← 执行 python debug.py

多阶段构建用一个 Dockerfile 中的多个 FROM 阶段,把编译环境和运行环境分离,大幅减小最终镜像体积。

# ===== 阶段 1:构建阶段(安装编译依赖、编译产物)=====
FROM python:3.11-slim AS builder
WORKDIR /build
COPY requirements.txt .
# 安装到指定目录,便于后续拷贝
RUN pip install --no-cache-dir --target=/build/libs -r requirements.txt
# ===== 阶段 2:运行阶段(只保留运行所需文件)=====
FROM python:3.11-slim AS runtime
WORKDIR /app
# 从构建阶段拷贝已安装的库
COPY --from=builder /build/libs /usr/local/lib/python3.11/site-packages/
# 拷贝应用源码
COPY ./src /app/src
ENV PYTHONPATH=/app/src
EXPOSE 8000
# 非 root 用户运行(安全最佳实践)
RUN useradd -m -u 1000 appuser
USER appuser
CMD ["python", "-m", "src.server"]

💡 多阶段构建可以把镜像从 2GB+ 压缩到 300MB 以下,对 CI 推送、服务器拉取速度提升明显。

Docker 18.09+ 的 BuildKit 支持 RUN --mount=type=cache,在多次构建间共享包管理器缓存:

# syntax=docker/dockerfile:1
FROM python:3.11-slim
# pip 缓存跨构建复用(不写入镜像层)
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
# apt 缓存同理
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get install -y --no-install-recommends \
curl git && rm -rf /var/lib/apt/lists/*

以下是一个完整的 GPU 推理服务 Dockerfile,基于 NVIDIA 官方 CUDA 镜像:

# 基于 CUDA 12.1 + cuDNN 的运行时镜像(比 devel 镜像小很多)
FROM nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04
# 设置时区、禁止交互式安装
ENV DEBIAN_FRONTEND=noninteractive \
TZ=Asia/Shanghai \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1
# 安装系统依赖(Python 3.11 + 基础工具)
RUN apt-get update && apt-get install -y --no-install-recommends \
python3.11 python3-pip python3.11-venv \
curl ca-certificates \
&& rm -rf /var/lib/apt/lists/* \
&& ln -sf /usr/bin/python3.11 /usr/bin/python
WORKDIR /app
# 先拷贝依赖文件(利用 layer cache)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 再拷贝源码(源码变化频繁,放后面)
COPY ./src /app/src
COPY ./models /app/models
# 声明端口和启动命令
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
CMD ["python", "-m", "src.server", "--host", "0.0.0.0", "--port", "8000"]

构建与运行:

Terminal window
# 构建镜像(-t 命名,. 表示 Dockerfile 所在目录)
docker build -t my-inference:latest .
# 启动 GPU 容器(--gpus all 挂载 GPU)
docker run --gpus all -p 8000:8000 -v /data:/data my-inference:latest
# 进入运行中的容器调试
docker exec -it <container_id> bash

GPU 容器依赖 NVIDIA Container Toolkit(原名 nvidia-docker2),它让容器能访问宿主机的 GPU 驱动:

Terminal window
# Ubuntu 安装
sudo apt-get install -y nvidia-container-toolkit
sudo systemctl restart docker
# 验证 GPU 可用
docker run --rm --gpus all nvidia/cuda:12.1.1-base-ubuntu22.04 nvidia-smi
Terminal window
# 挂载所有 GPU
docker run --gpus all my-inference
# 挂载指定 GPU(第 0、1 块)
docker run --gpus '"device=0,1"' my-inference
# 挂载 2 块 GPU(自动分配)
docker run --gpus 2 my-inference
变体大小说明
base~1GB最小,只有 CUDA 运行时
runtime~2GBbase + cuDNN,推荐用于推理
devel~4GBruntime + nvcc 编译器,用于需要编译 CUDA 扩展的场景
# 推理服务用 runtime(体积小)
FROM nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04
# 需要编译 flash-attention 等扩展时用 devel
FROM nvidia/cuda:12.1.1-cudnn8-devel-ubuntu22.04

💡 宿主机只需安装 NVIDIA 驱动(如 535.x),不需要安装完整 CUDA Toolkit。CUDA 库由镜像提供,容器和宿主机通过驱动通信。

容器默认通过 Docker 创建的虚拟网络通信。理解网络模式是排查容器间连接问题的关键。

驱动说明典型场景
bridge(默认)虚拟网桥,容器间隔离,通过端口映射访问宿主机单机多容器
host容器直接使用宿主机网络(无隔离)对网络性能要求高的场景
none无网络安全隔离、离线计算
overlay跨主机的虚拟网络Docker Swarm / 多主机
Terminal window
# 创建自定义网络(容器间通过容器名互相访问)
docker network create mynet
# 在自定义网络中启动容器
docker run -d --name api --network mynet my-api
docker run -d --name db --network mynet postgres:16
# api 容器内可以用 "db" 作为主机名连接数据库
# psql -h db -U postgres
# 查看网络详情
docker network inspect mynet
# 端口映射(宿主机:容器)
docker run -p 8000:8000 my-inference # 宿主机 8000 → 容器 8000
docker run -p 127.0.0.1:8000:8000 ... # 只绑定 localhost(安全)

容器文件系统是临时的,容器删除后数据丢失。数据卷(volume)和绑定挂载(bind mount)用于持久化数据。

Terminal window
# 命名卷(Docker 管理,推荐生产使用)
docker run -v model_data:/app/models my-inference
# 数据存在 Docker 管理的区域,容器删除后卷保留
# 绑定挂载(指定宿主机路径,适合开发)
docker run -v /home/alice/code:/app my-inference
# 宿主机目录直接映射到容器,修改实时同步
# 只读挂载(防止容器修改数据)
docker run -v /data/datasets:/data:ro my-inference
# 推荐的 --mount 语法(更清晰)
docker run --mount type=volume,src=model_data,dst=/app/models my-inference
docker run --mount type=bind,src=/home/alice/code,dst=/app my-inference
Terminal window
docker volume ls # 列出所有卷
docker volume inspect model_data # 查看卷详情
docker volume rm model_data # 删除卷(需先移除使用它的容器)
docker volume prune # 清理未使用的卷

⚠️ 绑定挂载的文件权限问题:容器内进程的 UID/GID 与宿主机不一致时,可能无法读写挂载目录。在 Dockerfile 中创建固定 UID 的用户,或用 --user $(id -u):$(id -g) 指定。

Terminal window
# 登录 Docker Hub
docker login
# 给镜像打标签(用户名/镜像名:标签)
docker tag my-inference:latest alice/my-inference:v1.0
# 推送
docker push alice/my-inference:v1.0
# 拉取
docker pull alice/my-inference:v1.0
Terminal window
# 阿里云 ACR、AWS ECR、Harbor 等私有仓库
docker login registry.cn-hangzhou.aliyuncs.com -u <user> -p <password>
docker tag my-inference:latest registry.cn-hangzhou.aliyuncs.com/myorg/my-inference:v1.0
docker push registry.cn-hangzhou.aliyuncs.com/myorg/my-inference:v1.0
Terminal window
# 导出为 tar 文件(无网络环境下传输)
docker save my-inference:latest -o my-inference.tar
# 在目标机器导入
docker load -i my-inference.tar
操作命令
列出镜像docker images
删除镜像docker rmi <image>
清理悬空镜像docker image prune
列出容器docker ps(运行中)/ docker ps -a(全部)
查看容器日志docker logs -f <container>
进入容器docker exec -it <container> bash
查看容器资源占用docker stats
查看容器详情docker inspect <container>
拷贝文件docker cp <container>:/app/log.txt .
查看镜像层docker history <image>
  1. 用非 root 用户运行:在 Dockerfile 末尾创建用户并 USER appuser,避免容器内进程拥有 root 权限。

  2. 使用 .dockerignore:排除不必要的文件,防止密钥、.git 目录被打包进镜像。

.dockerignore
.git
.gitignore
.venv/
__pycache__/
*.pyc
.env
*.md
tests/
  1. 使用固定版本标签:FROM python:3.11-slim 而非 python:latest;python:3.11.9-slim-bookworm 更精确。

  2. 合并 RUN 指令:减少镜像层数,每条 RUN 尽量合并多个命令。

# 好:一条 RUN,最后清理缓存
RUN apt-get update && apt-get install -y --no-install-recommends \
curl git vim \
&& rm -rf /var/lib/apt/lists/*
# 差:三条 RUN,产生三个层,中间层仍包含缓存
RUN apt-get update
RUN apt-get install -y curl git vim
RUN rm -rf /var/lib/apt/lists/* # 删了也没用,中间层还在
  1. 敏感信息用 secrets:不要在 Dockerfile 中硬编码密钥,使用 --secret 或构建参数 + 环境变量。
Terminal window
# BuildKit secret(不写入镜像层)
docker build --secret id=api_key,src=./api_key.txt .
# Dockerfile 中引用
# RUN --mount=type=secret,id=api_key \
# pip install --index-url $(cat /run/secrets/api_key) private-pkg
  1. 扫描镜像漏洞:用 docker scout(官方)或 Trivy 扫描镜像中的已知漏洞。
Terminal window
docker scout cves my-inference:latest
  1. Dockerfile 分层优化:把 COPY requirements.txt + RUN pip install 放在 COPY src 之前。源码频繁改动,放后面可以利用缓存跳过耗时的 pip 安装。
  2. 镜像尽量用 -slim 或 -alpine 变体,再用多阶段构建剥离编译工具。生产镜像不需要 gcc、构建头文件。
  3. GPU 容器要用 --gpus all 或 compose 的 deploy.resources 声明,否则容器内看不到 GPU。
  4. 敏感信息用环境变量或 secrets 管理,不要写死在 Dockerfile 或 compose 文件里。Docker Compose 支持 secrets 字段或 .env 文件。
  5. 用 .dockerignore 减小构建上下文,加速构建并避免泄露敏感文件。
术语英文解释
容器container镜像的运行实例,隔离的进程环境
镜像image容器的只读模板,由多层组成
多阶段构建multi-stage build用多个 FROM 指令分离构建和运行环境,减小镜像体积
层缓存layer cacheDocker 构建时复用未变化层,加速重复构建
命名卷named volumeDocker 管理的持久化存储,独立于容器生命周期
绑定挂载bind mount将宿主机目录直接映射到容器的挂载方式
健康检查healthcheck容器内定期执行的检查命令,判断服务是否正常