0%

使用Docker多阶段构建, 压缩镜像体积

什么是 Docker 多阶段构建

Docker 多阶段构建是一种通过在单个 Dockerfile 中定义多个构建阶段的技术。 每个阶段可以使用不同的基础镜像和依赖项,允许开发人员在构建过程中分离应用程序的编译环境与运行环境。 最终生成的镜像只包含运行应用程序所需的文件,而无需携带编译工具链或其他不必要的依赖
通过多阶段构建,可以有效减少镜像体积,提升部署效率

实例: 多阶段构建压缩 Docker 镜像

下面通过一个简单的 Go 程序,分别用传统方式和多阶段构建两种方法演示如何构建 Docker 镜像,并观察多阶段构建对镜像大小的显著优化效果

准备工作

  • 安装 Docker
  • 编写一个简单的 Go 程序 hello.go
1
2
3
4
5
6
7
package main

import "fmt"

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

传统构建方法

Dockerfile如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 第一阶段:构建环境
FROM rockylinux:9.3

# 安装Go环境
RUN dnf install -y golang git

WORKDIR /app

# 将本地代码复制到容器中
COPY . .

# 初始化Go模块
RUN go mod init example.com/hello

# 编译Go程序,静态链接
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o hello .

# 指定启动命令
CMD ["./hello"]

缺点:包含了完整的编译工具链(如 Go 编译器)以及操作系统的基础包,导致镜像体积庞大

构建与运行

1
2
3
docker build -t hello:1.0 .
docker run -it --rm hello:1.0
Hello, World!

查看镜像大小

1
2
3
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello 1.0 3a1caaa45859 10 minutes ago 671MB

多阶段构建方法

为了减小镜像体积,我们采用多阶段构建技术,将编译环境与运行环境分离

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 第一阶段:构建环境
FROM rockylinux:9.3 AS builder

# 安装Go环境
RUN dnf install -y golang git

WORKDIR /app

# 将本地代码复制到容器中
COPY . .

# 初始化Go模块
RUN go mod init example.com/hello

# 编译Go程序,静态链接
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o hello .

# 第二阶段:运行环境
FROM rockylinux:9.3 AS runner

# 从构建阶段复制可执行文件
COPY --from=builder /app/hello .

# 指定启动命令
CMD ["./hello"]

构建与运行

1
2
docker build -t hello-multi-stage:1.0 .
docker run -it --rm hello-multi-stage:1.0

查看镜像大小

1
2
3
4
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello 1.0 3a1caaa45859 12 minutes ago 671MB
hello-multi-stage 1.0 1579543c9b16 15 minutes ago 177MB

效果:镜像体积从 671MB 压缩到 177MB

进一步优化:使用 scratch 镜像

scratch 是 Docker 提供的一个空镜像,没有任何操作系统层或预装软件。它非常适合用于以下场景:

  • 静态编译的应用程序:如 Go 或 C/C++ 编写的程序,编译后生成的可执行文件不需要动态链接库支持
  • 最小化攻击面:由于没有操作系统层,几乎不存在因系统漏洞被攻击的风险
  • 快速部署:极小的镜像体积意味着更快的上传、下载和启动速度

Dockerfile如下:

1
2
3
4
5
6
7
8
9
10
11
FROM rockylinux:9.3 AS builder
RUN dnf install -y golang git
WORKDIR /app
COPY . .
RUN go mod init example.com/hello
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o hello .

# 第二阶段:运行环境, 基础镜像改为 scratch
FROM scratch
COPY --from=builder /app/hello .
CMD ["./hello"]

构建与运行

1
2
docker build -t hello-tiny:1.0 .
docker run -it --rm hello-tiny:1.0

查看镜像大小

1
2
3
4
5
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-tiny 1.0 40fd75a4f0ee 10 minutes ago 1.89MB
hello 1.0 3a1caaa45859 12 minutes ago 671MB
hello-multi-stage 1.0 1579543c9b16 15 minutes ago 177MB

效果: 镜像从177MB压缩到1.89M

构建镜像

1
docker build -t hello-tiny:1.0 .

运行

1
2
# docker run -it --rm hello-tiny:1.0
Hello, World!

查看镜像大小

1
2
3
4
5
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-tiny 1.0 40fd75a4f0ee 10 minutes ago 1.89MB
hello 1.0 3a1caaa45859 12 minutes ago 671MB
hello-multi-stage 1.0 1579543c9b16 15 minutes ago 177MB

其他几种常见的压缩 Docker 镜像的方法

1.选择合适的基础镜像

  • Alpine Linux:一个轻量级的Linux发行版,通常比其他基础镜像(如Ubuntu或Debian)要小得多。
  • Scratch:一个完全空白的基础镜像,适合静态编译的应用程序,如Go语言编写的应用程序。
  • 选择更小版本的基础镜像:例如,使用 python:3.8-slim 而不是 python:3.8。

2.删除不必要的文件和依赖

  • 在 Dockerfile 中执行清理操作,如移除安装包缓存或不需要的开发工具, 例如`RUN apt-get clean && rm -rf /var/lib/apt/lists/*

3.合并命令以减少层数

  • 每个 RUN 命令都会创建一个新的层。通过合并相关的命令到一个 RUN 指令中,可以减少最终镜像的层数,从而缩小镜像大小。
    1
    2
    3
    4
    RUN apt-get update && apt-get install -y \
    package-one \
    package-two \
    && rm -rf /var/lib/apt/lists/*

4.使用 .dockerignore 文件

  • 类似于 .gitignore,.dockerignore 文件可以指定哪些文件和目录不应包含在构建上下文中,从而避免不必要的文件被添加到镜像中。

5.利用 Docker Squash等外部工具优化, 将镜像中的多层合并为一层,减少最终镜像的大小

参考

Dockerfile 多阶段构建