Skip to content

Instantly share code, notes, and snippets.

@thaJeztah
Last active March 5, 2024 14:32
Show Gist options
  • Save thaJeztah/cfd929a31976b745e3f7515ae37eb192 to your computer and use it in GitHub Desktop.
Save thaJeztah/cfd929a31976b745e3f7515ae37eb192 to your computer and use it in GitHub Desktop.
Silly experiments with `RUN --mount`

Silly experiments with RUN --mount

relates to moby/moby#32507, moby/buildkit#442

Doing some silly experimenting with RUN --mount:

# syntax=docker/dockerfile:1

FROM alpine AS stage1
RUN mkdir -p /stage1-files
RUN echo "testing stage 1" > /stage1-files/s1-file

FROM alpine AS stage2
RUN mkdir -p /stage2-files
RUN echo "testing stage 2" > /stage2-files/s2-file

# The "utility" stage mounts "stage1" (readonly), and "stage2" (readwrite),
# processes files from "stage1", and writes the result to "stage2".
#
# The idea here is to have "utility" build-stages that have tools installed
# to manipulate other stages, without those tools ending up in the stage (layer)
# itself
FROM alpine AS utility
RUN --mount=from=stage1,dst=/stage1 --mount=from=stage2,dst=/stage2,readwrite cp -r /stage1/stage1-files /stage2/

# this "utility" stage processes files from the "stage1" stage, mounting it
# read-write to make modifications
FROM alpine AS utility2
RUN --mount=from=stage1,dst=/stage1,readwrite touch /stage1/stage1-files/s1-file2

# this of course works
FROM alpine AS utility3
RUN mkdir /utility3-files
RUN --mount=from=stage1,dst=/stage1 cp -r /stage1/stage1-files /utility3-files/

# this doesn't work: this still gives the original, unmodified layer from stage1
FROM stage1 AS attempt1
RUN apk add --no-cache tree
CMD tree /stage1-files

# this doesn't work for stage1 and 2: --mount still gives the original,
# unmodified layers from those stages
FROM alpine AS attempt2
RUN apk add --no-cache tree
RUN mkdir -p /results/stage1-result /results/stage2-result /results/utility3-result
RUN --mount=from=stage1,dst=/stage1 cp -r /stage1/stage1-files /results/stage1-result
RUN --mount=from=stage2,dst=/stage2 cp -r /stage2/stage2-files /results/stage2-result
RUN --mount=from=utility3,dst=/utility3 cp -r /utility3/utility3-files /results/utility3-result
CMD tree /results

Building works (no errors):

docker build --no-cache -t bla .

[+] Building 4.7s (18/18) FINISHED                                                                                                                                            
 => local://dockerfile (Dockerfile)                                                                                                                                      0.0s
 => => transferring dockerfile: 1.88kB                                                                                                                                   0.0s
 => local://context (.dockerignore)                                                                                                                                      0.0s
 => => transferring context: 02B                                                                                                                                         0.0s
 => docker-image://docker.io/tonistiigi/dockerfile:runmount20180618@sha256:576332cea88216b4bf20c56046fabb150c675be4a504440da11970bea501281b                              0.0s
 => => resolve docker.io/tonistiigi/dockerfile:runmount20180618@sha256:576332cea88216b4bf20c56046fabb150c675be4a504440da11970bea501281b                                  0.0s
 => => sha256:576332cea88216b4bf20c56046fabb150c675be4a504440da11970bea501281b 528B / 528B                                                                               0.0s
 => => sha256:d0fbaded5db6066249af00e1c83c06c976dc9ba74bfca3d5efee1c7856253aa3 1.58kB / 1.58kB                                                                           0.0s
 => local://dockerfile (Dockerfile)                                                                                                                                      0.0s
 => local://context (.dockerignore)                                                                                                                                      0.0s
 => CACHED docker-image://docker.io/library/alpine:latest                                                                                                                0.0s
 => /bin/sh -c mkdir -p /stage2-files                                                                                                                                    0.4s
 => /bin/sh -c mkdir -p /stage1-files                                                                                                                                    0.5s
 => /bin/sh -c mkdir /utility3-files                                                                                                                                     0.6s
 => /bin/sh -c apk add --no-cache tree                                                                                                                                   1.0s
 => /bin/sh -c echo "testing stage 2" > /stage2-files/s2-file                                                                                                            0.5s
 => /bin/sh -c echo "testing stage 1" > /stage1-files/s1-file                                                                                                            0.4s
 => /bin/sh -c mkdir -p /results/stage1-result /results/stage2-result /results/utility3-result                                                                           0.5s
 => /bin/sh -c cp -r /stage1/stage1-files /utility3-files/                                                                                                               0.5s
 => /bin/sh -c cp -r /stage1/stage1-files /results/stage1-result                                                                                                         0.4s
 => /bin/sh -c cp -r /stage2/stage2-files /results/stage2-result                                                                                                         0.4s
 => /bin/sh -c cp -r /utility3/utility3-files /results/utility3-result                                                                                                   0.5s
 => exporting to image                                                                                                                                                   0.0s
 => => exporting layers                                                                                                                                                  0.0s
 => => writing image sha256:3ef6a42407019d37b5d13c9be1685e3ce2fabdf4c4f3d3fa294fc64e7836ded7                                                                             0.0s
 => => naming to docker.io/library/bla                                                                                                                                   0.0s

