'Building a container image and re-using pulled layers

Suppose there is a container image published to a Docker registry, and I want to rebuild the image with modifications while re-using as many of the published layers as possible.

Concretely, suppose the image foo/bar is built from this Dockerfile, and I only want to modify the layer that contains script.sh:

FROM ubuntu:focal
COPY script.sh .

Even though pulling the image downloads the layers for ubuntu:focal, when I re-build my local machine may resolve the ubuntu:focal tag to another version, producing a new image with no common layers with the one I pulled.

                                   6a9e8d7 <foo/bar:new>
           <foo/bar:old> c7632a5      |
                            |         |
                            +----+----+
                                 |
                              3b15784 <ubuntu:focal (then)>
                                 |
                                ...
           
           
                             DESIRABLE




        <foo/bar:old> c7632a5          48fead0 <foo/bar:new>
                         |                |
                         |                |
<ubuntu:focal (then)> 3b15784          9a634f5 <ubuntu:focal (now)>
                         |                |
                        ...              ...


                            UNDESIRABLE

The desired outcome could possibly be achieved by looking at the pulled layers and tagging the correct one (3b15784) as ubuntu:focal before building. But I'm not sure if Docker exposes enough information to do this in an automatic way.



Solution 1:[1]

Maybe Docker supports this built in with --cache-from:

docker pull foo/bar:old
docker build . \
    --build-arg BUILDKIT_INLINE_CACHE=1 \
    --cache-from foo/bar:old
    --tag foo/bar:new

Solution 2:[2]

As a workaround, I could explicitly include the base image's digest as a label on the built image:

FROM ${UBUNTU_IMAGE_ID:-ubuntu:focal}
COPY script.sh .

Then I would build with:

# First build
UBUNTU_IMAGE_ID=$( \
    docker inspect \
        --format '{{ index .RepoTags 0 }}@{{ index . "Id" }}' \
        ubuntu:focal \
)

# Subsequent builds
docker pull foo/bar:old
UBUNTU_IMAGE_ID=$( \
    docker inspect \
        --format '{{ index .Config.Labels "ubuntu_image_id" }}' \
        foo/bar:old \
)


docker build . \
    --build-arg "UBUNTU_IMAGE_ID=${UBUNTU_IMAGE_ID}" \
    --label "ubuntu_image_id=${UBUNTU_IMAGE_ID}" \
    --tag foo/bar:new

However, a more elegant solution would definitely be welcome, especially one that does not involve any specific assumptions about how the original pulled image was built.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 rgov
Solution 2