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) │ ◀──────────────── │ 生成镜像层 │
└─────────────────┘ └──────────────────┘
关键点:构建上下文中的文件才会被 COPY 和 ADD 指令访问。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.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 扩展阅读
- Docker 官方文档 — Dockerfile reference
- Docker 官方文档 — Build context
- Best practices for writing Dockerfiles
- Docker BuildKit documentation
下一章:02 - 基础镜像选择 — Alpine、Distroless、Scratch 与多架构镜像的选型指南。