CertGrid CertGrid
Concepts·Docker

Docker Image Layers and Build Cache

Why instruction order decides whether your build takes one second or ninety. Watch the cache report CACHED, then break it with a one-byte change and watch which layers rebuild.

Dockerfiles and Builds Guide 17 of 46 Intermediate

Tested on the versions above. Layer sizes, digests and timings vary per run and per host. Match the shape of the output, not the exact strings.

One Docker host is all this guide needs. Nothing here depends on a second machine, and the hardware above is modest on purpose - a 2 core, 4 GB VM runs everything in this path.
Server NameIP AddressOSRolesCPURAMHDD
DOCKER01192.168.0.21Ubuntu 26.04 LTSDocker Host2 Core4 GB50 GB

Before you start

  1. Every instruction is a layer

    An image is a stack of read-only diffs. docker history shows them newest first, with the instruction that produced each and what it added. Metadata instructions such as CMD cost nothing; instructions that touch the filesystem cost real bytes.

    bash Example session
    docker history cg-hello:1.0 --format "table {{.CreatedBy}}\t{{.Size}}"CREATED BY                                      SIZECMD ["./hello.sh"]                              0BRUN /bin/sh -c chmod +x hello.sh # buildkit     12.3kBCOPY hello.sh . # buildkit                      12.3kBWORKDIR /app                                    8.19kBRUN /bin/sh -c apk add --no-cache curl # bui…   5.27MBADD alpine-minirootfs-3.22.5-x86_64.tar.gz /…   8.96MB

    Expected resultOne row per layer, with sizes attributable to specific instructions.

    Success conditionYou can see which instruction is expensive. Note chmod cost a whole 12.3kB layer just to flip a permission bit.

  2. A rebuild with nothing changed reuses everything

    BuildKit marks reused layers CACHED. Counting them is the quickest way to see how much work a build actually did.

    bash
    docker build -t cg-ctx:demo . 2>&1 | grep -c CACHED2

    Expected resultA non-zero count - every eligible layer was reused.

    Success conditionThe count matches the number of cacheable instructions. A count of zero means something invalidated the very first layer.

  3. Break the cache with one byte

    Change a single file that a COPY instruction reads. That layer's inputs differ, so it rebuilds - and crucially, so does every layer after it. Layers below the change are still reused, which is the entire basis of instruction ordering. That cascade is the whole reason instruction order matters. Put what changes least at the top - base image, system packages, dependency manifests, dependency install - and your source last. Copying source before installing dependencies discards the dependency cache on every commit: COPY . . then RUN npm ci rebuilds the install every time, while COPY package.json package-lock.json ./ then RUN npm ci then COPY . . keeps it cached.

    bash
    echo change > src/app.pydocker build -t cg-ctx:demo . 2>&1 | grep -E "CACHED"#6 CACHED

    Expected resultFar fewer CACHED lines than before - only the layers preceding the change survive.

    Success conditionThe count dropped. The surviving CACHED layer is everything below the file you touched - which is why the cheapest build optimisation available is reordering instructions.

  4. Force a clean build when you need to prove something

    --no-cache ignores every cached layer. It is slow on purpose. Use it to confirm a build is genuinely reproducible from scratch, or when you suspect a stale layer is hiding a problem - not as a habit.

    bash
    # slow by design - rebuilds every layer including package installsdocker build --no-cache -t cg-ctx:demo .# no CACHED lines will appear

    Expected resultA build with no CACHED lines at all.

    Success conditionEvery step runs. If the build fails only with --no-cache, a cached layer was masking a broken instruction.

Troubleshooting

Official sources