6.2 .dockerignore与多阶段构建

.dockerignore 排除无关文件,使用多阶段构建让最终镜像只保留运行所需内容。任务一:使用 .dockerignore 排除无关文件
在构建Docker镜像时,项目目录中经常会有很多与应用运行无关的文件,例如日志文件、缓存文件、测试文件、说明文档等。如果这些文件全部进入镜像,就像“安装包里混进了草稿纸和临时文件”,不仅占空间,还可能泄露不该公开的信息。
.dockerignore 文件可以理解为镜像构建时的“排除清单”,用来告诉Docker:哪些文件或目录不需要参与镜像打包。
这样做的好处是:
- 减小镜像体积
- 加快构建速度
- 减少无关文件和敏感信息进入镜像
1、创建项目目录并进入
mkdir -p /root/ignore
cd /root/ignore
2、准备测试文件
执行以下命令,模拟一个包含应用文件、日志文件、缓存文件、测试文件和说明文档的项目目录。
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
查看当前目录内容:
ls -R
以上文件中,哪些是应用运行必须的?哪些只是开发、测试或说明时使用的?
本实验中,真正需要进入镜像运行的是 app.sh,其他日志、缓存、测试、文档和临时文件都可以排除。
3、创建 .dockerignore 文件
vim .dockerignore
文件内容如下:
LICENSE
logs/
cache/
test/
docs/
*.tmp
在这个配置中:
LICENSE:排除许可证文件logs/:排除日志目录cache/:排除缓存目录test/:排除测试目录docs/:排除说明文档目录*.tmp:排除所有以.tmp结尾的临时文件
实际工作中,是否排除某个文件要根据项目要求判断,不能机械照抄。
4、创建 Dockerfile 文件
vim 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做了以下事情:
- 使用轻量的
alpine基础镜像 - 设置工作目录为
/app - 将当前项目文件复制到镜像中
- 启动容器时列出镜像中的文件,并运行
app.sh
注意:.dockerignore 中指定的文件和目录不会进入构建过程。
5、构建镜像
docker build -t ignore:v1 .
6、运行容器查看效果
docker run --rm ignore:v1
运行后可以看到,镜像中保留了应用运行所需文件,而 logs/、cache/、test/、docs/、LICENSE、debug.tmp 等无关内容没有被打包进去。
这说明 .dockerignore 已经生效。
任务二:对比单阶段与多阶段构建
.dockerignore 解决的是“哪些文件不该进入构建过程”的问题。接下来要解决另一个问题:有些工具只在“制作应用”时需要,应用真正运行时并不需要。
例如Go程序需要Go编译器来生成可执行文件,但程序运行时不需要把Go编译器也放进最终镜像。多阶段构建就是把“构建应用”和“运行应用”分开,最终镜像只保留运行所需内容。
多阶段构建可以理解为“先在厨房做饭,再把成品端上桌”。
- 构建阶段:准备工具、安装依赖、编译或打包应用
- 运行阶段:只保留最终运行所需的文件
这样最终镜像更小、更干净,也更安全。
1、创建项目目录
mkdir -p /root/go
cd /root/go
2、创建一个简单的Go程序
vim app.go
输入以下内容:
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
这是一个最简单的Go程序,只输出一句 Hello World!,但足以演示多阶段构建的优化效果。
方案一:单阶段构建
3、创建 Dockerfile-1
vim Dockerfile-1
文件内容如下:
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、构建单阶段镜像
docker build -f Dockerfile-1 -t go:v1 .
5、运行单阶段镜像
docker run --rm go:v1
6、查看镜像大小
docker images go
单阶段构建虽然能够正常运行,但最终镜像中包含完整的Go开发环境。
对于一个只输出 Hello World! 的程序来说,如果镜像体积达到几百MB,显然不够合理。原因是:编译器、工具链等内容运行时用不到,却被保留在最终镜像中。
方案二:多阶段构建
7、创建 Dockerfile-2
vim Dockerfile-2
文件内容如下:
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、构建多阶段镜像
docker build -f Dockerfile-2 -t go:v2 .
9、运行多阶段镜像
docker run --rm go:v2
如果 go:v1 和 go:v2 都能输出:
Hello World!
说明两种镜像的运行效果一致。 但它们的镜像体积和内部内容并不相同。
10、对比两个镜像的大小
docker images | grep go
请观察 go:v1 和 go:v2 的镜像大小差异。
一般情况下:
go:v1:包含Go编译环境,镜像较大go:v2:只保留运行文件,镜像明显变小
这说明多阶段构建能够在保证程序正常运行的前提下,有效减少镜像体积。
任务三:记录优化结果
完成以上操作后,请填写你的实验记录。
| 对比项目 | 单阶段构建 go:v1 | 多阶段构建 go:v2 |
|---|---|---|
| 是否构建成功 | ||
| 是否运行成功 | ||
| 镜像大小 | ||
| 是否包含构建工具 | ||
| 优化效果说明 |
请结合本次实验回答:
.dockerignore主要解决什么问题?- 多阶段构建为什么能减小镜像体积?
- 为什么说镜像构建不仅要“能运行”,还要“更小、更干净、更安全”?
提交要求
请在CloudTutor平台提交以下内容:
/root/ignore目录中的.dockerignore和Dockerfile/root/go目录中的Dockerfile-1和Dockerfile-2docker images | grep go的结果截图docker run --rm go:v1和docker run --rm go:v2的运行结果截图- 实验记录表和思考题答案
提交前请确认:
- 镜像能够成功构建
- 容器能够正常运行
- 优化前后镜像大小有对比
- 文件命名规范,截图清晰
多阶段构建的适用场景
多阶段构建特别适合以下场景:
编译型语言项目
- Go语言应用
- Java应用
- C/C++应用
- Rust应用
前端项目
- Vue项目
- React项目
- Angular项目
- TypeScript项目
通用原则 只要应用需要“构建、编译、打包”步骤,而这些构建工具在运行时不需要,就可以考虑使用多阶段构建。
总结:镜像优化的基本原则
通过本次实验,我们学习了两个重要的镜像优化方法:
1. 排除无关文件
- 使用
.dockerignore排除日志、缓存、测试文件、临时文件等内容 - 避免无关内容进入镜像构建过程
2. 分离构建环境和运行环境
- 构建阶段负责生成应用成果
- 运行阶段只保留最终运行所需内容
3. 构建后必须验证
- 镜像变小不是唯一目标
- 优化后应用仍然要能正常运行
- 要通过镜像大小、运行结果和日志信息综合判断优化是否成功