如何压缩 Docker 镜像?

52

我创建了一个相当大的Docker容器。当我将该容器提交以创建镜像时,镜像大约有7.8GB。但当我将容器导出(不是保存镜像!)到tar包并重新导入时,镜像只有3GB大小。当然历史记录会丢失,但这对我来说没问题,因为我认为镜像已经"完成"并准备好部署。

如何在不将其导出到磁盘并重新导入的情况下压缩图像/容器?此外:这样做明智吗,还是我错过了一些重要的点?


2
你是否使用--rm选项构建?这将删除中间镜像。或者我误解了问题? - shabbychef
1
有一些其他的技巧可以使图像更小:在一个RUN中调用一堆安装命令,删除不需要的Ubuntu软件包等。请参考https://github.com/dckc/ipython-docker/blob/master/Dockerfile获取一个好的例子。 - shabbychef
4个回答

54
现在Docker已经在17.05版本中发布了多阶段构建,你可以重新格式化构建过程,使其看起来像这样:
FROM buildimage as build
# your existing build steps here
FROM scratch
COPY --from=build / /
CMD ["/your/start/script"]

结果是您的构建环境层会被缓存在构建服务器上,但只有一个扁平化的副本会存在于您标记和推送的结果镜像中。
请注意,通常情况下,您需要重新制定这个过程以使用复杂的构建环境,只复制几个目录。以下是一个示例,使用Go从源代码创建单个二进制映像,并使用单个构建命令,在不安装Go的主机并在docker外部编译的情况下完成。
$ cat Dockerfile 
ARG GOLANG_VER=1.8
FROM golang:${GOLANG_VER} as builder
WORKDIR /go/src/app
COPY . .
RUN go-wrapper download 
RUN go-wrapper install

FROM scratch
COPY --from=builder /go/bin/app /app
CMD ["/app"]

go文件是一个简单的“hello world”示例:

$ cat hello.go 
package main

import "fmt"

func main() {
        fmt.Printf("Hello, world.\n")
}

构建过程创建了两个环境,一个是构建环境,另一个是基础环境,并将基础环境打上标签:
$ docker build -t test-multi-hello .                                                                                                                              
Sending build context to Docker daemon  4.096kB
Step 1/9 : ARG GOLANG_VER=1.8
 ---> 
Step 2/9 : FROM golang:${GOLANG_VER} as builder
 ---> a0c61f0b0796
Step 3/9 : WORKDIR /go/src/app
 ---> Using cache
 ---> af5177aae437
Step 4/9 : COPY . .
 ---> Using cache
 ---> 976490d44468
Step 5/9 : RUN go-wrapper download
 ---> Using cache
 ---> e31ac3ce83c3
Step 6/9 : RUN go-wrapper install
 ---> Using cache
 ---> 2630f482fe78
Step 7/9 : FROM scratch
 ---> 
Step 8/9 : COPY --from=builder /go/bin/app /app
 ---> Using cache
 ---> 5645db256412
Step 9/9 : CMD /app
 ---> Using cache
 ---> 8d428d6f7113
Successfully built 8d428d6f7113
Successfully tagged test-multi-hello:latest

看这些图片,我们可以发现,只有一个二进制文件被发布在镜像中,而构建环境却超过了700MB:

$ docker images | grep 2630f482fe78
<none>                <none>              2630f482fe78        6 days ago          700MB

$ docker images | grep 8d428d6f7113
test-multi-hello      latest              8d428d6f7113        6 days ago          1.56MB

是的,它可以运行:

$ docker run --rm test-multi-hello 
Hello, world.

1
这应该是目前为止被接受的答案了。它非常有效和灵活! - Dean Christian Armada
6
请注意,使用此方法会使工作目录、入口点、环境变量等消失,您需要重复它们。除此之外,非常完美! :) - Keymon
10
请注意,COPY 命令会将所有文件系统的所有权重写为 root(或运行该命令的用户),这可能不是理想的情况。 - ti7

43

从 Docker 1.13 开始,您可以使用 --squash 标志。


在版本 1.13 之前:

据我所知,您无法使用 Docker API。对于这种情况,docker exportdocker import 是专为此设计的,正如您自己已经提到的那样。

如果您不想保存到磁盘,您可能可以将导出流的输出流导入到导入流的输入流中。我没有测试过这个方法,但可以尝试。

docker export red_panda | docker import - exampleimagelocal:new

1
我刚刚使用“Docker version 1.1.1, build bd609d2”完成了这项操作,生成的镜像并没有缩小多少,实际上有点变大了。但是新镜像的历史记录已经被清除了。 - VolkerK
3
可以。适用于 Docker 17.03.0-ce,从 33GB 的镜像大小降至 19GB。 - Hitman_99
2
“--squash” 对于某些守护程序需要实验性标志。 “export” 适用于容器,而不是镜像。 您有任何关于如何将图像转换为容器而不运行图像内部任何内容的建议吗? 我想到的一个想法是运行一个在容器中不存在的入口点。 这似乎仍然会创建一个新的容器。 - init_js
9
--squash 标志在哪些情况下可以使用? - anon
3
Docker构建 --squash -f <dockerfile所在目录> . - Steve Scott
显示剩余5条评论

3

请查看docker-squash

安装命令如下:

pip install docker-squash

如果您有一个图片,可以使用以下方法将所有图层压缩到一个图层中:

docker-squash -f <nr_layers_to_squash> -t new_image:tag existing_image:tag

一个对我很有用的快速压缩所有图层的一行代码:

docker-squash -f $(($(docker history $IMAGE_NAME | wc -l | xargs)-1)) -t ${IMAGE_NAME}:squashed $IMAGE_NAME

正是我所需要的!!!没有Docker历史记录!!!:D 安全第一...信任度为0 - Marcello DeSales

2

3
现有的图片怎么办? - MrR

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接