Docker COPY/RUN/CMD は 1レイヤー増えるけど、FROM は指定したコンテナイメージ分だけレイヤー増える

Best practices for writing Dockerfiles によると、 FROM/COPY/RUN/CMD の 4 種類の操作ごとにレイヤーが作成されるらしい。 COPY/RUN/CMD は 1 レイヤ増えるだけなのは分かるが、FROM 部分は 1 レイヤだけ増えるんだろうか。それとも親のコンテナイメージのレイヤー分だけ増えるんだろうか。

Each instruction creates one layer:

FROM creates a layer from the ubuntu:18.04 Docker image.
COPY adds files from your Docker client’s current directory.
RUN builds your application with make.
CMD specifies what command to run within the container.

結論: 親のコンテナイメージのレイヤー分だけ増える。まあそうだよね。

検証内容

次のような Dockerfile からイメージを作成し、pull したときにダウンロードされるレイヤー数を確認する。

FROM nginx:alpine
RUN touch app.txt

イメージをビルドする。

# docker build -t my-nginx -f Dockerfile .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM nginx:alpine
alpine: Pulling from library/nginx
540db60ca938: Pull complete
0ae30075c5da: Pull complete
9da81141e74e: Pull complete
b2e41dd2ded0: Pull complete
7f40e809fb2d: Pull complete
758848c48411: Pull complete
Digest: sha256:0f8595aa040ec107821e0409a1dd3f7a5e989501d5c8d5b5ca1f955f33ac81a0
Status: Downloaded newer image for nginx:alpine
 ---> a6eb2a334a9f
Step 2/2 : RUN touch app.txt
 ---> Running in a2908d15addd
Removing intermediate container a2908d15addd
 ---> 56d6c8e6e54c
Successfully built 56d6c8e6e54c
Successfully tagged my-nginx:latest

# docker images
REPOSITORY   TAG       IMAGE ID       CREATED              SIZE
my-nginx     latest    56d6c8e6e54c   About a minute ago   22.6MB
nginx        alpine    a6eb2a334a9f   11 days ago          22.6MB

Dockerhub に push する。

# docker tag 56d6c8e6e54c  kimullaa/my-nginx:latest
# docker push kimullaa/my-nginx:latest
The push refers to repository [docker.io/kimullaa/my-nginx]
ed8e996ae7b6: Pushed
058eb06e0efd: Mounted from library/nginx
2f2df3ae0cad: Mounted from library/nginx
2b60f0243825: Mounted from library/nginx
96131b349b16: Mounted from library/nginx
a42a23cd7b07: Mounted from library/nginx
b2d5eeeaba3a: Mounted from library/nginx
latest: digest: sha256:5fe47980b1c632aa4536500a529eb7e2f41869b29b9140098ef975e18fc758ba size: 1774

ローカルのイメージを削除し、push したイメージを pull する。このとき、RUN を 1 こしか書いてないのに複数レイヤーがガウンロードされることから、FROM で指定した親のコンテナイメージのレイヤー分だけ増えてる様子がわかる。

# docker rmi -f 56d6c8e6e54c
Untagged: kimullaa/my-nginx:latest
Untagged: kimullaa/my-nginx@sha256:5fe47980b1c632aa4536500a529eb7e2f41869b29b9140098ef975e18fc758ba
Untagged: my-nginx:latest
Deleted: sha256:56d6c8e6e54c3d58e819f5b50e09990b2df139f20286938361ece7c441d24db7
Deleted: sha256:882a91f1f0cffc7159344c9205cbcdc3de679af92d56a3b50dd3c16ec5ee686c

# docker images
REPOSITORY          TAG       IMAGE ID       CREATED         SIZE
nginx               alpine    a6eb2a334a9f   11 days ago     22.6MB

# docker pull kimullaa/my-nginx:latest
latest: Pulling from kimullaa/my-nginx
540db60ca938: Already exists
0ae30075c5da: Already exists
9da81141e74e: Already exists
b2e41dd2ded0: Already exists
7f40e809fb2d: Already exists
758848c48411: Already exists
76c667677d7e: Pull complete
Digest: sha256:5fe47980b1c632aa4536500a529eb7e2f41869b29b9140098ef975e18fc758ba
Status: Downloaded newer image for kimullaa/my-nginx:latest
docker.io/kimullaa/my-nginx:latest

ついでにコンテナ実行して overlayfs の様子をみても、lowerdir に 7 レイヤ(FROM で指定した nginx が 6 レイヤ。RUN が 1 レイヤ)ある様子がわかる。

# docker run -d kimullaa/my-nginx
f46c13e0298f048f1712a738c3f7283c284fd5ffb0541e64d1972262b3837b4f

# docker exec -it f46c13e0298f048f1712a738c3f7283c284fd5ffb0541e64d1972262b3837b4f mount | grep overlay
overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/2G4GXEZAC6MBNYE5OKZR6IYDW5:/var/lib/docker/overlay2/l/O5GXX6WYSTHI5QLCH5GJ3OVD6E:/var/lib/docker/overlay2/l/ZWBFPQ7L4YSW3PVT7M5SUZG4ZS:/var/lib/docker/overlay2/l/VIATNKZE266STVGHR2CDTXOJVW:/var/lib/docker/overlay2/l/I6EPLY4J6TFHDFTELI2V4FSGI3:/var/lib/docker/overlay2/l/375Y7S4FQFOF5GEAG3ZFSZNTNM:/var/lib/docker/overlay2/l/ASNXD5OCHKNRG6XFYDPZWO7D3S:/var/lib/docker/overlay2/l/YXWBCZCQNRMR6VF2JXB4FAI6VR,upperdir=/var/lib/docker/overlay2/aa2d0884239fe4d6a91915e60a71de605e1c0db95e2cd2bc7fcbd44665144c16/diff,workdir=/var/lib/docker/overlay2/aa2d0884239fe4d6a91915e60a71de605e1c0db95e2cd2bc7fcbd44665144c16/work,xino=off)

蛇足

ちなみに以前の記事で実行時に重ねられるレイヤーの上限があることを確認した。

www.kimullaa.com

ということで、あまり大量のレイヤーから出来ているイメージを FROM に指定するのは良くなさげ。まあでも、最近は Multi-Stage Build があるから問題にならないか。

ちなみにじゃあ親イメージの親イメージの…と再帰的にさかのぼっていくと最終的にどうなるの?というところだけど、今回は scratch イメージっぽい(というかディストリビューションのイメージが FROM に指定されている場合はほぼ scratch っぽい)。 nginx:alpine から親の alpine イメージにさかのぼると、FROM が scratch になっている。

FROM scratch
ADD alpine-minirootfs-3.13.5-x86_64.tar.gz /
CMD ["/bin/sh"]

引用元: alpine の Dockerfile

で、このイメージはレイヤーが 1つ。え、FROM があるから scratch ぶんだけ 1 増えるんじゃないのという感じだけど、Dockerhub によると docker1.5.0 からは 何もしない虚無イメージらしい。

This image is most useful in the context of building base images (such as debian and busybox) or super minimal images (that contain only a single binary and whatever it requires, such as hello-world).

As of Docker 1.5.0 (specifically, docker/docker#8827), FROM scratch is a no-op in the Dockerfile, and will not create an extra layer in your image (so a previously 2-layer image will be a 1-layer image instead).

引用元: Dockerhub scratch