TT Lab
Get started
Learn Learning paths Courses

Building Images

Finding Where the Cache Breaks

Continue in TT Lab

This lab runs on a real VM

This box is not a Pod but a virtual machine launched by KubeVirt. A separate Linux kernel runs in it, systemd actually manages services, and docker is a real Docker engine, not an imitation. A container started with docker run becomes a real process, and both docker exec and docker logs work as usual.

This lab used to run inside a Pod. That box had dropped every kernel capability, so the step that starts a container was blocked, and you learned by working around it and unpacking image archives by hand. The workaround is no longer needed.

There are two things to know.

Goal

You prove by image ID where the cache breaks, and confirm by the size difference between two images that deleting cannot reduce the size on a union filesystem.

Why it matters

The problem "the build is slow" is overwhelmingly often not a matter of turning the cache settings on and off but a matter of order. Once you understand just one fact, that cache keys are chained to the parent digest, it becomes obvious that the point to fix is always "the single line where the cache first breaks". There is no need to touch anything above it, and there is no way to touch anything below it. The same goes for size. Layers are not rewound, so what must not be in the final image should never have been created in that layer in the first place.

Steps

  1. Create /root/build2, count the layers of nginx:1.27-alpine and write only the number to /root/build2/nginx-layers.txt.
  2. Save the per-layer history of alpine:3.20 without truncation to /root/build2/alpine-history.txt. The table that docker history shows is exactly the history array of the image config blob. In this box, you read the same values with skopeo inspect --config oci-archive:/opt/images/alpine_3.20.tar | jq -r '.history[]'.
  3. In /root/build2, create deps.txt and src/main.txt, and write a Dockerfile that COPYs each of the two files. Build it as labhub/cache:v1 and write the image ID to /root/build2/id1.txt, then build again without changing anything and write the ID to /root/build2/id2.txt. The two values must be the same.
  4. Change the contents of deps.txt, build it as labhub/cache:v2 and write the ID to /root/build2/id3.txt. It must differ from id1.txt.
  5. Create /root/build2/ordered.Dockerfile so that COPY deps.txt comes before COPY src/, and build it as labhub/cache:v3.
  6. Create /root/build2/big.log and put a rule that excludes log files into /root/build2/.dockerignore. With a Dockerfile that copies the whole context to /ctx, build labhub/cache:v4; the image must then contain /ctx/deps.txt but not /ctx/big.log.
  7. Build the image that creates a 20 MB file and deletes it in the next RUN as labhub/fat:bad, and the image that deletes it within the same RUN as labhub/fat:good.
  8. In /root/build2/cache.md, write the three lines below with the actual values.
bad_mb=<labhub/fat:bad 크기(MB)>
good_mb=<labhub/fat:good 크기(MB)>
diff_mb=<두 값의 차>

Notes

Count the layers of an image

Create /root/build2, count the layers of nginx:1.27-alpine and write only the number to /root/build2/nginx-layers.txt.

RootFS.Layers in the inspect result is an array. You can get its length with jq.

Look into the command of each layer

Save the per-layer history of alpine:3.20 without truncation to /root/build2/alpine-history.txt. The table that docker history shows is exactly the history array of the image config blob. In this box, you read the same values with skopeo inspect --config oci-archive:/opt/images/alpine_3.20.tar | jq -r '.history[]'.

There is a subcommand that shows which command created each layer and how much space it takes. Save it without truncation.

If nothing changed, it is the same image

In /root/build2, create deps.txt and src/main.txt, and write a Dockerfile that COPYs each of the two files. Build it as labhub/cache:v1 and write the image ID to /root/build2/id1.txt, then build again without changing anything and write the ID to /root/build2/id2.txt. The two values must be the same.

Build twice and leave each image ID in a file. If every layer hits the cache, the resulting image is identical too.

Try breaking an earlier layer

Change the contents of deps.txt, build it as labhub/cache:v2 and write the ID to /root/build2/id3.txt. It must differ from id1.txt.

If you modify the file COPYed at the very top, everything below it runs again. Build with a new tag and compare the IDs.

Arrange by frequency of change

Create /root/build2/ordered.Dockerfile so that COPY deps.txt comes before COPY src/, and build it as labhub/cache:v3.

Put what rarely changes (the dependency list) at the top and what changes often (the source) below it. Grading compares the line numbers of the two COPYs.

Exclude from the build context

Create /root/build2/big.log and put a rule that excludes log files into /root/build2/.dockerignore. With a Dockerfile that copies the whole context to /ctx, build labhub/cache:v4; the image must then contain /ctx/deps.txt but not /ctx/big.log.

Put the exclusion rules file at the root of the build context. If you exclude files you need, the build breaks.

Evidence that deleting does not shrink it

Build the image that creates a 20 MB file and deletes it in the next RUN as labhub/fat:bad, and the image that deletes it within the same RUN as labhub/fat:good.

Build the image that created the file and deleted it in the next RUN and the image that created it and deleted it within the same RUN, and compare their sizes.

Summarize the measurements

In /root/build2/cache.md, write the three lines below with the actual values.

All three values are calculated from the actual image sizes. Write integer MB based on 1 MB = 1048576 bytes.