Running the resulting image produces:

docker run --rm bla

/results
├── stage1-result
│   └── stage1-files
│       └── s1-file
├── stage2-result
│   └── stage2-files
│       └── s2-file
└── utility3-result
    └── utility3-files
        └── stage1-files
            └── s1-file

7 directories, 3 files

It's obvious from the above that I don't have a clue what/how the readwrite option is used (in combination with from=<stage|image>)

Silly experiment with mounting utilities into scratch

Being able to manipulate files in a build-stage that's created FROM scratch, by mounting a static binary.

The Dockerfile below mounts busybox:uclibc (which is statically linked) in a build-stage from scratch, performs an ls -la, and writes the output to a textfile /output.

The last build-stage copies that file to the final image.

While this example is a bit silly, it illustrates the possiblity to run tools in a "scratch" layer (e.g. to manipulate files).

# syntax=docker/dockerfile:1
FROM scratch AS one

# busybox will now be at /usr/bin/busybox (PATH is still taken into account)
RUN --mount=from=busybox:uclibc,dst=/usr/ ["busybox", "sh", "-c", "ls -la /usr/bin > /output"]

FROM busybox:uclibc
COPY --from=one /output /
CMD cat /output

Building the file;

$ docker build --no-cache -t scratchy -f Dockerfile3 .

[+] Building 2.0s (10/10) FINISHED                                                                                                                                            
 => local://dockerfile (Dockerfile3)                                                                                                                                     0.0s
 => => transferring dockerfile: 362B                                                                                                                                     0.0s
 => local://context (.dockerignore)                                                                                                                                      0.0s
 => => transferring context: 02B                                                                                                                                         0.0s
 => docker-image://docker.io/tonistiigi/dockerfile:runmount20180618@sha256:576332cea88216b4bf20c56046fabb150c675be4a504440da11970bea501281b                              0.0s
 => => resolve docker.io/tonistiigi/dockerfile:runmount20180618@sha256:576332cea88216b4bf20c56046fabb150c675be4a504440da11970bea501281b                                  0.0s
 => => sha256:576332cea88216b4bf20c56046fabb150c675be4a504440da11970bea501281b 528B / 528B                                                                               0.0s
 => => sha256:d0fbaded5db6066249af00e1c83c06c976dc9ba74bfca3d5efee1c7856253aa3 1.58kB / 1.58kB                                                                           0.0s
 => local://context (.dockerignore)                                                                                                                                      0.0s
 => local://dockerfile (Dockerfile3)                                                                                                                                     0.0s
 => CACHED docker-image://docker.io/tonistiigi/copy:v0.1.3@sha256:87c46e7b413cdd2c2702902b481b390ce263ac9d942253d366f3b1a3c16f96d6                                       0.0s
 => CACHED docker-image://docker.io/library/busybox:uclibc                                                                                                               0.0s
 => busybox sh -c ls -la /usr/bin > /output                                                                                                    0.4s
 => copy /src-0/output ./                                                                                                                                                0.5s
 => exporting to image                                                                                                                                                   0.0s
 => => exporting layers                                                                                                                                                  0.0s
 => => writing image sha256:a5926f41a2bc2b7088f29efe9677bd88c570ab0ad7f570109b75337ec35c8b71                                                                             0.0s
 => => naming to docker.io/library/scratchy                                                                                                                              0.0s

