Skip to main content

Configuration Reference

Angos is configured via a TOML file (default: config.toml). The configuration is automatically reloaded when the file changes.

Hot Reloading

Most configuration changes take effect immediately without restart. The following options require a restart:

  • server.bind_address
  • server.port
  • observability.tracing.sampling_rate
  • Enabling or disabling TLS
  • Changing storage backend type (filesystem ↔ S3)
  • Changing lock strategy

TLS certificate files are also automatically reloaded when they change.


Server (server)

OptionTypeDefaultDescription
bind_addressstringrequiredAddress to bind (e.g., "0.0.0.0", "127.0.0.1")
portu168000Port number
query_timeoutu643600Query timeout in seconds
query_timeout_grace_periodu6460Grace period for queries in seconds

TLS (server.tls)

When omitted, the server runs without TLS (insecure).

OptionTypeDefaultDescription
server_certificate_bundlestringrequiredPath to server certificate (PEM)
server_private_keystringrequiredPath to server private key (PEM)
client_ca_bundlestring-Path to client CA bundle for mTLS

Global Options (global)

OptionTypeDefaultDescription
max_concurrent_requestsusize64Tokio worker threads (see Performance Tuning)
max_concurrent_cache_jobsusize4Maximum concurrent cache jobs
update_pull_timeboolfalseTrack pull times for retention policies
enable_redirectbooltrueAllow HTTP 307 redirects for blob downloads
immutable_tagsboolfalseGlobal immutable tags default
immutable_tags_exclusions[string][]Regex patterns for mutable tags
authorization_webhookstring-Name of webhook for authorization
event_webhooks[string][]Event webhook names for all repositories

Global Access Policy (global.access_policy)

OptionTypeDefaultDescription
default_allowboolfalseDefault action when no rules match
rules[string][]CEL expressions for access control

Global Retention Policy (global.retention_policy)

OptionTypeDefaultDescription
rules[string][]CEL expressions for retention

Cache (cache)

Token and key cache configuration. Defaults to in-memory (not suitable for multi-replica).

Redis Cache (cache.redis)

OptionTypeDefaultDescription
urlstringrequiredRedis URL (e.g., "redis://localhost:6379")
key_prefixstring-Prefix for cache keys

Blob Storage (blob_store)

Choose one: blob_store.fs or blob_store.s3.

Filesystem (blob_store.fs)

OptionTypeDefaultDescription
root_dirstringrequiredDirectory for blob storage
sync_to_diskboolfalseForce fsync after writes

S3 (blob_store.s3)

OptionTypeDefaultDescription
access_key_idstringrequiredAWS access key ID
secret_keystringrequiredAWS secret key
endpointstringrequiredS3 endpoint URL
bucketstringrequiredS3 bucket name
regionstringrequiredAWS region
key_prefixstring-Prefix for S3 keys
multipart_part_sizestring"50MiB"Minimum multipart part size
multipart_copy_thresholdstring"5GB"Threshold for multipart copy
multipart_copy_chunk_sizestring"100MB"Chunk size for multipart copy
multipart_copy_jobsusize4Max concurrent multipart copy jobs
max_attemptsu323Retry attempts for S3 operations
operation_timeout_secsu64900Total operation timeout
operation_attempt_timeout_secsu64300Per-attempt timeout

Metadata Storage (metadata_store)

Optional. Defaults to same backend as blob store.

Lock Strategy Compatibility

The following table shows which lock strategies are supported with each metadata store backend:

Lock StrategyS3 metadata storeFS metadata store
memoryYesYes
redisYesYes
s3YesNo

Filesystem (metadata_store.fs)

OptionTypeDefaultDescription
root_dirstring-Directory for metadata (defaults to blob store)
sync_to_diskboolfalseForce fsync after writes
lock_strategystring/table"memory"Lock backend: "memory" (string), or [lock_strategy.redis] (table form). S3 locking not supported.

Note: The S3 lock strategy is not supported for filesystem metadata stores. Use "memory" for single-instance deployments or [lock_strategy.redis] for multi-replica deployments.

S3 (metadata_store.s3)

Same connection options as blob_store.s3, plus:

OptionTypeDefaultDescription
link_cache_ttlu6430Read-through cache TTL for link metadata, in seconds (0 to disable)
access_time_debounce_secsu6460Buffer access time writes and flush periodically, in seconds (0 to disable)
lock_strategystring/table"memory"Lock backend: "memory" (string), or [lock_strategy.s3]/[lock_strategy.redis] (table form, see below)
capabilitiestable-Optional S3 conditional operation capabilities; see below

