“Using ‘latest’ is convenient, but why are we told not to use it in practice?” Today, we’ll uncover the reasons.
>
π― What This Article Covers
- What happens when container image tags are mutable
- What an Immutable tag is and why it’s considered best practice
- Common tagging strategy patterns used in practice (SemVer, Git SHA, date-based)
- How to enforce Tag Immutability in ECR, GCR, and Harbor
- Examples of automating immutable tags in CI/CD pipelines
π Introduction / Background
When first learning about containers, everyone starts with a single line: docker pull nginx:latest. It’s simple, seems to always get the latest version, and is intuitively clear.
However, there are countless cases where using the ‘latest’ tag as-is in a real operational environment has led to trouble.
It definitely worked yesterday, but when I deployed it today, the app suddenly crashed.
A significant portion of the causes for this statement is due to mutable tags. The core problem is that the image content changes despite having the same tag name.
As the container ecosystem has matured, managing image tags immutably has become the de facto industry standard. Whether you delve into Kubernetes, GitOps, or DevSecOps, you will eventually encounter this principle.
π Mutable vs Immutable Tags: What’s the Exact Difference?
What is a Mutable Tag?
It’s a state where you can overwrite a different image with the same tag name.
# 10 AM build
docker build -t myapp:latest .
docker push myapp:latest
# β latest = commit abc123 image
# 3 PM build (after code change)
docker build -t myapp:latest .
docker push myapp:latest
# β latest = commit def456 image (morning image is gone)
Besides ‘latest’, tags like ‘stable’, ‘production’, or ‘v1’ also become mutable if overwritten in the same way.
What is an Immutable Tag?
It’s a policy where a tag, once pushed, can never be overwritten by a different image.
# Tag with specific Git SHA or version
docker build -t myapp:1.4.2 .
docker push myapp:1.4.2
# β 1.4.2 is permanently fixed to this image
# When attempting to push with the same tag later
docker push myapp:1.4.2
# β ERROR: Tag immutability enabled. Cannot overwrite existing tag.
The key is that “a tag = a unique identifier for a specific image”.
β‘ Practical Problems Created by Mutable Tags
1οΈβ£ Irreproducible Build/Deployment Environments
Using the ‘latest’ tag means that the image deployed today might be different from the image you attempt to roll back to a week later. If docker pull myapp:latest has been overwritten in the interim, the rollback itself loses its meaning.
2οΈβ£ Difficulty in Security Audits
When you need to track “which libraries were included in this image?”, mutable tags fail to provide evidence. When a CVE emerges, you cannot prove “exactly what image was deployed at that time”.
3οΈβ£ Caching Issues in Kubernetes
Kubernetes caches images with the same tag on nodes. With imagePullPolicy: IfNotPresent, if the tag is the same, it will continue to use the existing cached image without realizing a new image has been updated.
# Example of a dangerous setting
spec:
containers:
- name: myapp
image: myapp:latest # β mutable tag
imagePullPolicy: IfNotPresent # β Cache first, don't always pull
4οΈβ£ Race Conditions During Concurrent Deployments
In a multi-instance environment, if ‘latest’ is pulled concurrently, different instances might end up running different images. This makes troubleshooting failures an absolute nightmare.
ποΈ Common Immutable Tag Strategies in Practice
Strategy 1: Git SHA-based (Strongest Traceability)
# CI Pipeline Example (GitHub Actions)
IMAGE_TAG=$(git rev-parse --short HEAD) # ex: a3f5c12
docker build -t myapp:${IMAGE_TAG} .
docker push myapp:${IMAGE_TAG}
| Pros | Cons |
| 1:1 mapping of commit to image | Difficult for humans to read |
| Perfect traceability | Hard to grasp version meaning |
### Strategy 2: SemVer (Semantic Versioning) based
# v1.4.2 format
docker build -t myapp:1.4.2 .
docker push myapp:1.4.2
The MAJOR.MINOR.PATCH system allows for intuitive representation of change scope, which is beneficial for team communication.
Strategy 3: Git SHA + SemVer Concurrently (Best Practice)
VERSION="1.4.2"
GIT_SHA=$(git rev-parse --short HEAD)
# SemVer tag: for releases
docker push myapp:${VERSION}
# SHA tag: for traceability
docker push myapp:${GIT_SHA}
# 'latest' is for reference only (dev/test environments)
docker push myapp:latest
This way, you can always trace back “exactly which commit 1.4.2 corresponds to”.
Strategy 4: Date + Build Number based
# 20260414-001 format
TAG=$(date +%Y%m%d)-${BUILD_NUMBER}
docker push myapp:${TAG}
This is suitable for teams whose release cycle is date-centric.
π» Enforcing Tag Immutability per Registry
AWS ECR
aws ecr put-image-tag-mutability
--repository-name myapp
--image-tag-mutability IMMUTABLE
Or with Terraform:
resource "aws_ecr_repository" "myapp" {
name = "myapp"
image_tag_mutability = "IMMUTABLE" # MUTABLE is the default, so it must be explicitly specified
image_scanning_configuration {
scan_on_push = true
}
}
Harbor (Self-hosted Registry)
Harbor UI β Project Settings β Add “Immutable Tags” rule:
# Tag pattern: only apply immutability to tags starting with v*
Tag Pattern: v*
Repository Pattern: **
Google Artifact Registry
gcloud artifacts repositories update myapp-repo
--location=us-central1
--update-immutable-tags=true # Enable tag immutability policy
π€ Automating Immutable Tags in GitHub Actions CI/CD
name: Build and Push
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set image tag
id: tag
run: |
# Short SHA + date combination
echo "IMAGE_TAG=$(date +%Y%m%d)-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Build image
run: docker build -t ${{ secrets.ECR_REGISTRY }}/myapp:${{ env.IMAGE_TAG }} .
- name: Push image
run: docker push ${{ secrets.ECR_REGISTRY }}/myapp:${{ env.IMAGE_TAG }}
- name: Update Kubernetes manifest
run: |
# GitOps approach: Replace tag in manifest with new image tag
sed -i "s|image: myapp:.*|image: myapp:${{ env.IMAGE_TAG }}|g" k8s/deployment.yaml
git commit -am "chore: update image tag to ${{ env.IMAGE_TAG }}"
git push
This way, a unique tag is automatically generated with each deployment, and a record is kept in the Kubernetes manifest. Later, you can check “what image was deployed at that time” through the Git history.
β οΈ Cautions / Common Mistakes
π« Do not use ‘latest’ in production environments It’s acceptable for convenience in development and testing, but absolutely forbidden in staging and production.
π« Insufficient handling of tag conflicts after Immutable setting If you re-push the same tag, the registry will return an error. The tag generation logic in your CI pipeline must be designed to be unique. (What if the same commit is built twice? β SHA is the same β error occurs β combination with build number is recommended)
π« Confusing digest with tag An image digest (sha256:abc…) is a hash of the image content and is inherently immutable. A tag being immutable means that the tag name must always point to the same digest.
π« Managing only by convention without registry policies “We promised not to use ‘latest’” is not enough. Registry-level enforcement policies like ECR Immutability are necessary to prevent mistakes at the source.
β Summary / Conclusion
| Category | Mutable Tag | Immutable Tag |
| Reproducibility | β Same tag β Same image | β Same tag = Same image |
| Security Audit | β History untraceable | β Full traceability of deployed images |
| Rollback | β Image might disappear | β Always revert to exact version |
| Collaboration | β Potential for confusion | β Clear communication |
| Operational Risk | π΄ High | π’ Low |
Container image tag immutability is not just a simple convention; it is a core principle that forms the foundation of GitOps, DevSecOps, and reproducible builds. Turn on the Immutability policy in your registry right now and apply Git SHA-based tag automation in your CI pipeline.
As a next step, extending to image integrity verification with Image Signing (Cosign) or SBOM (Software Bill of Materials) will further elevate your security posture.

Leave a Reply