6.2 排除文件与多阶段构建

系列 - 应用程序容器化
摘要
本实验将带你探索Docker镜像构建的实用技巧,学习如何通过.dockerignore文件排除不必要的文件,以及使用多阶段构建大幅减小镜像体积。

在构建Docker镜像时,我们经常会遇到这样的问题:项目目录中有很多与运行应用无关的文件(比如文档、许可证文件、测试文件等),如果全部打包到镜像中,会白白增加镜像大小。这时候 .dockerignore 就派上用场了!

.dockerignore的作用

.dockerignore 文件的作用类似于 .gitignore,可以指定哪些文件或目录不需要被复制到Docker镜像中。这样做的好处是:

  • 减小镜像体积
  • 加快构建速度
  • 避免敏感信息泄露

1、创建项目目录并进入

bash

mkdir /root/ignore
cd /root/ignore

2、执行以下命令,下载测试用的示例文件

这个脚本会在当前目录中准备三个文件,用于演示 .dockerignore 的效果。

bash

curl -fsSL https://git.seahi.me/seahi/docker/raw/branch/main/6.1%E6%9E%84%E5%BB%BA%E9%95%9C%E5%83%8F%E5%8E%9F%E5%88%99/download_me.sh | bash

下载文件

3、创建 .dockerignore 文件

bash

vim .dockerignore

文件内容如下:

text

LICENSE
go/
配置说明

在这个配置中:

  • LICENSE:排除许可证文件(运行应用不需要)
  • go/:排除整个go目录

4、创建 Dockerfile 文件

bash

vim Dockerfile

文件内容如下:

dockerfile

FROM alpine
WORKDIR /app
COPY . .
CMD ["ls","-la"]
Dockerfile说明

这个简单的Dockerfile做了以下事情:

  • 使用轻量的alpine基础镜像
  • 设置工作目录为 /app
  • 复制当前目录所有文件到容器(.dockerignore中指定的文件会被排除)
  • 启动容器时执行 ls -la 命令,列出复制到容器的文件

5、构建镜像

bash

docker build -t ignore .

6、运行容器查看效果

通过运行容器,我们可以看到哪些文件被实际复制到了镜像中。

bash

docker run --rm ignore

运行结果

观察结果
你会发现,虽然项目目录中有 LICENSE 文件和 go/ 目录,但容器中并没有这些文件。这就是 .dockerignore 的作用!它成功地将这些不需要的文件排除在镜像之外。

在这个任务中,我们将通过一个实际的Go应用程序,对比单阶段构建和多阶段构建的镜像大小差异。结果可能会让你大吃一惊!

什么是多阶段构建?

多阶段构建可以将"构建阶段"和"运行阶段"分离:

  • 第一阶段:使用包含完整开发工具的镜像编译应用
  • 第二阶段:使用精简的基础镜像,只复制编译好的可执行文件

这样最终的镜像只包含运行所需的最小内容!

1、创建项目目录

bash

mkdir /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

网络慢怎么办?

在 Dockerfile 第2行加入以下内容:

dockerfile

ENV http_proxy=http://192.168.192.199:10809
ENV https_proxy=http://192.168.192.199:10809
ENV all_proxy=http://192.168.192.199:10809

最终文件内容如下:

最终文件内容

4、构建单阶段镜像

bash

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

构建过程

5、查看镜像大小

bash

docker images go

镜像列表

镜像太大了

单阶段构建的镜像大小是 421 MB

一个只打印 “Hello World!” 的简单程序,镜像竟然要400多MB,这显然不合理。大部分空间都被编译工具占用了,而这些工具在程序运行时完全用不到。

问题出在哪里?

这种方式的问题在于:最终的镜像将包含整个Go开发环境,包括:

  • Go编译器
  • Go工具链
  • 各种构建工具

这些东西运行时根本用不到,却占用大量空间!

6、创建 Dockerfile-2(使用多阶段构建)

bash

vim Dockerfile-2

文件内容如下:

多阶段构建Dockerfile

多阶段构建详解

这个Dockerfile分为两个阶段:

第一阶段(builder)

  • 使用 alpine 镜像(安装Go编译器)
  • 设置工作目录
  • 复制源代码
  • 编译生成可执行文件 /app/app

第二阶段(最终镜像)

  • 使用更小的 alpine 基础镜像(不包含Go编译器)
  • 只从第一阶段复制编译好的可执行文件
  • 设置启动命令

最终镜像中不包含Go编译器和源代码,大大减小了体积!

7、构建多阶段镜像

bash

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

构建过程

8、对比两个镜像的大小

bash

docker images | grep go

镜像对比

惊人的差距

对比结果:

  • 单阶段构建(go:v1):421 MB
  • 多阶段构建(go:v2):10.5 MB

多阶段构建的镜像比单阶段构建小了 40倍!这意味着:

  • 下载时间快40倍
  • 节省存储空间40倍
  • 启动速度更快
  • 攻击面更小(不包含不必要的开发工具)

对比运行结果

什么时候使用多阶段构建?

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

编译型语言项目

  • Go语言应用
  • Java应用(Maven/Gradle构建)
  • C/C++应用
  • Rust应用

前端项目

  • React/Vue/Angular应用(需要npm build)
  • TypeScript项目

通用原则: 只要你的应用需要"构建"步骤,而构建工具在运行时不需要,就应该使用多阶段构建!

通过本次实验,我们学习了两个重要的镜像优化技巧。在实际工作中,应该将这些技巧结合起来使用:

镜像优化三大原则

1. 选择合适的基础镜像

  • 优先选择alpine等精简版本
  • 例如:alpine(5MB)vs ubuntu(80MB+)

2. 减少镜像层数

  • 合并多个RUN命令(使用 && 连接)
  • 清理不必要的缓存文件

3. 使用多阶段构建

  • 将构建环境与运行环境分离
  • 只保留运行所需的最终产物
  • 特别适合编译型语言和需要构建的项目

相关内容