• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
showyoui
博客园    首页    新随笔    联系   管理    订阅  订阅

Dockerfile 详解

目录
  • Dockerfile 概念与用途
  • 1. 构建上下文(Build Context)
  • 2. 缓存机制(Build Cache)
    • 缓存机制实例详解
  • 3. 多阶段构建(Multi-Stage Build)
  • 4. 常用指令详解
  • 5. 编写 Dockerfile 的 10 条最佳实践
  • 6. 常见问题排查
  • 7. 参考链接

Dockerfile 概念与用途

Dockerfile 是用于描述 容器镜像 构建流程的声明式脚本文件,每条指令依次说明"从哪个基础镜像开始 → 复制/下载哪些文件 → 运行哪些命令 → 镜像启动时执行什么"。

主要优势:

  • 可移植:一次编写,任何支持 OCI 标准的容器运行时(Docker、Podman、containerd、CRI-O 等)都能运行生成的镜像
  • 可重复:构建步骤显式记录,便于在 CI/CD 中自动化执行
  • 可追溯:镜像的每一层都对应一条指令,排错与版本回退更直观

借助 Dockerfile,开发者可以把应用以及依赖(系统库、配置文件、启动命令)封装进不可变镜像,实现"像复制文件一样部署软件"。


1. 构建上下文(Build Context)

  • docker build <上下文路径> 中的路径即 构建上下文,其下所有文件都会被打包发送给 Docker Daemon。
  • 使用 .dockerignore 排除无关文件(如 node_modules/、*.log、.git 等),可显著减少传输体积与构建时间。
  • 也可以通过 docker buildx build https://github.com/your/repo.git#branch 直接使用远程 Git 仓库作为上下文。
# 示例:在独立目录中放置 Dockerfile
mkdir build && cp Dockerfile build/
cd build && docker build -t myimage:latest .

2. 缓存机制(Build Cache)

  • Docker 逐行解析 Dockerfile 并为每条指令创建镜像层。若 同一指令文本 + 同一上一层 ID 已构建过,Docker 将复用缓存。COPY/ADD 还会比较源文件校验和;在 BuildKit 模式下,ARG、ENV 等变化同样会触发失效。
  • 失效规则:某层失效后,其后的所有层均需重新构建。

缓存机制实例详解

以 Golang 应用为例,演示缓存机制的工作原理:

❌ 低效的写法(缓存命中率低):

FROM golang:1.21-alpine
WORKDIR /src
COPY . .                    # 第3步:复制所有文件
RUN go mod download        # 第4步:下载依赖
RUN go build -o app .      # 第5步:编译应用
CMD ["./app"]              # 第6步:启动命令

构建过程分析:

# 第一次构建
$ docker build -t myapp .
Step 1/6 : FROM golang:1.21-alpine
 ---> abc123def456 (缓存命中)
Step 2/6 : WORKDIR /src  
 ---> Running in xyz789
 ---> def456ghi789 (新层)
Step 3/6 : COPY . .
 ---> hij789klm012 (新层,复制所有文件)
Step 4/6 : RUN go mod download
 ---> Running in mno345pqr678
 ---> pqr678stu901 (新层,下载依赖,耗时45秒)
Step 5/6 : RUN go build -o app .
 ---> Running in stu901vwx234
 ---> vwx234yza567 (新层,编译应用,耗时30秒)
Step 6/6 : CMD ["./app"]
 ---> yza567bcd890 (新层)

# 修改源码后重新构建
$ echo 'fmt.Println("updated")' >> main.go
$ docker build -t myapp .
Step 1/6 : FROM golang:1.21-alpine
 ---> abc123def456 (缓存命中)
Step 2/6 : WORKDIR /src
 ---> def456ghi789 (缓存命中)
Step 3/6 : COPY . .
 ---> efg123hij456 (文件变化,缓存失效!)
