How to Use Container Image Digests to Improve Reproducibility

Video demonstration of using digests with Chainguard Images

Tools used in this video

Commands used

docker pull
docker manifest inspect
crane digest --full-ref
docker pull


FROM AS build 

RUN CGO_ENABLED=0 go build -o /bin/server ./src

FROM AS prod

COPY --from=build /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]


0:05 You might have heard the advice to pin to a digest when using container images.

0:10 But what does this mean?

0:11 Why is it useful and how can you do it?

0:15 So a digest is a content based hash of a unique container image.

0:19 No two container images can have the same digest.

0:23 If you pull an image by the digest, you are guaranteed to get exactly the same image each time.

0:30 And this contrasts with tags like latest or 3.0 which are continually updated and changed.

0:37 So you run pull twice, you might well get a different image.

0:41 And this has implications for reproducibility.

0:46 If images are changing how can I be sure if it works for me it will work for anybody else or myself in the future?

0:52 So what’s the easiest way to get the digest of an image?

0:56 You can run Docker pull and grab it directly from there, for example.

1:02 And you can see the digest right here, but do note that this digest will refer to the index of the image which will list different images for different platforms.

1:15 And most likely this is what you want.

1:18 But we can see what I mean by using the Docker manifest inspect tool.

1:28 And we’ll use this digest and we’ll pipe it through jq.

1:40 And so note that this is the index of the image and it actually lists two more digests that point to actual platform specific images.

1:48 In this case, the arm64 image and the amd64 image.

1:54 If you wanted the image for a specific platform, you could use the address listed here, but not that that might break for some people.

2:02 So make sure you know what you’re doing before using that.

2:06 And I should also mention the crane tool which is a little bit easier to use in scripts as you don’t have to parse the output. If I can spell.

2:16 There we go.

2:18 So here I’ve run crane digest.

2:20 I’ve asked for the full reference which makes it output the beginning part again.

2:24 So we got the digest for the node image as a single line in output.

2:29 And you can also pass a `–platform`` argument which will return the digest for specific platform.

2:38 So I could ask for the arm64 platform and get this digest back.

2:42 OK.

2:43 So now that we have the digest, how can we use it?

2:46 Well, the most obvious way is to just do a Docker pull.

2:53 So here we’ve done a docker pull on node:latest, using this above digest and that works.

3:01 We get back exactly the same image.

3:03 One of the interesting things there and you might have noticed as I went backwards from history is that we can change this tag here.

3:10 It doesn’t, and it turns out Docker or Docker Hub and registries don’t actually care what this tag is when you specify an digest - that part gets ignored.

3:21 So we can put anything at all there and you can use it for metadata.

3:26 But where you’re most likely to use a digest is in the configuration file like a Dockerfile, a compose file or Kubernetes yaml file.

3:35 So I have an example here using the Dockerfile here.

3:39 We’ve pinned the version of the Go compiler.

3:43 And what that means is every time I run the docker build, I’ll be using exactly the same Go compiler.

3:50 So I’m absolutely sure that nothing’s changed in the Go compiler that might cause this build to fail.

3:56 And by using digest in Dockerfiles, we make the whole process much more reproducible; things won’t break because the underlying image has changed in this case.

4:08 And that’s really about it.

4:09 We’ve looked at what our digest is, how you can get it and how you can use it to improve reproducibility.

Last updated: 2023-08-10 15:21