Skip to main content

Protect Tags with Immutability

Configure immutable tags to prevent overwrites and ensure deployment reproducibility.

Prerequisites

  • Angos running

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

A repository can add immutability on top of the global setting, but cannot remove it: a tag is immutable when the global flag or the repository flag is true. To keep some repositories mutable, leave the global flag off and enable immutability per repository.

Per-repository immutable_tags_exclusions replace the global list when non-empty; otherwise the global list applies.

# Global default: all tags mutable
[global]
immutable_tags = false

# Development repositories inherit the global default: all tags mutable

# Production: immutable, only 'latest' may change
[repository."production"]
immutable_tags = true
immutable_tags_exclusions = ["^latest$"]

# Staging: immutable, 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

The pattern engine does not support negative lookahead, so "everything except vX.Y.Z" cannot be written as a single exclusion. List the mutable tag conventions explicitly instead:

immutable_tags = true
immutable_tags_exclusions = [
"^(?:latest|main|develop)$", # Branch tags stay mutable
"^(?:dev|feature|nightly)-.*$" # Work-in-progress tags stay mutable
]

Release tags such as v1.2.3 match no exclusion and stay immutable.

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: DENIED
  • 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:8000/test/image:v1.0
docker push localhost:8000/test/image:v1.0

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

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

Check Current Configuration

curl http://localhost:8000/v2/_angos/repositories/list | jq

Response includes immutable_tags: true/false per repository.


Troubleshooting

Can't overwrite expected mutable tag:

  • Check exclusion patterns match the tag
  • Non-empty repository exclusions replace the global list
  • A repository cannot disable a global immutable_tags = true
  • Patterns are regex, escape special characters

Immutability not enforced:

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

Reference