Step 4/6 : RUN go mod download  
 ---> Running in hij456klm789
 ---> klm789nop012 (重新执行,又要45秒!)
Step 5/6 : RUN go build -o app .
 ---> Running in nop012pqr345
 ---> pqr345stu678 (重新执行,又要30秒!)
Step 6/6 : CMD ["./app"]
 ---> stu678vwx901 (重新执行)

✅ 高效的写法(缓存命中率高):

FROM golang:1.21-alpine
WORKDIR /src
COPY go.mod go.sum ./      # 第3步:只复制依赖文件
RUN go mod download        # 第4步:下载依赖
COPY . .                   # 第5步:复制源代码
RUN go build -o app .      # 第6步:编译应用
CMD ["./app"]              # 第7步:启动命令

优化后的构建过程:

# 第一次构建
$ docker build -t myapp .
Step 1/7 : FROM golang:1.21-alpine
 ---> abc123def456 (缓存命中)
Step 2/7 : WORKDIR /src
 ---> def456ghi789 (新层)
Step 3/7 : COPY go.mod go.sum ./
 ---> ghi789jkl012 (新层,仅依赖文件)
Step 4/7 : RUN go mod download
 ---> jkl012mno345 (新层,下载依赖,耗时45秒)
Step 5/7 : COPY . .
 ---> mno345pqr678 (新层,复制源码)
Step 6/7 : RUN go build -o app .
 ---> pqr678stu901 (新层,编译应用,耗时30秒)
Step 7/7 : CMD ["./app"]
 ---> stu901vwx234 (新层)

# 修改源码后重新构建(go.mod未变)
$ echo 'fmt.Println("updated")' >> main.go
$ docker build -t myapp .
Step 1/7 : FROM golang:1.21-alpine
 ---> abc123def456 (缓存命中)
Step 2/7 : WORKDIR /src
 ---> def456ghi789 (缓存命中)
Step 3/7 : COPY go.mod go.sum ./
 ---> ghi789jkl012 (缓存命中,go.mod未变)
Step 4/7 : RUN go mod download
 ---> jkl012mno345 (缓存命中!跳过45秒下载)
Step 5/7 : COPY . .
 ---> vwx234yza567 (文件变化,但依赖已缓存)
Step 6/7 : RUN go build -o app .
 ---> yza567bcd890 (重新编译,耗时30秒)
Step 7/7 : CMD ["./app"] 
 ---> bcd890efg123 (重新执行)

性能对比:

  • 低效写法:每次代码修改都要重新下载依赖+重新编译 (~75秒)
  • 高效写法:只有依赖变化才重新下载,代码变化只需重新编译 (~30秒)
  • 速度提升:2.5倍!

进阶特性

  • Inline cache:--build-arg BUILDKIT_INLINE_CACHE=1 让生成的镜像携带缓存元数据,便于在 CI/CD 间共享。
  • 缓存挂载:RUN --mount=type=cache,target=/root/.cargo 用于依赖缓存,避免写入镜像层。

3. 多阶段构建(Multi-Stage Build)

通过多阶段构建可显著减小最终镜像体积,并隔离编译依赖。

FROM golang:1.22 AS builder
WORKDIR /src
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app

# 轻量级运行时镜像
FROM gcr.io/distroless/base
COPY --from=builder /out/app /usr/bin/app
USER nonroot:nonroot
ENTRYPOINT ["/usr/bin/app"]
  • 第一阶段包含完整构建链条;第二阶段仅拷贝编译产物。
  • FROM scratch 适用于极简场景(如静态编译的 Go 程序)。

4. 常用指令详解

