Search code examples
dockerdockerfile

Docker: WORKDIR create a layer?


https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#minimize-the-number-of-layers

After read the above doc, I tested to prove the following statement:

Only the instructions RUN, COPY, ADD create layers. Other instructions create temporary intermediate images, and do not increase the size of the build.

My envs are:

❯ sw_vers
ProductName:    macOS
ProductVersion: 11.6.1
BuildVersion:   20G224

❯ docker version
Client:
 Cloud integration: v1.0.22
 Version:           20.10.13
 API version:       1.41
 Go version:        go1.16.15
 Git commit:        a224086
 Built:             Thu Mar 10 14:08:44 2022
 OS/Arch:           darwin/amd64
 Context:           default
 Experimental:      true

Server: Docker Desktop 4.6.1 (76265)
 Engine:
  Version:          20.10.13
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.16.15
  Git commit:       906f57f
  Built:            Thu Mar 10 14:06:05 2022
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.5.10
  GitCommit:        2a1d4dbdb2a1030dc5b01e96fb110a9d9f150ecc
 runc:
  Version:          1.0.3
  GitCommit:        v1.0.3-0-gf46b6ba
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

❯ dive --version
dive 0.10.0
# installed from brew

Following Dockerfile, using to test, cotains all instruction but it doesn't make sense quietly(Some of them are not used, so it does not cover all possibilities maybe):

# syntax=docker/dockerfile:1
FROM alpine:3.15.4
LABEL name=layer
EXPOSE 3456
ENV APP=layer
ADD add.tar.gz /
COPY copy /copy
ENTRYPOINT ["date"]
RUN rm /bin/arch
CMD ["--help"]
VOLUME /log
USER root
ARG workdir
WORKDIR $workdir
ONBUILD RUN echo 'b'
STOPSIGNAL SIGTERM
HEALTHCHECK CMD which date
SHELL ["/bin/sh", "-c"]

Build and inspect the image, the name as layer, it has 5 layers:

# Preparation: create build contexts for ADD and COPY
> mkdir add
> dd if=/dev/urandom of=add/1 bs=1M count=1
> dd if=/dev/urandom of=add/2 bs=1M count=1
> dd if=/dev/urandom of=copy bs=1M count=1
> tar -cf add.tar add
> gzip add.tar

# Build
❯ docker build --build-arg workdir=/etc -t layer .
(...eliding)

# Inspect
❯ docker inspect layer | jq '.[0].RootFS.Layers | length'
5

Inspect with dive, there are unexpected instructions which make layers, FROM and WORKDIR :

# the left pane of `dive layer'
┃  ●  Layers ┣ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ │
Cmp   Size  Command                                                   
    5.6 MB  FROM 8792aa27a60beb9                                       
    2.1 MB  ADD add.tar.gz / # buildkit                                
    1.0 MB  COPY copy /copy # buildkit                                 
       0 B  RUN /bin/sh -c rm /bin/arch # buildkit                     
       0 B  WORKDIR /etc

Except RUN, ADD and COPY, there is other instructions that create a image layer?

  • For FROM, it seem reasonable somehow
  • But for WORKDIR, Why? Can I get the references about it?

+ Like dive, how could I map between the image layer digest and the command each ?

❯ docker inspect layer | jq '.[0].RootFS.Layers[]'
"sha256:4fc242d58285699eca05db3cc7c7122a2b8e014d9481f323bd9277baacfa0628"
"sha256:38df8fd6b1d0100d029b0cfa3611ee48cf2d0e2d71856dab9b3c818ae180e100"
"sha256:deab9dc570f38644bbad2bb4a9a91ecec8afdbd4dbd7ed3bae4c3c63e84b1924"
"sha256:34b91600d244859b8ce5aaf493bed6c778c933d51d7f72df7f9ab426f55d50db"
"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"


Update

After the several times of recreating image, it has 4 layers except for WORKDIR. It seems undeterminitic...:

❯ docker inspect layer | jq -r '.[0].RootFS.Layers[]' | wc -l
4

# the leftpane in dive
┃  ●  Layers ┣ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━
Cmp   Size  Command                                                    
    5.6 MB  FROM 8792aa27a60beb9                                       
    2.1 MB  ADD add.tar.gz / # buildkit                                
    1.0 MB  COPY copy /copy # buildkit                                 
       0 B  RUN /bin/sh -c rm /bin/arch # buildkit

Solution

  • This seems to be a documentation "bug" in the best practices document. A better way to have phrased this is that only those commands you mention will create layers that increase the total build size.

    Note that the layers you're seeing are 0 bytes, as opposed to the FROM, ADD, COPY which each have an associated size (the amount they contribute to the total filesystem growth).

    Cmp   Size  Command                                                   
        5.6 MB  FROM 8792aa27a60beb9                                       
        2.1 MB  ADD add.tar.gz / # buildkit                                
        1.0 MB  COPY copy /copy # buildkit                                 
           0 B  RUN /bin/sh -c rm /bin/arch # buildkit                     
           0 B  WORKDIR /etc
    

    A layer exists for WORKDIR - but it's metadata. A layer exists for the rm command, but all it adds is a "file deleted" marker - not actual file contents. So they contribute nothing to the eventual filesystem size in the image.

    As for FROM, that doesn't, in and of itself, create a layer - it imports a starting image. The reference could be clearer about that, I guess, but either way it would confuse someone. They must assume it's obvious that FROM contributes to the size (unless you are using FROM scratch).

    I wasn't able to find a definitive reference about this. A reading of the source code might be the only such reference available.