An image tag is a readable name associated with an image reference. Unless a registry policy prevents reassignment, the same tag can later identify another artifact. Tags such as v1.4 and latest differ in naming convention, not in an inherent guarantee of immutability. Reproducing a deployment requires recording the artifact actually selected, together with the platform and runtime configuration that determined its behavior.
When it breaks
The word latest does not ask a registry to find its newest image or highest semantic version. It is the conventional default tag when a reference omits one. Publishing or tagging another version does not automatically move latest; someone or some automation must update that specific reference. Treating every push as an implicit latest update gives an incorrect picture of release behavior.
A tag’s local resolution can also differ from its current registry resolution. Pull policy, local image availability, and orchestration behavior determine when the registry is consulted. Two machines running a readable tag can therefore use different artifacts. Recording only the command text leaves uncertainty about what each machine actually fetched and which cached reference it retained.
Moving a tag does not rewrite the image selected by an already-created container. Restarting that container retains its configured image and writable state rather than recreating it from the tag’s new target. An update procedure that retags an image and merely restarts existing containers can therefore appear successful while continuing to run the old application.
Check it quickly
Run this Python script with a Docker builder and the pinned base available. The recorded environment used Docker 29.3.1 on Linux arm64. All tags are unique local names; nothing is pushed to a registry. Containers use no network, and cleanup removes only the experiment’s container and tags:
from pathlib import Path
from tempfile import TemporaryDirectory
import json,subprocess,uuid
BASE='python@sha256:528257d48c1da0dcecc2e725d1ae34498d60c965f1241e39cd6a85a8859bdf84'
def docker(*args,check=True):
result=subprocess.run(['docker',*args],capture_output=True,text=True,timeout=120)
if check:assert result.returncode==0,result.stderr
return result
def inspect(name,kind='image'):return json.loads(docker(kind,'inspect',name).stdout)[0]
name='xbcatm-tags-'+uuid.uuid4().hex[:10];tags=[name+':one',name+':two',name+':release',name+':latest'];container=name+'-old'
try:
with TemporaryDirectory(prefix=name+'-') as directory:
root=Path(directory)
(root/'Dockerfile').write_text(f'FROM {BASE}\nCOPY version.txt /version.txt\nCMD ["cat", "/version.txt"]\n')
for tag,version in zip(tags,['one','two']):
(root/'version.txt').write_text(version+'\n');docker('build','--network','none','-t',tag,str(root))
one=inspect(tags[0])['Id'];two=inspect(tags[1])['Id'];assert one!=two
docker('image','tag',tags[0],tags[2]);docker('image','tag',tags[0],name)
assert inspect(tags[2])['Id']==inspect(tags[3])['Id']==one
docker('create','--name',container,'--network','none',tags[2])
docker('image','tag',tags[1],tags[2])
assert inspect(tags[2])['Id']==two and inspect(tags[3])['Id']==one
assert inspect(container,'container')['Image']==one
assert docker('start','--attach',container).stdout.strip()=='one'
assert docker('run','--rm','--network','none',tags[2]).stdout.strip()=='two'
assert docker('run','--rm','--network','none',tags[3]).stdout.strip()=='one'
print('moving release changes newly created containers; the existing container still runs its original image')
print('omitting a tag selected latest, but updating release did not update latest')
docker('image','rm',tags[2]);assert inspect(tags[1])['Id']==two
print('removing one tag leaves another reference to the same image intact; all operations stayed local')
finally:
docker('rm','-f',container,check=False);docker('image','rm',*tags,check=False)
The script builds two images whose /version.txt contents are one and two. Their image identities differ. It points a release tag at the first image and tags that same image using a repository name with no explicit tag. Inspection confirms that the omitted tag became latest, and both references initially select the first image.
Before moving release, the script creates a container from it without starting the container. It then moves release to the second image. The release tag now identifies the second artifact, while latest still identifies the first. This isolates tag reassignment from the independent default-tag convention and demonstrates that related names do not automatically move together.
Starting the existing container prints one. Creating a new container from release prints two, while creating one from latest prints one. The container inspection also retains the original selected image identity. These assertions compare actual execution results, so the distinction does not depend solely on interpreting a table of tag names.
Removing the release tag leaves the second image available through its two tag. A tag is a reference, and removing one reference does not necessarily delete shared content or every other name. Image reclamation depends on remaining references and the engine’s storage rules; this experiment does not prune caches or measure physical space recovery.
The fixture compares identities returned by the current engine without assuming that every inspect Id universally means an OCI configuration digest. Modern image stores can expose different object levels. Registry indexes, platform manifests, image configurations, and layer blobs each have their own digests, so the object type and reference context must accompany an identity claim.
Promote the artifact that was tested
For registry deployments, record a repository-qualified digest reference from the publication workflow. A digest can identify an image index containing multiple platform-specific manifests; selecting the intended platform still matters. Retain the corresponding artifact and required blobs so a later rollback can actually retrieve them. A recorded hash cannot recover content that is no longer available.
Use tags as human navigation when useful, and enforce registry immutability rules if the release process promises that version tags never move. Verify the policy rather than inferring it from a version-shaped name. Digest pinning also does not prove who built an artifact or whether it is safe; provenance and behavioral validation answer those questions.
After deployment, compare the running workload’s selected artifact and expected behavior with the release record. An updated tag, a successful pull, and a running container are separate observations. The release is established by connecting them to the exact artifact that passed validation.
Sources
- Docker image tag, local tag creation and the default latest tag, checked 2026-09-12.
- Docker image pull, pulling repository artifacts by tag or digest, checked 2026-09-12.
- OCI image index 1.1.1, an index referencing platform-specific image manifests, checked 2026-09-12.
- OCI image manifest 1.1.1, manifest references to configuration and layer objects, checked 2026-09-12.