And running it (shows contents of /output);

$ docker run --rm scratchy
total 408300
drwxr-xr-x    2 0        0            12288 May 22 17:00 .
drwxr-xr-x    1 0        0             4096 Jul 23 10:35 ..
-rwxr-xr-x  391 0        0          1067344 May 22 17:00 [
-rwxr-xr-x  391 0        0          1067344 May 22 17:00 [[
-rwxr-xr-x  391 0        0          1067344 May 22 17:00 acpid
-rwxr-xr-x  391 0        0          1067344 May 22 17:00 add-shell
-rwxr-xr-x  391 0        0          1067344 May 22 17:00 addgroup
-rwxr-xr-x  391 0        0          1067344 May 22 17:00 adduser
-rwxr-xr-x  391 0        0          1067344 May 22 17:00 adjtimex
-rwxr-xr-x  391 0        0          1067344 May 22 17:00 ar
-rwxr-xr-x  391 0        0          1067344 May 22 17:00 arch
-rwxr-xr-x  391 0        0          1067344 May 22 17:00 arp
-rwxr-xr-x  391 0        0          1067344 May 22 17:00 arping
-rwxr-xr-x  391 0        0          1067344 May 22 17:00 ash
-rwxr-xr-x  391 0        0          1067344 May 22 17:00 awk
-rwxr-xr-x  391 0        0          1067344 May 22 17:00 base64
-rwxr-xr-x  391 0        0          1067344 May 22 17:00 basename
...

Update with SHELL

Setting the SHELL correctly, allows running commands "as usual"; busybox will be used to start a shell, but is not actually part of the image;

# syntax=docker/dockerfile:1
FROM scratch AS one
SHELL ["busybox", "sh", "-c"]

# busybox will now be at /usr/bin/busybox (PATH is still taken into account)
RUN --mount=from=busybox:uclibc,dst=/usr/ ls -la /usr/bin > /output

FROM busybox:uclibc
COPY --from=one /output /
CMD cat /output

Using the sym/hardlinks to busybox, even allows using /usr/bin/sh;

# syntax=docker/dockerfile:1
FROM scratch AS one
SHELL ["/usr/bin/sh", "-c"]

# busybox will now be at /usr/bin/busybox (PATH is still taken into account)
RUN --mount=from=busybox:uclibc,dst=/usr/ ls -la /usr/bin > /output

FROM busybox:uclibc
COPY --from=one /output /
CMD cat /output

Using src and dst

Everything can be done even simpler; --mount also has a src option; previous approach was because you cannot mount using / as dst, but that's not needed if src is set properly.

# syntax=docker/dockerfile:1
FROM scratch AS one

# mount busybox's /bin/ directory at /bin/
RUN --mount=from=busybox:uclibc,src=/bin/,dst=/bin/ busybox > /output

# which means there's now a regular shell available; provided by busybox
RUN --mount=from=busybox:uclibc,src=/bin/,dst=/bin/ ls -la /bin/ >> /output


FROM busybox:uclibc
COPY --from=one /output /
CMD cat /output

Here's with multiple sources mounted (just for fun);

# syntax=docker/dockerfile:1
FROM scratch AS one

# mount busybox's /bin/ directory at /bin/, and mount the docker CLI
RUN \
  --mount=from=busybox:uclibc,src=/bin/,dst=/bin/ \
  --mount=from=docker:uclibc,src=/usr/local/bin/,dst=/usr/local/bin/ \
  docker version > /output || true

FROM busybox:uclibc
COPY --from=one /output /
CMD cat /output
@rickywu
Copy link

rickywu commented Jan 8, 2021

@thaJeztah

Thanks for you recomendation, I noticed that I can reuse base layer

@ulope
Copy link

ulope commented Dec 9, 2022

Note that starting from 1.34 the official busybox image switched the default latest image to use glibc and dynamically linked binaries instead of static uclibc as before (see docker-library/busybox#155) which means that many of those mount tricks no longer work.
The easiest fix is to use the busybox:uclibc tag instead of latest.

@thaJeztah
Copy link
Author

@ulope thanks for the heads-up; I updated the examples to use busybox:uclibc

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment