🏷️ Why You Should Ditch the ‘latest’ Tag Right Now β€” Mastering Container Image Tag Immutability

“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.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *