6.2 .dockerignore与多阶段构建

系列 - 应用程序容器化
摘要
本实验将带你完成一次“镜像瘦身”任务。Docker镜像可以理解为应用上线前的“标准安装包”,如果把日志、缓存、测试文件、构建工具等无关内容也打包进去,就会导致镜像体积变大、传输变慢,也可能带来安全风险。本实验将学习两种常用优化方法:使用 .dockerignore 排除无关文件,使用多阶段构建让最终镜像只保留运行所需内容。

在构建Docker镜像时,项目目录中经常会有很多与应用运行无关的文件,例如日志文件、缓存文件、测试文件、说明文档等。如果这些文件全部进入镜像,就像“安装包里混进了草稿纸和临时文件”,不仅占空间,还可能泄露不该公开的信息。

.dockerignore的作用

.dockerignore 文件可以理解为镜像构建时的“排除清单”,用来告诉Docker:哪些文件或目录不需要参与镜像打包。

这样做的好处是:

  • 减小镜像体积
  • 加快构建速度
  • 减少无关文件和敏感信息进入镜像

1、创建项目目录并进入

bash

mkdir -p /root/ignore
cd /root/ignore

2、准备测试文件

执行以下命令,模拟一个包含应用文件、日志文件、缓存文件、测试文件和说明文档的项目目录。

bash

cat > app.sh <<'EOF'
#!/bin/sh
echo "应用启动成功"
EOF

chmod +x app.sh

mkdir -p logs cache test docs
echo "运行日志:debug info..." > logs/run.log
echo "缓存文件" > cache/temp.cache
echo "测试用例文件" > test/test_app.txt
echo "项目说明文档" > docs/readme.md
echo "许可证文件" > LICENSE
echo "临时文件" > debug.tmp

查看当前目录内容:

bash

ls -R
想一想

以上文件中,哪些是应用运行必须的?哪些只是开发、测试或说明时使用的?

本实验中,真正需要进入镜像运行的是 app.sh,其他日志、缓存、测试、文档和临时文件都可以排除。

3、创建 .dockerignore 文件

bash

vim .dockerignore

文件内容如下:

text

LICENSE
logs/
cache/
test/
docs/
*.tmp
配置说明

在这个配置中:

  • LICENSE:排除许可证文件
  • logs/:排除日志目录
  • cache/:排除缓存目录
  • test/:排除测试目录
  • docs/:排除说明文档目录
  • *.tmp:排除所有以 .tmp 结尾的临时文件

实际工作中,是否排除某个文件要根据项目要求判断,不能机械照抄。

4、创建 Dockerfile 文件

bash

vim Dockerfile

文件内容如下:

dockerfile

FROM alpine:3.20
WORKDIR /app
COPY . .
CMD ["sh","-c","echo '镜像中的文件:'; find /app -maxdepth 2 -type f | sort; echo '运行结果:'; sh /app/app.sh"]
Dockerfile说明

这个Dockerfile做了以下事情:

  • 使用轻量的 alpine 基础镜像
  • 设置工作目录为 /app
  • 将当前项目文件复制到镜像中
  • 启动容器时列出镜像中的文件,并运行 app.sh

注意:.dockerignore 中指定的文件和目录不会进入构建过程。

5、构建镜像

bash

docker build -t ignore:v1 .

6、运行容器查看效果

bash

docker run --rm ignore:v1
观察结果

运行后可以看到,镜像中保留了应用运行所需文件,而 logs/cache/test/docs/LICENSEdebug.tmp 等无关内容没有被打包进去。

这说明 .dockerignore 已经生效。

.dockerignore 解决的是“哪些文件不该进入构建过程”的问题。接下来要解决另一个问题:有些工具只在“制作应用”时需要,应用真正运行时并不需要。

例如Go程序需要Go编译器来生成可执行文件,但程序运行时不需要把Go编译器也放进最终镜像。多阶段构建就是把“构建应用”和“运行应用”分开,最终镜像只保留运行所需内容。

什么是多阶段构建?

多阶段构建可以理解为“先在厨房做饭,再把成品端上桌”。

  • 构建阶段:准备工具、安装依赖、编译或打包应用
  • 运行阶段:只保留最终运行所需的文件

这样最终镜像更小、更干净,也更安全。

1、创建项目目录

bash

mkdir -p /root/go
cd /root/go

2、创建一个简单的Go程序

bash

vim app.go

输入以下内容:

go

package main

import "fmt"

func main() {
    fmt.Println("Hello World!")
}

这是一个最简单的Go程序,只输出一句 Hello World!,但足以演示多阶段构建的优化效果。