指令 关键点 示例
FROM 选择安全、体积小的基础镜像,如 alpine、distroless。可用 AS 命名阶段。 FROM alpine:3.20 AS base
LABEL 添加元数据;支持 label filter。 LABEL maintainer="dev@example.com"
RUN 使用 && 链式执行并在末尾清理缓存,减少层大小;失败即终止构建。 RUN apt-get update && apt-get install -y curl \ && rm -rf /var/lib/apt/lists/*
COPY 仅本地文件;支持 --chmod / --chown;优先于 ADD。 COPY --chmod=755 app /usr/bin/app
ADD 额外支持 URL、自动解压,但因不易控制建议慎用。 ADD https://example.com/busybox.tar.gz /
ENV 设置环境变量;会影响后续缓存。 ENV TZ=Asia/Shanghai
EXPOSE 声明元数据,不会真正开放端口。 EXPOSE 80 443
USER 以非 root 身份运行提高安全性。 USER 10001:10001
WORKDIR 设置工作目录,相当于 cd。 WORKDIR /app
ENTRYPOINT 定义主命令;用 JSON 形式可避免 shell 解析。 ENTRYPOINT ["/usr/bin/app"]
CMD 默认参数;docker run 追加/覆盖。 CMD ["--help"]
VOLUME 声明匿名卷;声明后对同一路径的修改不再进入镜像层。 VOLUME ["/data"]

ENTRYPOINT 与 CMD 有何区别?

• ENTRYPOINT:定义容器启动后必定执行的"主进程",一般不会被替换;当使用 JSON 形式时,docker run 只能追加参数而不能修改主进程
• CMD:为 ENTRYPOINT 提供"默认参数";如果镜像未定义 ENTRYPOINT,则 CMD 本身可作为要执行的命令。当 docker run 明确给出新命令时会覆盖 CMD

实践范式:

ENTRYPOINT ["/usr/bin/app"]
CMD ["--port=80"]

用户可以通过 docker run image --port=8080 覆盖默认参数,但仍然执行 /usr/bin/app 这一主进程。

JSON 形式示例:

# JSON 形式(推荐)- 直接执行,不经过 shell 解析
ENTRYPOINT ["/usr/bin/app", "--config", "/etc/app.conf"]
CMD ["--port", "80", "--debug"]

# Shell 形式 - 会通过 /bin/sh -c 执行
ENTRYPOINT /usr/bin/app --config /etc/app.conf
CMD --port 80 --debug

5. 编写 Dockerfile 的 10 条最佳实践

  1. 按变更频率排序:先 COPY go.mod 等少变化文件,再 COPY 源码,提高缓存命中。
  2. 合并 RUN:将相关命令用 && 合并并清理缓存目录,减少层级。
  3. 使用 .dockerignore:排除无关文件。
  4. 只装必需依赖:过多包会带来额外漏洞与体积。
  5. 优先使用 COPY,仅在需 URL/解压时用 ADD。
  6. 启用 BuildKit:DOCKER_BUILDKIT=1 或 docker buildx build 获取新特性与更快速度。
  7. 多阶段 + distroless:减小运行时镜像并降低攻击面。
  8. 非 root 运行:USER 指定普通用户,并确保文件权限正确。
  9. 健康检查:为生产镜像添加 HEALTHCHECK,便于编排系统检测。
  10. CI 中扫描漏洞:结合 trivy、grype 等工具发布前扫描镜像。

6. 常见问题排查

现象 可能原因 解决方案
using cache 但代码已更新 COPY 的路径不一致或文件在 .dockerignore 中 检查 COPY/ADD 路径;确认文件未被忽略
镜像过大 未清理包缓存 / 未多阶段构建 采用多阶段;在 RUN 中删除 /var/lib/apt/lists 等缓存
应用无权限访问文件 运行用户非 root 调整文件属主或使用 --chown COPY

7. 参考链接

  • Docker Docs – Build images with BuildKit: https://docs.docker.com/build/
  • 官方最佳实践:https://docs.docker.com/develop/dev-best-practices/
  • Distroless 镜像介绍:https://github.com/GoogleContainerTools/distroless

posted @ 2025-06-22 12:08  showyoui  阅读(172)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3