Docker 多阶段构建实战:Python 应用镜像瘦身与安全加固清单
一个 Python Web 应用的 Dockerfile 如果写成单阶段,常常会把编译工具、测试依赖和缓存一起打进最终镜像:体积大、启动慢、攻击面也大。多阶段构建的做法是把“构建”和“运行”分开,最终镜像只带运行时必需的东西。
1. 先看一个反模式
单阶段写法通常是:FROM python:3.12,然后 apt-get install gcc、pip install -r requirements.txt,代码、.git、测试文件全在里面。这样做的问题有三点:第一,构建工具在生产镜像里无用却占体积;第二,依赖安装顺序不合理会导致缓存频繁失效;第三,以 root 运行、且把源码和密钥文件混在一起,风险更高。
2. 多阶段 Dockerfile 示例
# 构建阶段:只负责装依赖、生成 wheel
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# 运行阶段:只复制安装结果和业务代码
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /install /usr/local
COPY app/ ./app/
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
CMD ["python", "-m", "app.main"]
关键点:构建阶段用 --prefix 把依赖装到独立目录,运行阶段用 COPY --from=builder 精确复制;不要 COPY . 把整个工作区带进来,配合 .dockerignore 排除 .git、pycache、tests 和本地配置文件。
3. 让缓存真正生效
Docker 按层缓存,顺序很重要:先复制 requirements.txt 再 pip install,依赖不变时这一层可以直接复用;业务代码经常变,要放在后面复制。如果把 COPY . 放在 pip install 之前,改一行代码就会导致依赖层重建。需要区分开发依赖和生产依赖时,用 requirements-prod.txt 单独维护生产清单,测试、lint 工具不要进生产镜像。
4. 安全加固清单
- 基础镜像固定版本并优先用 slim,避免使用 latest 导致不可复现。
- 非 root 用户运行,必要时再配合只读文件系统和 --cap-drop=ALL 做进一步收紧。
- 不要在镜像里写 AK/SK、数据库密码等密钥,运行时通过环境变量或密钥管理服务注入。
- 定期用 docker scout 或 trivy 扫描基础镜像和依赖,及时升级有漏洞的版本。
- 健康检查不要只检查进程存在,应用应提供 /healthz,返回依赖关键组件的真实状态。
5. 验证效果的方法
构建完成后先用 docker images 对比前后体积,再用 docker history 查看每一层大小,确认构建工具层没有进入最终镜像;最后在干净环境 docker run --rm -p 8000:8000 跑一次,检查启动日志、端口和健康检查是否正常。体积数字会因依赖而异,重要的是“构建产物与运行环境分离”这个结构,而不是某个固定数值。
总结:多阶段构建不是为了炫技,而是把职责分清楚——构建阶段可以“重”,运行阶段必须“轻而干净”。按依赖先行、精确复制、非 root 运行、密钥外置这条清单落地,镜像自然会更小、更可复现、也更安全。
- 点赞
- 收藏
- 关注作者
评论(0)