强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

01 - Dockerfile 概述

01 - Dockerfile 概述:构建上下文与层缓存机制

1.1 什么是 Dockerfile

Dockerfile 是一个纯文本文件,包含一系列指令(Instruction),Docker 引擎按顺序执行这些指令来构建一个镜像(Image)。每条指令对应镜像的一层(Layer),层与层之间叠加形成最终的文件系统。

# 一个最简单的 Dockerfile
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y curl
COPY app.sh /app/app.sh
CMD ["/app/app.sh"]

Dockerfile 与镜像、容器的关系

Dockerfile  ──(docker build)──▶  Image  ──(docker run)──▶  Container
    源码                         只读模板                   运行实例
概念说明
Dockerfile构建描述文件,版本可控
Image(镜像)只读的分层文件系统,包含运行应用所需的一切
Container(容器)镜像的运行实例,在镜像层之上添加可写层

1.2 构建上下文(Build Context)

当你运行 docker build . 时,Docker 会将命令末尾指定的路径(这里是 .)打包发送给 Docker 引擎,这个路径就是构建上下文

构建上下文的工作流程

# 用户执行
docker build -t myapp:1.0 .
┌─────────────────┐     发送 tar 归档     ┌──────────────────┐
│  客户端 (CLI)    │  ──────────────────▶  │  Docker 引擎     │
│                 │                        │                  │
│  构建上下文目录  │     按层构建镜像       │  执行 Dockerfile  │
│  (含 .dockerignore) │  ◀──────────────── │  生成镜像层       │
└─────────────────┘                        └──────────────────┘

关键点:构建上下文中的文件才会被 COPYADD 指令访问。Dockerfile 本身的位置不一定在构建上下文内(可以通过 -f 参数指定)。

.dockerignore 文件

类似于 .gitignore,用于排除不需要发送到构建上下文的文件:

# .dockerignore 示例
.git
.gitignore
*.md
node_modules
.env
.DS_Store
__pycache__
*.pyc
docker-compose*.yml

.dockerignore 的好处

益处说明
减少上下文体积避免发送不必要的文件,加速构建
防止敏感文件泄露.env、密钥文件不会进入镜像
避免缓存失效无关文件变动不会影响构建缓存

构建上下文大小检查

# 查看构建上下文大小(构建时会显示)
docker build -t myapp .
# 输出: Sending build context to Docker daemon  2.048MB

# 查看当前目录大小
du -sh --exclude=.git .

注意事项:构建上下文过大(几百 MB 甚至 GB)会严重拖慢构建速度。始终配合 .dockerignore 控制上下文大小。

1.3 层缓存机制(Layer Cache)

Docker 构建镜像时,每条指令都会生成一个层(Layer)。Docker 会缓存这些层,如果某一层及其所有祖先层都没有变化,就会直接使用缓存,跳过执行。

缓存命中的判断逻辑

┌──────────────────────────────────────────┐
│ FROM ubuntu:22.04                        │ ← 检查基础镜像是否有更新
├──────────────────────────────────────────┤
│ RUN apt-get update && apt-get install -y │ ← 检查指令文本是否变化
├──────────────────────────────────────────┤
│ COPY package.json /app/                  │ ← 检查文件内容(校验和)是否变化
├──────────────────────────────────────────┤
│ RUN npm install                          │ ← 上层缓存失效则此层也失效
├──────────────────────────────────────────┤
│ COPY . /app/                             │ ← 任何文件变化都会使此层失效
└──────────────────────────────────────────┘

缓存失效规则

指令何时缓存失效
FROM基础镜像被更新(digest 变化)
RUN指令字符串完全一致(注意:不会检查命令的实际输出)
COPY / ADD被复制文件的内容(checksum)发生变化
ENV / LABEL指令文本变化

利用缓存优化构建顺序

一个典型的 Node.js 应用,错误的写法:

FROM node:20-alpine
WORKDIR /app
COPY . .                   # 任何文件变化都会使缓存失效
RUN npm install            # 每次都重新安装依赖
CMD ["node", "server.js"]

正确的写法(先复制依赖清单,后复制源码):

FROM node:20-alpine
WORKDIR /app

# 第一步:仅复制依赖清单
COPY package.json package-lock.json ./

# 第二步:安装依赖(只要依赖清单不变,此层就命中缓存)
RUN npm ci --production

# 第三步:复制源码
COPY . .

CMD ["node", "server.js"]

效果对比

场景错误写法正确写法
仅修改 server.js重新执行 npm install(耗时 30s+)直接命中缓存(< 1s)
修改 package.json重新执行 npm install重新执行 npm install

–no-cache 参数

# 完全忽略缓存,从头构建
docker build --no-cache -t myapp .

# 从指定阶段开始不使用缓存
docker build --no-cache-filter stage_name -t myapp .

业务场景:在 CI/CD 流水线中,每日构建(nightly build)建议使用 --no-cache 确保获取最新的安全补丁;而日常开发构建则充分利用缓存加速迭代。

1.4 Dockerfile 基本结构

一个完整的 Dockerfile 通常遵循以下结构:

# ===== 1. 基础镜像 =====
FROM python:3.12-slim

# ===== 2. 元数据 =====
LABEL maintainer="[email protected]"
LABEL version="1.0"

# ===== 3. 环境变量 =====
ENV PYTHONUNBUFFERED=1

# ===== 4. 系统依赖 =====
RUN apt-get update && \
    apt-get install -y --no-install-recommends libpq-dev && \
    rm -rf /var/lib/apt/lists/*

# ===== 5. 工作目录 =====
WORKDIR /app

# ===== 6. 依赖安装(利用缓存) =====
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# ===== 7. 复制源码 =====
COPY . .

# ===== 8. 运行配置 =====
EXPOSE 8000
USER appuser
CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000"]

指令执行顺序的原则

  1. 变化频率低的指令放前面(如安装系统依赖)
  2. 变化频率高的指令放后面(如复制业务源码)
  3. 利用缓存减少重复构建时间

1.5 构建参数与调试

常用构建命令

# 基本构建
docker build -t myapp:latest .

# 指定 Dockerfile 路径
docker build -t myapp -f docker/Dockerfile.prod .

# 多平台构建
docker buildx build --platform linux/amd64,linux/arm64 -t myapp .

# 构建时传入参数
docker build --build-arg NODE_ENV=production -t myapp .

# 查看构建过程(不使用缓存时可看到每层的执行输出)
docker build --progress=plain -t myapp .

调试构建失败

# 方法一:在失败层的前一层启动容器手动调试
docker build -t myapp-debug --target build_stage .

# 方法二:使用 BuildKit 的交互式调试(需 BuildKit 0.12+)
# 在 Dockerfile 中添加:
RUN --mount=type=bind,from=debugger,target=/debugger ...

1.6 扩展阅读


下一章02 - 基础镜像选择 — Alpine、Distroless、Scratch 与多架构镜像的选型指南。