The link cache reduces S3 round-trips for repeated tag/layer reads. The access time debounce batches last_pulled_at timestamp writes in memory and flushes them periodically, reducing the critical-path operations per manifest pull from 4 (lock, read, write, unlock) to 1 (read).

Conditional Capabilities (metadata_store.s3.capabilities)

When using S3 as the metadata store, you can declare which conditional write operations your S3-compatible provider supports. This avoids a startup probe and allows optimization of certain operations.

OptionTypeDefaultDescription
put_if_none_matchbool-S3 supports PutObject with If-None-Match: * (create-only, reject if object exists)
put_if_matchbool-S3 supports PutObject with If-Match: <etag> (update-only, reject if ETag mismatch)
delete_if_matchbool-S3 supports DeleteObject with If-Match: <etag> (conditional delete, reject if ETag mismatch)

Probe behavior:

  • When capabilities table is explicitly configured, the startup probe is skipped entirely. Provide accurate values matching your S3 provider's capabilities.
  • When capabilities is omitted:
    • If lock_strategy = "s3", a startup probe automatically tests each capability and populates the status.
    • For other lock strategies, capabilities default to all false (no probing occurs).

Example with explicit capabilities (AWS S3):

[metadata_store.s3.capabilities]
put_if_none_match = true
put_if_match = true
delete_if_match = true

Example with explicit capabilities (minimal, memory locking):

[metadata_store.s3]
lock_strategy = "memory"

[metadata_store.s3.capabilities]
put_if_none_match = false
put_if_match = false
delete_if_match = false

Example with auto-probe (S3 locking):

[metadata_store.s3]
# No capabilities field — probe runs at startup for S3 lock strategy
[metadata_store.s3.lock_strategy.s3]
ttl_secs = 30

Performance impact:

  • When put_if_match is available, access time writes use optimized compare-and-swap (CAS) instead of distributed lock operations, reducing S3 API calls per manifest pull.
  • When delete_if_match is available, S3 lock release is atomic and race-condition-free.
  • Both features require both put_if_none_match and put_if_match to be true for full CAS support.

Warning: Setting access_time_debounce_secs = 0 with S3 lock strategy causes every manifest pull to perform a full lock-acquire → read → write → release cycle via S3 API. At scale with many concurrent pulls, this adds significant latency and S3 API costs. Keep the default value of 60 or higher for S3-locked deployments, or disable access time tracking entirely if not needed for retention policies.

Distributed Locking

Multi-replica deployments require a distributed lock backend. The lock_strategy field on the metadata store selects the backend. Three options are available:

Lock Strategy Compatibility Matrix:

Lock StrategyS3 metadataFS metadata
memoryYesYes
redisYesYes
s3YesNo

Memory (default) — in-process locks, suitable for single-instance deployments only:

[metadata_store.s3]
lock_strategy = "memory"

S3 — uses S3 conditional writes (If-None-Match: *) for distributed locking without extra infrastructure. The S3 provider must support conditional writes; angos verifies this at startup:

# With defaults (empty table body; all fields use defaults)
[metadata_store.s3.lock_strategy.s3]

# With custom settings
[metadata_store.s3.lock_strategy.s3]
ttl_secs = 30
max_retries = 100
retry_delay_ms = 50

Note: The bare-string form lock_strategy = "s3" is not supported; use the table form [metadata_store.s3.lock_strategy.s3] to accept defaults or override individual fields.

OptionTypeDefaultDescription
ttl_secsu6430Lock TTL in seconds (minimum: 9). Heartbeat renews at intervals of ttl_secs / 3
max_retriesu32100Max lock acquisition retries
retry_delay_msu6450Delay between retries (minimum: 1)
max_hold_secsu64300Maximum lock hold duration in seconds (minimum: 10, must be >= ttl_secs). Guard is invalidated if held beyond this duration
operation_timeout_secsu6415Total timeout for lock S3 operations
operation_attempt_timeout_secsu644Per-attempt timeout for lock S3 operations
max_attemptsu322Maximum retry attempts for lock S3 operations