3、创建 Dockerfile-1

bash

vim Dockerfile-1

文件内容如下:

dockerfile

FROM golang:1.22-alpine
WORKDIR /app
COPY app.go .
RUN go build -o app app.go
CMD ["./app"]
单阶段构建说明

这个Dockerfile在同一个镜像中完成两件事:

  • 使用Go环境编译程序
  • 直接在包含Go环境的镜像中运行程序

这种方式简单,但最终镜像会包含Go编译器和相关工具,而这些内容在程序运行时并不需要。

镜像拉取慢怎么办?

如果拉取基础镜像速度较慢,请优先使用CloudTutor平台已配置的镜像加速环境。

如教师提供了代理地址或校内镜像仓库地址,可按教师要求进行配置。

4、构建单阶段镜像

bash

docker build -f Dockerfile-1 -t go:v1 .

5、运行单阶段镜像

bash

docker run --rm go:v1

6、查看镜像大小

bash

docker images go
问题分析

单阶段构建虽然能够正常运行,但最终镜像中包含完整的Go开发环境。

对于一个只输出 Hello World! 的程序来说,如果镜像体积达到几百MB,显然不够合理。原因是:编译器、工具链等内容运行时用不到,却被保留在最终镜像中。

7、创建 Dockerfile-2

bash

vim Dockerfile-2

文件内容如下:

dockerfile

FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY app.go .
RUN go build -ldflags="-s -w" -o app app.go

FROM alpine:3.20
WORKDIR /app
COPY --from=builder /src/app .
CMD ["./app"]
多阶段构建详解

这个Dockerfile分为两个阶段:

第一阶段:builder

  • 使用包含Go编译环境的镜像
  • 复制源代码
  • 编译生成可执行文件 app

第二阶段:最终运行镜像

  • 使用更小的 alpine 镜像
  • 只从第一阶段复制编译好的 app
  • 不保留Go编译器、源代码和构建工具

最终镜像只包含运行程序所需的最小内容。

8、构建多阶段镜像

bash

docker build -f Dockerfile-2 -t go:v2 .

9、运行多阶段镜像

bash

docker run --rm go:v2
运行验证

如果 go:v1go:v2 都能输出:

text

Hello World!

说明两种镜像的运行效果一致。 但它们的镜像体积和内部内容并不相同。

10、对比两个镜像的大小

bash

docker images | grep go
对比结果

请观察 go:v1go:v2 的镜像大小差异。

一般情况下:

  • go:v1:包含Go编译环境,镜像较大
  • go:v2:只保留运行文件,镜像明显变小

这说明多阶段构建能够在保证程序正常运行的前提下,有效减少镜像体积。

完成以上操作后,请填写你的实验记录。

对比项目 单阶段构建 go:v1 多阶段构建 go:v2
是否构建成功
是否运行成功
镜像大小
是否包含构建工具
优化效果说明
思考题

请结合本次实验回答:

  1. .dockerignore 主要解决什么问题?
  2. 多阶段构建为什么能减小镜像体积?
  3. 为什么说镜像构建不仅要“能运行”,还要“更小、更干净、更安全”?

请在CloudTutor平台提交以下内容:

  1. /root/ignore 目录中的 .dockerignoreDockerfile
  2. /root/go 目录中的 Dockerfile-1Dockerfile-2
  3. docker images | grep go 的结果截图
  4. docker run --rm go:v1docker run --rm go:v2 的运行结果截图
  5. 实验记录表和思考题答案
提交提醒

提交前请确认:

  • 镜像能够成功构建
  • 容器能够正常运行
  • 优化前后镜像大小有对比
  • 文件命名规范,截图清晰
什么时候使用多阶段构建?

多阶段构建特别适合以下场景:

编译型语言项目

  • Go语言应用
  • Java应用
  • C/C++应用
  • Rust应用

前端项目

  • Vue项目
  • React项目
  • Angular项目
  • TypeScript项目

通用原则 只要应用需要“构建、编译、打包”步骤,而这些构建工具在运行时不需要,就可以考虑使用多阶段构建。

通过本次实验,我们学习了两个重要的镜像优化方法:

镜像优化三大原则

1. 排除无关文件

  • 使用 .dockerignore 排除日志、缓存、测试文件、临时文件等内容
  • 避免无关内容进入镜像构建过程

2. 分离构建环境和运行环境

  • 构建阶段负责生成应用成果
  • 运行阶段只保留最终运行所需内容

3. 构建后必须验证

  • 镜像变小不是唯一目标
  • 优化后应用仍然要能正常运行
  • 要通过镜像大小、运行结果和日志信息综合判断优化是否成功