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
@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