核心定义:Dockerfile 不仅仅是安装脚本,它是对 UnionFS(联合文件系统) 的分层描述。每一条指令(Instruction)都在只读层(Read-only Layer)之上叠加新的变更。
核心机制:分层与缓存 (Layers & Cache)
- 机制:Docker Daemon 会缓存每一行指令构建出的 Layer。如果
instruction和context file没有变化,直接复用缓存。 - 优化策略:将变更最频繁的指令放在最后。
- ❌ 错误:先 COPY 代码,再安装依赖(代码一改,依赖缓存失效,每次都要重新 pip install)。
- ✅ 正确:先 COPY 依赖描述文件(package.json/requirements.txt),安装依赖,最后 COPY 源代码。
# 缓存命中率极高
COPY requirements.txt .
RUN pip install -r requirements.txt
# 经常变化,放在最后
COPY src/ .关键指令解析
FROM: 选择基座
- 安全视角:基座决定了攻击面(Attack Surface)的大小。
- 选择优先级:
distroless(Google无Shell镜像) >alpine(musl libc) >slim(精简Debian) >latest(完整OS)。 - 注意:Alpine 使用 musl libc,可能导致 C 扩展库(如 numpy/pandas)编译问题,需权衡构建成本。
RUN: 执行与层合并
- 原子化:每一个
RUN都会生成新的一层。 - 清理原则:在同一个
RUN指令中安装包并清理缓存,防止临时文件被持久化到中间层。# 推荐写法:使用 && 连接,并清理 apt 缓存 RUN apt-get update && apt-get install -y \ gcc \ && rm -rf /var/lib/apt/lists/*
CMD vs ENTRYPOINT: PID 1 的控制权
- 容器即进程:容器内的 PID 1 进程负责接收内核信号(SIGTERM/SIGINT)。
- Shell 模式 vs Exec 模式:
CMD "python app.py"(Shell模式): PID 1 是/bin/sh,应用是子进程,收不到停止信号,导致无法优雅退出。CMD ["python", "app.py"](Exec模式): 推荐。PID 1 直接是 python 进程,能处理信号。
- 组合拳:用
ENTRYPOINT定义“不可变”的启动命令,用CMD定义“可覆盖”的默认参数。
多阶段构建 (Multi-stage Build)
解决 “构建环境” vs “运行环境” 的矛盾。
- 场景:Go/C++/Java/Python(带C扩展) 编译。
- 原理:利用
AS别名,在一个 Dockerfile 中定义多个流,最后只保留 Runtime 必需的文件。 - 价值:
- 极度精简:产物从 1GB → 50MB。
- 安全:生产环境不包含编译器(GCC/Make)、源码和调试工具,黑客进入容器后难以利用。
# Stage 1: Builder (包含 GCC, Headers 等)
FROM python:3.9-slim AS builder
WORKDIR /app
COPY requirements.txt .
# 编译并安装到用户目录
RUN pip install --user -r requirements.txt
# Stage 2: Runtime (只有 Python 解释器)
FROM python:3.9-slim
WORKDIR /app
# 仅复制 Stage 1 的构建产物(Python包)
COPY --from=builder /root/.local /root/.local
COPY app.py .
# 确保 path 包含用户目录
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]安全加固 CheckList
- 非 Root 运行:永远不要以 Root 身份运行应用。
RUN useradd -m appuser USER appuser - 敏感信息:严禁在 Dockerfile 中使用
ENV存储密码/Key(docker history可见)。应在运行时通过-e或 Volume 挂载传入。 - .dockerignore:必须配置。防止
.git、__pycache__、.env等敏感或无用文件被打入 Context,导致镜像膨胀或泄露。