CertGrid CertGrid
Concepts·Podman

Podman Image Layers and Build Cache

Rebuilding an unchanged Containerfile produces the same image ID, so `app:1` and `app:2` are one image with two tags. Change the copied file and the cache breaks at `COPY` and stays broken for every instruction below it.

Building Images Guide 18 of 47 Intermediate

Written against the versions above. Podman follows the distribution here rather than a vendor repository, so the version you get is the one Ubuntu shipped. The commands are stable across 5.x.

Every command on this page ran on podman01.
Server NameIP AddressOSRolesCPURAMHDD
PODMAN01192.168.0.24Ubuntu 26.04 LTSPrimary Container Host2 Core4 GB50 GB

Before you start

  1. The same build twice

    Change nothing and build again under a new tag. Every step reports:

    --> Using cache 6180d06f43b1292a5b2b48b89ab2c48f...

    The expensive apk add did not run. Neither did anything else.

    Then read the last lines carefully - the build reports Successfully tagged localhost/app:2 and localhost/app:1, and prints the same final ID as before. Nothing was created. You added a second tag to the image you already had.

    The cache key is what an instruction *would produce*: the parent layer plus the instruction text plus, for COPY, the contents of the files. Identical inputs mean an identical layer, so there is no reason to build one.

    bash Example session
    cd ~/app && podman build -t app:2 .STEP 1/5: FROM docker.io/library/alpine:3.22STEP 2/5: RUN apk add --no-cache curl--> Using cache 6180d06f43b1292a5b2b48b89ab2c48ffded5f0f56c1d4981891188f1bacc8ad--> 6180d06f43b1STEP 3/5: COPY hello.sh /usr/local/bin/hello--> Using cache 1f4fdfd3f2281e8afa6a39098bd3dd3721db1291dcfd3f4b1b7a90e96258326c--> 1f4fdfd3f228STEP 4/5: RUN chmod +x /usr/local/bin/hello--> Using cache ab7f293673bf308fe705a9cfc7a571a3c82491eee09dc5f04ecb9665ff101241--> ab7f293673bfSTEP 5/5: CMD ["/usr/local/bin/hello"]--> Using cache c154b5c3995cb4c11724e001b4be0eec6051a98131ed502f345445293ac8f92fCOMMIT app:2--> c154b5c3995cSuccessfully tagged localhost/app:2Successfully tagged localhost/app:1

    Expected resultUsing cache on every step, and the same final image ID as app:1.

    Success conditionA rebuild produced no new image.

  2. Break it, and watch the cascade

    Change one line in hello.sh and rebuild:

    STEP 2/5: RUN apk add --no-cache curl
    --> Using cache 6180d06f43b1...      <- still cached
    STEP 3/5: COPY hello.sh /usr/local/bin/hello
    --> 9779093186b3                     <- rebuilt
    STEP 4/5: RUN chmod +x /usr/local/bin/hello
    --> 1582b6a9092a                     <- rebuilt too

    Step 2 is still cached because nothing above it changed. Step 3 rebuilt because the file contents are part of its key. And step 4 rebuilt even though its instruction text is identical, because its parent layer changed - and a layer's identity includes what it was built on.

    That cascade is the single most useful thing to know about build performance. The cache is not per-instruction, it is a chain: the first instruction whose inputs change invalidates everything below it.

    Which gives the ordering rule. Put the instructions that rarely change at the top and the ones that change every commit at the bottom. COPY your dependency manifest and install dependencies, *then* copy your source - because source changes on every build and dependencies do not.

    bash Example session
    cd ~/app && printf '#!/bin/sh\necho "hello again from $(hostname)"\n' > hello.shcd ~/app && podman build -t app:3 .STEP 1/5: FROM docker.io/library/alpine:3.22STEP 2/5: RUN apk add --no-cache curl--> Using cache 6180d06f43b1292a5b2b48b89ab2c48ffded5f0f56c1d4981891188f1bacc8ad--> 6180d06f43b1STEP 3/5: COPY hello.sh /usr/local/bin/hello--> 9779093186b3STEP 4/5: RUN chmod +x /usr/local/bin/hello--> 1582b6a9092aSTEP 5/5: CMD ["/usr/local/bin/hello"]COMMIT app:3--> 3add4ec8193bSuccessfully tagged localhost/app:33add4ec8193b5c90f4bdbfad26fa4ba259222ea21f5bcb500daba13d4ca11444podman run --rm app:3hello again from 93362a62e3a9

    Expected resultStep 2 cached, steps 3 and 4 rebuilt, and a new final image ID.

    Success conditionYou can predict which steps a given edit will invalidate.

  3. Two tags, one image, and one different

    The image list makes the whole lesson visible at once:

    localhost/app  3  3add4ec8193b
    localhost/app  2  c154b5c3995c
    localhost/app  1  c154b5c3995c

    app:1 and app:2 share an ID - one image, two names, occupying one image's worth of disk. app:3 is genuinely different.

    Practical consequences worth holding onto:

    • A tag is not a version. Two tags can be the same bytes, and one tag can be moved to different bytes tomorrow. When it has to be the same image later, record the digest - see guide 12.
    • podman rmi app:1 will not free space while app:2 points at the same image; it just removes a name.
    • A build that produces no new ID did nothing, which is a fast way to check whether the change you thought you made actually reached the context.
    bash Example session
    podman images --format 'table {{.Repository}} {{.Tag}} {{.ID}}' appREPOSITORY     TAG         IMAGE IDlocalhost/app  3           3add4ec8193blocalhost/app  2           c154b5c3995clocalhost/app  1           c154b5c3995c

    Expected resultThree rows, two of which share an image ID.

    Success conditionYou can tell from an image list which tags are the same image.

Troubleshooting

Official sources