top of page
Search

When “Latest” Doesn’t Mean Latest: Container Image Tag and Digest

Updated: 2 days ago

Three boxes sit on a shelf. The left box has "latest," "6," "6.1," and "6.1.2" sticky notes attached. The middle has "beta" and "unstable." The right has "clearance."
Tags are more like these sticky notes than you might realize.

Introduction

This article is the third in a three-part series on container image names. Part 1 provides the background for the series and a discussion of the image registry. Part 2 describes the namespace and repository.


As a refresher, here’s a diagram of an example fully-qualified image name (FQIN) with the parts labeled:


Diagram of a fully-qualified image name

Throughout this series, we’re using the following image names as examples.


  • python:3

  • cgr.dev/chainguard/python

  • nvcr.io/nvidia/pytorch:25.03-py3

  • registry.gitlab.com/gitlab-org/gitlab-runner/gitlab-runner-helper:ubuntu-x86_64-latest


In today’s article, we’ll learn about the tag and digest.


Container Image Tags


The tag is the last part of an image name, separated from the rest of the name by a colon (:). The tag is a human-readable reference, or pointer, to a specific artifact within a repository. (See Part 2 for a discussion of the repository.)


Tags are unstructured text, although there are constraints on the maximum length and the characters that can be used. See the Open Container Initiative (OCI) Distribution Specification for more details.


Tags are typically used to describe the version of the software package the image provides. They can also describe the variant of the software (e.g., server vs client, runtime vs SDK, etc.) or the base image that was used (e.g., Debian, Ubuntu, Alpine, UBI, Distroless, Windows, etc.).


If the tag is not specified, the default is latest. As far as the author is aware, this is a de facto standard and not defined in any specification. However, here are some implementation references for completeness:



This may be the most confusing of the default values we’ve discussed. To many, "latest" implies an automatically-updated pointer to the most-recently-pushed artifact. This is, for example, how Golang processes @latest, how most package managers (e.g., pip, npm) behave when no version is specified, and how Maven SNAPSHOTs work. However, this is not the case for container images.


This leads me to my first important point: the tag latest is the same as any other tag. Other than being the default value, there is nothing special about it. It must be explicitly defined in the registry.


Important Point 1: Tags Are Explicitly Defined


This is where it’s easy to get tripped up. Tags must be explicitly defined and created. There is no such thing as a meta-tag. Tags are not computed on the server side, and they are not semantically interpreted on the client side. This includes the latest tag!


The image publisher must explicitly create and push every tag, including latest, to the registry. If they don’t, the tag will simply not exist.


This is very different from package managers you may be used to, as I pointed out above.

Also, if a publisher doesn’t continually update the latest tag (or any other tag) to point to new versions, it will continue to point to the same artifact.


This leads me to my next important point: tags are mutable.


Important Point 2: Tags Are Mutable


Remember our definition of a tag: a human-readable reference, or pointer, to a specific artifact. An image publisher can update these references at any time. This means that reproducibility is extremely hard to guarantee when using tags.


This doesn’t mean that a publisher cannot treat certain tags as immutable. It’s possible to define tags on the server side that will be flagged as immutable. But this is completely up to the publisher, and you must consult their documentation to determine whether they provide “immutable” tags.


This is also different from package managers you may be used to, where retrieving a specific, locked package version will always result in downloading the same artifact. This is due to an explicit contract between clients and the package registry, where the registry disallows re-publishing versions and forces publishers to bump new releases to new version numbers. Container image registries do not enforce such a contract unless the publisher explicitly configures it.


Mutable tags also have implications for running systems. If a tag is updated regularly, there’s a risk that a running deployment might use different images when its instances do not all start at the same time (e.g., scale-out or pod evictions). You also run the risk that a tag is cached locally and never gets re-pulled when the upstream tag is updated, leading to using outdated applications.


Part of the reason for this is due to what we mentioned above: tags must be explicitly defined and created. This means that enabling users to easily fetch the “latest” version or the latest release of a major version requires the publisher to update many tags on every new release, which would not be possible with globally immutable tags.


And this brings me to my next important point: there is, generally, no semantic version matching with container images.


Important Point 3: There Is No Semantic Version Matching


Let’s look at what semantic versioning is and how it does or does not apply to container images.


For those unfamiliar, you can check out the Semantic Versioning specification. This specification “proposes a simple set of rules and requirements that dictate how version numbers are assigned and incremented.”


With traditional package managers, we can specify version constraints and get the latest matching version of a package (for example: Python, npm, OpenTofu). The client tools will query the repository for all available versions and decide which version to use based on documented rules.


This concept does not apply to container image tags. The main reason for this is that tags are unstructured text and do not have to conform to semantic version numbers. This makes it impossible to reliably compute versions from tags and compare them.


Another reason is that, even for repositories that do adhere to semantic versioning, tags contain additional information that is unrelated to the version number.


Example


Let’s take a look at Eclipse Temurin (a popular Java™ runtime) as an example. In addition to version numbers, the tags describe:


  • whether the image contains a runtime (“jre”) or development kit (“jdk”),

  • the base image (“alpine,” “noble,” “ubi9,” etc.), and/or

  • for some base images, the version of the base image (Alpine 3.20 or 3.21, Nanoserver LTSC 2022 or 2025, etc.).


