Finding Where the Cache Breaks
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.
- The first start takes a little over a minute. The VM boots and installs Docker, so it is slower than a Pod lab (usually 40 seconds).
- There is no browser preview. Only one grading port is open for connections into the VM. If you start a web server, check it with
curlfrom inside the VM.
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
- Create
/root/build2, count the layers ofnginx:1.27-alpineand write only the number to/root/build2/nginx-layers.txt. - Save the per-layer history of
alpine:3.20without truncation to/root/build2/alpine-history.txt. The table thatdocker historyshows is exactly thehistoryarray of the image config blob. In this box, you read the same values withskopeo inspect --config oci-archive:/opt/images/alpine_3.20.tar | jq -r '.history[]'. - In
/root/build2, createdeps.txtandsrc/main.txt, and write aDockerfilethat COPYs each of the two files. Build it aslabhub/cache:v1and 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. - Change the contents of
deps.txt, build it aslabhub/cache:v2and write the ID to/root/build2/id3.txt. It must differ fromid1.txt. - Create
/root/build2/ordered.Dockerfileso thatCOPY deps.txtcomes beforeCOPY src/, and build it aslabhub/cache:v3. - Create
/root/build2/big.logand put a rule that excludes log files into/root/build2/.dockerignore. With a Dockerfile that copies the whole context to/ctx, buildlabhub/cache:v4; the image must then contain/ctx/deps.txtbut not/ctx/big.log. - 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 aslabhub/fat:good. - 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
- You get the image ID with
docker image inspect <이미지> | jq -r '.[0].Id'and the size in bytes withjq -r '.[0].Size'(put the image name where the placeholder is). - You can make a 20 MB file with
dd if=/dev/zero of=/big bs=1M count=20. - Common mistake 1: if you touch a file between builds in step 3, the ID changes.
- Common mistake 2: the MB in step 8 is based on 1048576 bytes. Write an integer with the decimals dropped.
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.