Skip to main content

Protect Tags with Immutability

Configure immutable tags to prevent overwrites and ensure deployment reproducibility.

Prerequisites

  • Angos running

Why Immutable Tags?

Immutable tags ensure that a tag always points to the same image:

  • Prevents accidental overwrites
  • Guarantees reproducible deployments
  • Required for security compliance in many organizations

Basic Configuration

Enable Globally

[global]
immutable_tags = true

All tags are now immutable across all repositories.

Allow Specific Tags to Change

Use exclusion patterns for mutable tags:

[global]
immutable_tags = true
immutable_tags_exclusions = [
"^latest$", # Allow 'latest' to be updated
"^develop$", # Allow 'develop' branch tag
"^main$" # Allow 'main' branch tag
]

Repository-Specific Settings

Override global settings per repository:

# Global default
[global]
immutable_tags = true
immutable_tags_exclusions = ["^latest$"]

# Development: all tags mutable
[repository."dev"]
immutable_tags = false

# Production: stricter exclusions
[repository."production"]
immutable_tags = true
immutable_tags_exclusions = ["^latest$"]

# Staging: allow more mutable tags
[repository."staging"]
immutable_tags = true
immutable_tags_exclusions = [
"^latest$",
"^staging-.*$",
"^rc-.*$"
]

Exclusion Patterns

Patterns are regular expressions matching the full tag:

PatternMatches
^latest$Only "latest"
^v\d+\.\d+$"v1.0", "v2.3" (minor versions)
^pr-\d+$"pr-123", "pr-456"
^nightly-.*$"nightly-2024-01-15"
^dev-.*$"dev-feature-x"
^.*-SNAPSHOT$"1.0-SNAPSHOT"

Common Patterns

Protect Semver, Allow Pre-release

immutable_tags = true
immutable_tags_exclusions = [
"^latest$",
"^.*-alpha.*$",
"^.*-beta.*$",
"^.*-rc.*$",
"^.*-SNAPSHOT$"
]

Protect Release Tags Only

immutable_tags = true
immutable_tags_exclusions = [
"^(?!v[0-9]+\\.[0-9]+\\.[0-9]+$).*$" # Everything except vX.Y.Z
]

CI/CD Friendly

immutable_tags = true
immutable_tags_exclusions = [
"^latest$",
"^main$",
"^develop$",
"^pr-\\d+$",
"^sha-[a-f0-9]+$"
]

Pull-Through Cache Optimization

Immutable tags improve pull-through cache performance:

[repository."library"]
immutable_tags = true
immutable_tags_exclusions = ["^latest$", "^nightly.*$"]

[[repository."library".upstream]]
url = "https://registry-1.docker.io"

Benefits:

  • Immutable tags (e.g., nginx:1.25.0): Served from cache without upstream checks
  • Mutable tags (e.g., nginx:latest): Check upstream for updates

Error Handling

When attempting to overwrite an immutable tag:

HTTP Response:

  • Status: 409 Conflict
  • Code: CONFLICT
  • Message: Tag 'tagname' is immutable and cannot be overwritten

Docker CLI:

$ docker push registry.local/myapp:v1.0.0
The push refers to repository [registry.local/myapp]
171a26c7bc56: Layer already exists
unknown: Tag 'v1.0.0' is immutable and cannot be overwritten

Verification

Test Immutability

# First push succeeds
docker tag alpine:latest localhost:5000/test/image:v1.0
docker push localhost:5000/test/image:v1.0

# Second push with different content fails
docker tag busybox:latest localhost:5000/test/image:v1.0
docker push localhost:5000/test/image:v1.0
# Error: Tag 'v1.0' is immutable and cannot be overwritten

# Excluded tags can be overwritten
docker push localhost:5000/test/image:latest # Succeeds
docker push localhost:5000/test/image:latest # Succeeds again

Check Current Configuration

curl http://localhost:5000/v2/_ext/_repositories | jq

Response includes immutable_tags: true/false per repository.


Troubleshooting

Can't overwrite expected mutable tag:

  • Check exclusion patterns match the tag
  • Repository settings override global
  • Patterns are regex, escape special characters

Immutability not enforced:

  • Verify immutable_tags = true is set
  • Check repository-specific overrides

Reference