Lock operation timeouts: Lock operations use their own S3 client with significantly tighter timeouts than blob/metadata operations. This is intentional: lock operations are small JSON payloads and should fail fast rather than blocking for minutes on a stuck request. The defaults (operation_timeout_secs = 15, operation_attempt_timeout_secs = 4, max_attempts = 2) ensure that a single stuck request cannot consume an entire heartbeat interval (10s with default TTL). Each heartbeat tick is also capped to the heartbeat interval to prevent the slow path (two sequential SDK calls) from exceeding it. A startup warning is emitted if operation_attempt_timeout_secs × max_attempts >= ttl_secs / 3. For high-latency S3 scenarios, increase these values but keep attempt_timeout × max_attempts below the heartbeat interval.

Heartbeat Mechanism:

The S3 lock implementation uses a heartbeat to keep locks alive. Once acquired, a background task automatically renews the lock at regular intervals of ttl_secs / 3. For example, with the default ttl_secs = 30, the heartbeat runs every 10 seconds. This allows the lock to remain valid beyond the initial TTL as long as the lock-holder remains alive. If a lock-holder crashes, other instances must wait for the full ttl_secs duration before the lock becomes available for recovery.

Contention note: The first lock acquisition attempt uses parallel PUTs for low latency. If any key is contended, the system falls back to sequential sorted acquisition for all subsequent retries, which eliminates circular wait and prevents livelock. When CAS blob index updates are active (S3 lock strategy), blob digest keys are excluded from locking, avoiding cross-namespace contention on shared layers. Randomized jitter on retry delays desynchronises retrying instances.

Clock synchronisation: The lock implementation uses S3's server-side timestamps for expiry checks, so lock correctness does not depend on synchronised instance clocks. Registry instances should still maintain synchronised clocks (NTP) for logging and other operational reasons.

Redis — distributed locking via Redis, suitable for multi-instance deployments:

[metadata_store.s3.lock_strategy.redis]
url = "redis://localhost:6379"
ttl = 10
OptionTypeDefaultDescription
urlstringrequiredRedis URL
ttlusizerequiredLock TTL in seconds
key_prefixstring-Prefix for lock keys
max_retriesu32100Max lock acquisition retries
retry_delay_msu6410Delay between retries

Legacy form: The [metadata_store.*.redis] table (e.g., [metadata_store.s3.redis]) is still accepted for backward compatibility and is equivalent to [metadata_store.*.lock_strategy.redis]. New configurations should use the lock_strategy form. Both forms cannot be set simultaneously.


Authentication (auth)

Basic Auth (auth.identity.<name>)

OptionTypeDefaultDescription
usernamestringrequiredUsername
passwordstringrequiredArgon2 password hash

OIDC (auth.oidc.<name>)

GitHub Provider

OptionTypeDefaultDescription
providerstringrequiredMust be "github"
issuerstring"https://token.actions.githubusercontent.com"Issuer URL
jwks_uristring"https://token.actions.githubusercontent.com/.well-known/jwks"JWKS URI
jwks_refresh_intervalu643600JWKS refresh interval (seconds)
required_audiencestring-Required audience claim
clock_skew_toleranceu6460Clock skew tolerance (seconds)

Generic Provider

OptionTypeDefaultDescription
providerstringrequiredMust be "generic"
issuerstringrequiredOIDC issuer URL
jwks_uristring-Custom JWKS URI (auto-discovered if not set)
jwks_refresh_intervalu643600JWKS refresh interval (seconds)
required_audiencestring-Required audience claim
clock_skew_toleranceu6460Clock skew tolerance (seconds)

Webhooks (auth.webhook.<name>)

OptionTypeDefaultDescription
urlstringrequiredWebhook URL
timeout_msu64requiredRequest timeout in milliseconds
bearer_tokenstring-Bearer token for authentication
basic_auth.usernamestring-Basic auth username
basic_auth.passwordstring-Basic auth password
client_certificate_bundlestring-Client cert for mTLS
client_private_keystring-Client key for mTLS
server_ca_bundlestring-CA bundle for server verification
forward_headers[string][]Headers to forward from client
cache_ttlu6460Response cache duration (0 to disable)

Repository (repository."<namespace>")

OptionTypeDefaultDescription
immutable_tagsboolinheritsOverride global immutable tags
immutable_tags_exclusions[string]inheritsOverride global exclusions
authorization_webhookstringinheritsWebhook name (empty to disable)
event_webhooks[string]inheritsEvent webhook names

Upstream (repository."<namespace>".upstream)

Array of upstream registries for pull-through cache.