A human may be able to pick the correct version based on their requirements (“I need the JRE, the latest version 21, with the latest available Ubuntu base …”) and a human may be able to code up decision logic for this particular repository. But building this functionality into client tooling for general usage is intractable.


Now, Eclipse Temurin (and other publishers) provide some ability to abstract these away. For example, they provide tags for “21-*” that point to the latest version 21 release. They also provide tags for “21-jre” and “21-jdk” if you don’t particularly care about the base image. But there are tradeoffs: you’re at the mercy of the base images the publisher decides to use for these abstracted tags. And, depending on your application, there could be breaking differences between them.


The Digest


A digest is a cryptologic hash of an artifact. It can be used as an immutable reference to an artifact when a mutable tag will not suffice. You can read more about digests in the OCI Image Specification.


Using docker.io/library/python:3 as an example, we could reference this image by digest as follows:


  • docker.io/library/python@sha256:a4b2b11a9faf847c52ad07f5e0d4f34da59bad9d8589b8f2c476165d94c6b377


Beautiful, right?


Unlike tags, digests are immutable and will always point to the same, immutable image. If you are concerned about reproducibility, you can use the digest to ensure you always use the exact same image. Most container engines will log this digest somewhere, as well, so you can use it to help troubleshoot differences in image behavior or content.


However, there is one catch that is not immediately apparent. If an image becomes untagged (all the tags that point to it are updated or removed), its registry might prune it. This would render future builds or deployments using this digest impossible. This is up to the registry and/or the publisher to implement, so the risk of this happening can vary.


Our Example Image Names


Let’s look at our example image names one more time:


  • For python:3, the tag is 3.

  • For cgr.dev/chainguard/python, the tag is latest.

  • For nvcr.io/nvidia/pytorch:25.03-py3, the tag is 25.03-py3.

  • For registry.gitlab.com/gitlab-org/gitlab-runner/gitlab-runner-helper:ubuntu-x86_64-latest, the tag is ubuntu-x86_64-latest.


Putting this all together, here are the FQINs of our example image names:


  • docker.io/library/python:3

  • cgr.dev/chainguard/python:latest

  • nvcr.io/nvidia/pytorch:25.03-py3

  • registry.gitlab.com/gitlab-org/gitlab-runner/gitlab-runner-helper:ubuntu-x86_64-latest


Recommendations


Now that we’ve learned a lot about container image tags, allow me to make some recommendations based on my own experience.


If you’ve been reading my posts so far, you can probably guess my first recommendation: always specify the tag, even when it is the default.


You can also guess my second recommendation: always consult the vendor documentation to understand what tags are available and what the differences are between them.


Third, be as specific as you reasonably can with image tags. There is a tradeoff here between reproducibility and maintenance load.


  • You could use a very specific tag to pin a specific patch version, which would provide a stronger guarantee of reproducibility. But it would require you to keep that tag updated over time.

  • You could use a mutable tag for a major version, which would make your application easier to maintain and update. But this introduces the risk of not being able to reproduce a build or behavior, which makes the application harder to debug.

  • You could use a digest to pin a specific image, which would provide an even stronger guarantee of reproducibility. But the tradeoffs are potentially worse than using a tag. First, there’s the possibility that the image becomes untagged and the registry prunes it, making builds impossible. Second, it’s much harder to capture the intent when using a digest. It requires the developer to maintain documentation describing what version, base image, etc. should be used when updating the digest.


This is where being reasonable comes in. Does your team have the bandwidth to track new releases of images and update the codebase? Do you already have a tool like Dependabot or Renovate in your workflow? What is the risk of using a mutable major or minor version tag? Is your orchestration system configured to re-pull updated tags on a regular basis?


As an example, it might make sense to use the latest tag for a security scanning job in a CI pipeline, to ensure you’re always using the latest version of the scanner. But when building a widely-distributed application, you might want to pin a very specific base image to make it easier to support and debug, just like you would pin other dependencies.


Unfortunately, I can’t give more specific advice in this article regarding which tags to choose for what purpose. This would require a deeper dive into choosing a proper base image, which may be a future blog post!


Conclusion


  • Wow, that little tag at the end of the image name is actually pretty complex, huh? Let’s reflect on what we learned today.

  • We learned that the tag is a human-readable reference, or pointer, to a specific artifact within a repository.

  • We learned that the digest is an immutable hash of a specific image.

  • We learned that tags are explicitly defined at publishing time and there are no “meta-tags.”

  • We learned that tags are mutable.

  • We learned that tags are free-form text (with constraints) and do not have to conform to semantic versioning.

  • We learned that container tooling does not support version-matching constraints to reference images.

  • We learned a couple recommendations for specifying an image tag.


Thanks for sticking with me through this three-part series! I’ll see you in my next post!


Jason Miller is a Senior Platform Engineer at Clarity Business Solutions. You can find him on LinkedIn.


The mention of any image name, registry, or vendor does not constitute a recommendation or endorsement. These are solely provided as examples for demonstration purposes.

 
 
 

Comments


bottom of page