OptionTypeDefaultDescription
urlstringrequiredUpstream registry URL
max_redirectu85Maximum redirects to follow
server_ca_bundlestring-CA bundle for server verification
client_certificatestring-Client certificate for mTLS
client_private_keystring-Client key for mTLS
usernamestring-Basic auth username
passwordstring-Basic auth password

Access Policy (repository."<namespace>".access_policy)

Same as global.access_policy.

Retention Policy (repository."<namespace>".retention_policy)

Same as global.retention_policy.


Event Webhooks (event_webhook.<name>)

HTTP POST notifications for registry operations. See Event Webhooks Reference for full details.

OptionTypeDefaultDescription
urlstringrequiredHTTP/HTTPS endpoint URL
policystringrequiredDelivery policy: required, optional, async
events[string]requiredEvent types to deliver (at least one)
tokenstring-Bearer token and HMAC signing secret
timeout_msu645000HTTP request timeout in milliseconds
max_retriesu320Maximum retry attempts after initial failure
repository_filter[string]-Regex patterns to match repository names

Webhooks are enabled by referencing their names:

LocationOptionTypeDescription
globalevent_webhooks[string]Webhook names for all repositories
repository."<namespace>"event_webhooks[string]Webhook names for this repository

Observability

Tracing (observability.tracing)

OptionTypeDefaultDescription
endpointstringrequiredOpenTelemetry endpoint
sampling_ratef64requiredSampling rate (0.0 - 1.0)

Prometheus Metrics

Angos emits Prometheus metrics on the /metrics endpoint. The following metrics are available for lock operations:

Lock Metrics:

MetricTypeLabelsDescription
lock_acquisition_duration_msHistogrambackendLock acquisition duration in milliseconds
lock_acquisitions_totalCounterbackend, resultTotal lock acquisition attempts
lock_retries_totalCounterbackendTotal lock acquisition retries
lock_invalidations_totalCounterbackend, reasonTotal lock invalidations
lock_recoveries_totalCounterbackend, resultTotal stale lock recovery attempts

Label Values:

  • backend: s3, redis, memory
  • result (acquisitions): success, timeout, error
  • result (recoveries): acquired, not_stale, failed, error
  • reason (invalidations): ownership_lost, max_hold, heartbeat_failure, etag_unavailable, file_disappeared

Web UI (ui)

OptionTypeDefaultDescription
enabledboolfalseEnable web interface
namestring"Angos"Registry name in UI header

Performance Tuning

max_concurrent_requests

Controls the number of Tokio worker threads handling HTTP requests. Default: 64.

Registry operations are likely I/O-bound (network transfers, storage I/O), so more threads than CPU cores typically improves throughput.

Rule of thumb: Start with 8-16x your CPU core count and adjust based on monitoring.


Example Configuration

[server]
bind_address = "0.0.0.0"
port = 5000

[server.tls]
server_certificate_bundle = "/tls/server.crt"
server_private_key = "/tls/server.key"

[global]
update_pull_time = true
immutable_tags = true
immutable_tags_exclusions = ["^latest$"]

[blob_store.fs]
root_dir = "/var/registry/blobs"

[metadata_store.fs]
root_dir = "/var/registry/metadata"

[metadata_store.fs.lock_strategy.redis]
url = "redis://localhost:6379"
ttl = 10

[cache.redis]
url = "redis://localhost:6379"

[auth.identity.admin]
username = "admin"
password = "$argon2id$v=19$m=19456,t=2,p=1$..."

[auth.oidc.github-actions]
provider = "github"

[global.access_policy]
default_allow = false
rules = ["identity.username != ''"]

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

[ui]
enabled = true
name = "My Registry"

S3-Only Multi-Instance Deployment

This example uses S3 for both blob and metadata storage with S3-based distributed locking, eliminating the need for Redis:

[server]
bind_address = "0.0.0.0"
port = 5000

[global]
update_pull_time = true

[blob_store.s3]
# Example credentials - replace for production
access_key_id = "minioadmin"
secret_key = "minioadmin"
endpoint = "https://s3.example.com"
bucket = "registry"
region = "us-east-1"

[metadata_store.s3]
# Example credentials - replace for production
access_key_id = "minioadmin"
secret_key = "minioadmin"
endpoint = "https://s3.example.com"
bucket = "registry-metadata"
region = "us-east-1"

[metadata_store.s3.lock_strategy.s3]

[auth.identity.admin]
username = "admin"
password = "$argon2id$v=19$m=19456,t=2,p=1$..."