Reference

Configuration

DeltaGlider Proxy is configured via a YAML file and/or environment variables (DGP_* prefix). Environment variables always take precedence over file contents.

YAML is the only supported format. TOML support was removed in v1.4.1: a .toml config — whether set via DGP_CONFIG or found on the default search path — makes the proxy fail at startup with TOML configs are no longer supported (removed in v1.4.1). If you still carry a TOML config, run deltaglider_proxy config migrate on v1.4.0 to convert it, then point the server at the YAML file before upgrading. See How to upgrade the proxy.

Table of contents


YAML layout

The canonical YAML has four optional top-level sections:

# deltaglider_proxy.yaml

admission:   # pre-auth request gating (deny / reject / allow-anonymous)
  blocks: [...]

access:      # SigV4 credentials + iam_mode selector
  iam_mode: gui             # gui (default) or declarative
  access_key_id: admin
  secret_access_key: changeme

storage:     # backend(s) + per-bucket overrides
  s3: https://s3.example.com
  buckets: {...}

advanced:    # process-level tunables
  listen_addr: "0.0.0.0:9000"
  cache_size_mb: 2048
  log_level: deltaglider_proxy=info

Every section is optional. Fields equal to their default are omitted from canonical exports (GET /api/admin/config/export), keeping GitOps diffs minimal.

The flat (pre-Phase-3) shape — root-level listen_addr:, backend:, etc. — still loads unchanged. Mixing the two shapes in one document is a hard parse error naming the conflicting keys.

The same document is editable from the admin UI. The form keeps section ownership visible, shows YAML paths next to fields, and calls out restart-only environment overrides.

Access configuration form

Storage backend configuration form

Advanced limits configuration form


Shorthands

Three operator-authoring shorthands expand at load time into their canonical forms.

Storage shorthand — single backend

storage:
  s3: https://s3.example.com       # expands to backend: { type: s3, endpoint: ... }
  region: eu-central-1             # optional
  access_key_id: admin             # optional
  secret_access_key: changeme      # optional
  force_path_style: true           # optional

or

storage:
  filesystem: /var/lib/deltaglider

Only one of backend: / s3: / filesystem: may be set. Companion fields (region, access_key_id, etc.) apply only to s3:.

Bucket public: true

storage:
  buckets:
    docs-site:
      public: true           # shorthand for public_prefixes: [""]

The canonical exporter collapses public_prefixes: [""] back to public: true when unambiguous. The GUI "Public read" toggle maps 1:1 to the YAML.

Mixing public: true and a non-empty public_prefixes is a hard error.


Config-file search order

Config::resolve_config_path returns the first match from:

  1. DGP_CONFIG env var (returned unconditionally — if set, the path is used even when the file doesn't yet exist).
  2. ./deltaglider_proxy.yaml
  3. ./deltaglider_proxy.yml
  4. ./deltaglider_proxy.toml (tripwire — startup fails)
  5. /etc/deltaglider_proxy/config.yaml
  6. /etc/deltaglider_proxy/config.yml
  7. /etc/deltaglider_proxy/config.toml (tripwire — startup fails)

The .toml entries are tripwires, not loadable formats: a leftover TOML config matched by the search (with no YAML earlier in the order) stops startup with an actionable error rather than being silently ignored.

CLI flags (--config <path>, --listen <addr>) take precedence over all of the above; env vars take precedence over file contents.


Server / Advanced

Process-level knobs. In sectioned YAML these live under advanced:.

listen_addr

HTTP listen address.

Env varDGP_LISTEN_ADDR
YAMLadvanced.listen_addr (sectioned) or root listen_addr: (flat)
Default0.0.0.0:9000
Hot-reloadNo (restart required)
advanced:
  listen_addr: "0.0.0.0:8080"

log_level

Tracing filter string (tracing-subscriber syntax). Overridden by RUST_LOG if set. Changeable at runtime via the admin GUI (Settings → System → Logging card), which hot-reloads the filter through the apply pipeline.

Resolution order at startup: RUST_LOG > DGP_LOG_LEVEL > advanced.log_level in file > --verbose CLI flag (sets trace) > default.

Env varDGP_LOG_LEVEL
YAMLadvanced.log_level
Defaultdeltaglider_proxy=debug,tower_http=debug
Hot-reloadYes (via admin GUI or config apply)
advanced:
  log_level: deltaglider_proxy=info,tower_http=warn

request_timeout_secs

Per-request deadline (HTTP 504 when exceeded).

Env varDGP_REQUEST_TIMEOUT_SECS
Default300 (5 minutes)
Hot-reloadNo

max_concurrent_requests

Global tower ConcurrencyLimit. Requests beyond this queue.

Env varDGP_MAX_CONCURRENT_REQUESTS
Default1024
Hot-reloadNo

max_multipart_uploads

Concurrent multipart uploads cap. Each upload holds part data in memory.

Env varDGP_MAX_MULTIPART_UPLOADS
Default1000
Hot-reloadNo

blocking_threads

Tokio blocking thread-pool size. Controls how many concurrent CPU-bound ops (xdelta3 subprocesses) can run.

Env varDGP_BLOCKING_THREADS
YAMLadvanced.blocking_threads
Defaulttokio default (512)
Hot-reloadNo

debug_headers

Expose debug/fingerprinting headers (x-amz-storage-type, x-deltaglider-cache). Disable in production to prevent server fingerprinting.

Env varDGP_DEBUG_HEADERS
Defaultfalse
Hot-reloadNo

cors_permissive

Enable permissive CORS for cross-origin admin access (dev only — opens the door to CSRF against session-cookie endpoints).

Env varDGP_CORS_PERMISSIVE
Defaultfalse
Hot-reloadNo

config

Path to the config file.

Env varDGP_CONFIG
DefaultAuto-detect (search list above)

When DGP_CONFIG is set, the path is returned unconditionally — a missing file there is NOT silently replaced by the default search list. This prevents the admin API from persisting to a CWD-relative file the operator never asked for.


Delta engine

max_delta_ratio

Store an object as a delta only if delta_size / original_size is below this ratio. Lower = more aggressive savings; higher = more files kept as deltas.

Env varDGP_MAX_DELTA_RATIO
YAMLadvanced.max_delta_ratio
Default0.75
Hot-reloadYes

max_object_size

Maximum object size in bytes. Enforced as the HTTP request body limit, so it caps uploads for both delta and passthrough objects; it is also the per-object ceiling for delta processing (xdelta3 memory constraint) and sizes the multipart upload budget. 0 rejects all uploads (startup warning).

Env varDGP_MAX_OBJECT_SIZE
Default104857600 (100 MB)
Hot-reloadYes

cache_size_mb

In-memory reference cache size in MB. Recommend 1024+ MB for production. Undersized caches (<1024 MB) emit a startup warning.

Env varDGP_CACHE_MB
YAMLadvanced.cache_size_mb
Default100
Hot-reloadNo

metadata_cache_mb

In-memory FileMetadata cache size in MB. Set to 0 to disable. Budget: ~125K-150K entries at 50 MB. 10-minute TTL.

Env varDGP_METADATA_CACHE_MB
YAMLadvanced.metadata_cache_mb
Default50
Hot-reloadNo

codec_concurrency

Maximum concurrent xdelta3 subprocesses. Auto-detected as num_cpus * 4 (min 16).

Env varDGP_CODEC_CONCURRENCY
YAMLadvanced.codec_concurrency
Defaultnum_cpus * 4 (min 16)
Hot-reloadNo

codec_timeout_secs

Maximum time for an xdelta3 subprocess. Hung processes are killed after this.

Env varDGP_CODEC_TIMEOUT_SECS
Default60
Hot-reloadNo

Storage backend

Filesystem backend

Local filesystem. Activated by setting DGP_DATA_DIR or a backend: block with type = "filesystem".

data_dir

Env varDGP_DATA_DIR
YAML (shorthand)storage.filesystem: <path>
YAML (canonical)storage.backend.path
Default./data
Hot-reloadYes (triggers engine rebuild)
# Shorthand
storage:
  filesystem: /var/lib/deltaglider

# Canonical (equivalent)
storage:
  backend:
    type: filesystem
    path: /var/lib/deltaglider

Paths containing .. components are rejected at load time.

S3 backend

AWS S3 / MinIO / Hetzner / Backblaze / any S3-compatible service. Activated by setting DGP_S3_ENDPOINT or a backend: block with type = "s3".

endpoint / region / force_path_style / access_key_id / secret_access_key

FieldEnv varYAML shorthandYAML canonicalDefault
endpointDGP_S3_ENDPOINTstorage.s3: <url>storage.backend.endpoint— (AWS default)
regionDGP_S3_REGIONstorage.regionstorage.backend.regionus-east-1
force_path_styleDGP_S3_PATH_STYLEstorage.force_path_stylestorage.backend.force_path_styletrue
access_key_idDGP_BE_AWS_ACCESS_KEY_IDstorage.access_key_idstorage.backend.access_key_id
secret_access_keyDGP_BE_AWS_SECRET_ACCESS_KEYstorage.secret_access_keystorage.backend.secret_access_key
# Shorthand
storage:
  s3: https://hel1.your-objectstorage.com
  region: hel1
  access_key_id: AKIAIOSFODNN7EXAMPLE
  secret_access_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

# Canonical
storage:
  backend:
    type: s3
    endpoint: https://hel1.your-objectstorage.com
    region: hel1
    force_path_style: true
    access_key_id: AKIAIOSFODNN7EXAMPLE
    secret_access_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

Endpoint URLs must start with http:// or https:// (scheme-less values rejected at load time).


Access — authentication

The proxy refuses to start without credentials unless you set authentication = "none".

authentication

Explicit auth-mode selector. Absent = auto-detect from credentials; "none" = open access (dev only).

Env varDGP_AUTHENTICATION
YAMLaccess.authentication
Default— (auto-detect; fatal error if absent AND no credentials)
Hot-reloadNo

access_key_id / secret_access_key

Proxy-level SigV4 credentials (the "bootstrap admin" credential pair).

Env varsDGP_ACCESS_KEY_ID / DGP_SECRET_ACCESS_KEY
YAMLaccess.access_key_id / access.secret_access_key
DefaultNone
Hot-reloadYes
access:
  access_key_id: admin
  secret_access_key: changeme

bootstrap_password_hash

Bcrypt hash of the bootstrap password (encrypts the IAM config DB, signs session cookies, gates admin GUI access in bootstrap mode). Auto-generated on first run. Accepts base64-encoded hashes to avoid $ escaping in Docker/env vars.

Env varDGP_BOOTSTRAP_PASSWORD_HASH (legacy alias: DGP_ADMIN_PASSWORD_HASH)
YAMLadvanced.bootstrap_password_hash (treated as an infra secret — stripped by canonical exports)
DefaultAuto-generated on first run

DGP_BOOTSTRAP_PASSWORD

Plaintext bootstrap password for the config apply / admission trace admin CLI commands (they authenticate via this env var; argv is avoided because it leaks via ps). Not read by the server itself.

Env varDGP_BOOTSTRAP_PASSWORD
ConsumerAdmin CLI (deltaglider_proxy config apply, ... admission trace)

Access — IAM mode

The access.iam_mode YAML selector controls where IAM state (users, groups, OAuth providers, mapping rules) lives. Orthogonal to the authentication selector.

ModeMeaning
gui (default)Encrypted SQLCipher DB is source of truth. Admin GUI + admin API mutate it. YAML access.* carries only the legacy SigV4 pair + authentication selector.
declarativeYAML access.iam_users, iam_groups, auth_providers, and group_mapping_rules are authoritative. Admin API IAM mutation routes (POST/PUT/PATCH/DELETE on /users, /groups, /ext-auth/*, /migrate, backup import) return 403 { "error": "iam_declarative" }. Read routes stay accessible.
access:
  iam_mode: declarative

Mode transitions are audit-logged at warn level on the deltaglider_proxy::config target. In declarative mode, every /config/apply or section-PUT on access runs a dry validation + diff, then reconciles the encrypted config DB to YAML in one SQLite transaction. Creates, updates, and deletes emit iam_reconcile_* audit entries.

The initial gui → declarative flip is guarded: if YAML contains no users or groups while the DB is non-empty, apply fails instead of wiping IAM by accident. To seed GitOps YAML from an existing DB, use GET /_/api/admin/config/declarative-iam-export; see Declarative IAM for the full workflow.


Admission chain

Operator-authored pre-auth request gating. Blocks are evaluated top-to-bottom; first match wins. Operator blocks fire before synthesized public-prefix blocks derived from storage.buckets[*].public_prefixes.

admission:
  blocks:
    - name: deny-known-bad-ips
      match:
        source_ip_list:
          - "198.51.100.17"
          - "198.51.100.0/24"
      action: deny

    - name: maintenance-mode
      match: {}              # empty = match every request
      action:
        type: reject
        status: 503
        message: "Planned maintenance — back at 18:00 UTC."

    - name: allow-public-zips
      match:
        method: [GET, HEAD]
        bucket: releases
        path_glob: "*.zip"
      action: allow-anonymous

Block fields

FieldTypeNotes
namestring (required)1-128 chars, [A-Za-z0-9_:.-]. Must be unique across the chain. public-prefix:* is reserved for synthesized blocks.
matchobject (default {})AND-combined predicates. Empty {} fires on every request.
match.method[string]HTTP methods: GET HEAD PUT POST DELETE PATCH OPTIONS. Case-insensitive on parse.
match.source_ipIPExact match. Mutually exclusive with source_ip_list.
match.source_ip_list[IP | CIDR]Accepts bare IPs (promoted to /32 or /128) and CIDRs. Cap: 4096 entries.
match.bucketstringTarget bucket (lowercased on parse).
match.path_globstringGlob against the full key: *.zip, releases/**, docs/readme.md.
match.authenticatedbooltrue = only authenticated; false = only anonymous; absent = either.
match.config_flagstringNamed flag. Registry is not yet live — maintenance_mode is recognised but always evaluates false; a warning fires at chain-build time.
actionstring | object (required)Simple: allow-anonymous, deny, continue. Tagged: { type: reject, status: <4xx|5xx>, message?: <string> }.

continue is an explicit terminal that falls through to authentication — useful as the final block for diagnostic visibility in trace output.

Round-trip

Operator-authored source_ip_list entries round-trip verbatim (bare IPs stay bare, CIDRs stay CIDRs) so GitOps diffs don't flip on every apply.

The admin GUI's Admission page (/_/admin/access/admission) is the authoring surface. Synthesized public-prefix:* blocks appear read-only below the operator list; edit them via Storage → Buckets instead.


Security

trust_proxy_headers

Trust X-Forwarded-For / X-Real-IP for rate limiting and aws:SourceIp IAM conditions. Disable if the proxy is internet-facing without a reverse proxy.

Env varDGP_TRUST_PROXY_HEADERS
Defaultfalse (secure-by-default)
Hot-reloadNo

Behind a reverse proxy that sets these headers, set to true.

session_ttl_hours

Admin GUI session TTL.

Env varDGP_SESSION_TTL_HOURS
Default4
Hot-reloadNo

clock_skew_seconds

SigV4 clock skew tolerance.

Env varDGP_CLOCK_SKEW_SECONDS
Default300 (5 min)
Hot-reloadNo

replay_window_secs

SigV4 replay detection window. A request whose signature was already seen within this many seconds is treated as a replay.

  • Mutating methods (PUT/POST/DELETE/…) are rejected with 400 Request replay detected.
  • Idempotent reads (GET/HEAD) are tolerated: a duplicate within the window is served normally rather than rejected. This is deliberate — boto3/botocore emit byte-identical SigV4 signatures for the same request issued (or auto-retried) within one signing second, because SigV4 timestamps have 1-second granularity. A replayed read just re-reads the same bytes, so there is no double-effect to guard against. The signature still stays in the cache, so a captured read signature can't be replayed past the window.
  • Presigned URLs are exempt entirely (they are designed to be reused for their whole expiry).
  • A replay rejection is not an authentication failure — the signature is valid — so it is audited as replay_rejected and does not count toward the per-IP brute-force lockout.

Set DGP_REPLAY_WINDOW_SECS=0 to disable replay rejection entirely (the window never matches). Useful in CI, or as an escape hatch if a client clusters mutations tighter than the default tolerates.

Env varDGP_REPLAY_WINDOW_SECS
Default2
Hot-reloadNo

secure_cookies

Require HTTPS for admin session cookies (Secure flag).

Env varDGP_SECURE_COOKIES
Defaulttrue
Hot-reloadNo

Rate limiting

Per-IP brute-force protection for auth endpoints. See Rate limits and concurrency for the full model.

SettingEnv varDefault
Max failures before lockoutDGP_RATE_LIMIT_MAX_ATTEMPTS100
Rolling windowDGP_RATE_LIMIT_WINDOW_SECS300 (5 min)
Lockout durationDGP_RATE_LIMIT_LOCKOUT_SECS600 (10 min)

TLS

When enabled, both the S3 API and admin GUI serve HTTPS on the single listener.

advanced:
  tls:
    enabled: true
    cert_path: /etc/ssl/certs/proxy.pem
    key_path: /etc/ssl/private/proxy-key.pem
FieldEnv varYAMLDefault
enabledDGP_TLS_ENABLEDadvanced.tls.enabledfalse
cert_pathDGP_TLS_CERTadvanced.tls.cert_pathAuto-generate self-signed
key_pathDGP_TLS_KEYadvanced.tls.key_pathAuto-generate

When cert_path and key_path are both absent, a self-signed certificate is generated on startup.


Config sync

Multi-instance IAM sync via S3. When enabled, the encrypted config DB file is replicated to a shared S3 bucket.

Env varDGP_CONFIG_SYNC_BUCKET
YAMLadvanced.config_sync_bucket
DefaultNone (disabled)
advanced:
  config_sync_bucket: dgp-iam-sync

Sync uses the same S3 credentials as the storage backend (DGP_BE_AWS_*) and only works when the storage backend is S3 (not filesystem). On every IAM mutation, the DB is uploaded to s3://<bucket>/.deltaglider/config.db; readers poll the S3 ETag every 5 minutes and download on change.


Multi-backend routing

Route different buckets to different storage backends. When backends is non-empty, the legacy single backend is ignored at runtime.

storage:
  default_backend: hetzner-fsn1
  backends:
    - name: hetzner-fsn1
      type: s3
      endpoint: https://fsn1.your-objectstorage.com
      region: fsn1
      access_key_id: HETZNER_KEY
      secret_access_key: HETZNER_SECRET
    - name: aws-dr
      type: s3
      endpoint: https://s3.eu-west-1.amazonaws.com
      region: eu-west-1
      access_key_id: AWS_KEY
      secret_access_key: AWS_SECRET
    - name: local-disk
      type: filesystem
      path: /var/lib/dgp-local
  buckets:
    db-archive:
      backend: hetzner-fsn1
      alias: acme-db-archive-prod

Backends can be added/removed via the admin GUI (Storage → Backends) without restart. default_backend is validated against the backends list at load time — invalid references are cleared with a warning.


Bucket policies

Per-bucket overrides. All fields optional.

storage:
  buckets:
    releases:
      compression: true
      max_delta_ratio: 0.9
      backend: hetzner-fsn1
      alias: acme-prod-releases-fsn1
      quota_bytes: 10737418240    # 10 GiB
    downloads:
      public_prefixes: ["public/"]
    docs-site:
      public: true                # shorthand for public_prefixes: [""]
FieldTypeDefaultDescription
compressionboolglobalEnable/disable delta compression for this bucket
max_delta_ratiofloat (0-1)globalOverride the delta-keep threshold
backendstringdefaultRoute to a named backend from storage.backends
aliasstringsame as bucket nameVirtual → real bucket name mapping on the backend
public_prefixes[string][]Anonymous read (GET/HEAD/LIST) scoped to these key prefixes
publicboolShorthand for public_prefixes: [""] (entire bucket public)
quota_bytesu64Soft storage quota (may overshoot by up to 5 minutes of writes); 0 = freeze bucket

Public prefixes

When public_prefixes (or public: true) is set, anonymous users can GET, HEAD, and LIST objects under the prefix. Writes always require authentication. Use trailing / for directory-aligned matching ("public/" matches public/installer.zip but not publicity/). The empty string "" makes the entire bucket public (logged as a warning). Prefixes containing .., null bytes, or // are rejected. The proxy synthesizes public-prefix:<bucket> admission blocks from this config.


Lifecycle rules

Expiration (delete) and transition/archive rules live under storage.lifecycle. Disabled by default; every delete and copy goes through the DeltaGlider engine.

storage:
  lifecycle:
    enabled: false
    tick_interval: "1h"
    max_failures_retained: 100
    rules:
      - name: expire-nightly-dumps
        enabled: false
        bucket: db-archive
        prefix: "nightly/"
        action: delete
        expire_after: "90d"
        include_globs: ["nightly/**/*.dump"]
        exclude_globs: [".deltaglider/**", "nightly/golden/**"]

Use POST /_/api/admin/jobs/lifecycle:<name>/preview (or the Preview button on the Jobs screen) before enabling a rule. See Lifecycle Rules for API details, skip rules, and limitations.


Event delivery

Durable object mutation events are always appended to the encrypted config DB when it is available. HTTP delivery is disabled by default; enabling advanced.event_delivery starts a background dispatcher that POSTs each event to every configured webhook endpoint.

advanced:
  event_delivery:
    enabled: true
    webhook_url: "https://events.example.com/deltaglider"
    webhook_urls:
      - "https://audit.example.com/deltaglider"
    webhook_headers:
      authorization: "Bearer redacted-token"
      x-dgp-env: "prod"
    tick_interval: "10s"
    batch_size: 50
    request_timeout: "5s"
    max_attempts: 8
    retry_base: "5s"
    retry_max: "5m"
    stale_claim_after: "60s"
    delivered_retention: "24h"
    delivered_max_rows: 10000
    prune_batch: 100

webhook_url is the single-endpoint shortcut. webhook_urls adds fan-out endpoints, and webhook_headers are attached to every delivery request. A row is marked delivered only after all endpoints return 2xx; failed rows back off and can be requeued from the admin API/UI. See Event outbox for payload and diagnostics details.

Slack format

Set format: slack to render each event as a Slack message (Block Kit + text fallback) instead of the raw {schema,event} envelope. Two modes — pick one:

advanced:
  event_delivery:
    enabled: true
    format: slack
    # Incoming Webhook mode (simplest, single channel):
    webhook_url: "https://hooks.slack.com/services/T000/B000/XXXX"
    slack_username: "DeltaGlider"        # optional cosmetic override
    slack_icon_emoji: ":package:"        # optional
    # Bot-token mode (multi-channel + @mentions) — set these INSTEAD of webhook_url:
    # slack_bot_token: "xoxb-..."        # needs chat:write + chat:write.public scopes
    # slack_channel: "C0123456"          # channel id or #name (required in this mode)
    # Scope what gets posted:
    slack_notify_kinds: ["ObjectCreated"]   # add ObjectDeleted, etc.
    slack_include_globs: ["firmware/**"]    # empty = all user objects
    slack_exclude_globs: ["**/*.tmp"]       # exclude wins over include
    # Per-bucket/prefix routing (bot-token mode only):
    slack_routes:
      - name: "Releases → #ci"
        bucket: releases
        prefix_globs: ["firmware/**"]    # empty = any key in the bucket
        channel: "C_CI"
KeyNotes
formatraw (default) or slack.
slack_bot_tokenxoxb-… Slack Web API token. Secret — masked on export, preserved on an untouched round-trip. Selects bot-token mode (chat.postMessage).
slack_channelTarget channel (C0123 or #name). Required in bot-token mode; ignored for Incoming Webhook URLs (each URL is bound to one channel by Slack).
slack_username / slack_icon_emojiCosmetic sender overrides (Incoming Webhook mode).
slack_notify_kindsWhich event kinds post. Default ["ObjectCreated"].
slack_include_globs / slack_exclude_globsKey-glob pre-filter (exclude wins).
slack_routesPer-bucket / per-prefix → channel routing (bot-token mode only). When non-empty, an eligible event posts to every matching route; slack_channel is the fallback for events matching no route.

The whole thing is editable from the admin GUI at Integrations → Event delivery (toggle the format to Slack). See Event outbox for delivery semantics.


Encryption at rest

Per-backend encryption with four modes: none, aes256-gcm-proxy, sse-kms, sse-s3. Each backend carries its own encryption block — operators can mix (e.g. SSE-KMS for the production backend, plaintext for a public-CDN backend) without sharing a single blast-radius key.

YAML — named-backends path:

storage:
  backends:
    - name: hetzner-fsn1
      type: s3
      # endpoint, region, credentials …
      encryption:
        mode: aes256-gcm-proxy
        key: "${env:DGP_BACKEND_HETZNER_FSN1_ENCRYPTION_KEY}"
        key_id: hetzner-2026-06   # optional; derived from SHA-256(name + key) when absent
    - name: aws-dr
      type: s3
      # region, credentials …
      encryption:
        mode: sse-kms
        kms_key_id: arn:aws:kms:eu-west-1:123456789012:key/abcd-ef01
        bucket_key_enabled: true

YAML — singleton-backend path (backends: empty):

storage:
  backend: { ... }
  backend_encryption:
    mode: aes256-gcm-proxy
    key: "${env:DGP_ENCRYPTION_KEY}"

Env vars (infra secrets — these are the recommended key source; every key / kms_key_id field in YAML is stripped by canonical exports):

Env varBinds to
DGP_ENCRYPTION_KEYbackend_encryption.key (singleton path)
DGP_BACKEND_<NAME>_ENCRYPTION_KEYbackends[name=<NAME>].encryption.key
DGP_SSE_KMS_KEY_IDbackend_encryption.kms_key_id (singleton SSE-KMS)
DGP_BACKEND_<NAME>_SSE_KMS_KEY_IDnamed SSE-KMS override

Name normalisation: <NAME> is uppercased; - and . become _ (so hetzner-fsn1DGP_BACKEND_HETZNER_FSN1_ENCRYPTION_KEY).

Defaults: absent encryption block → mode: none (plaintext).

Formats: key / legacy_key are 64-char lowercase hex (256 bits). kms_key_id is a KMS ARN or alias. key_id (optional) must match [A-Za-z0-9_.-]{1,64} (S3 user-metadata header-safe).

Rotation within a single mode is not automated — use the legacy_key / legacy_key_id shim fields (decrypt-only, for proxy→native transitions) or copy objects to a new backend. See the encryption reference for the full wire format, key-id mismatch mechanics, and the shim lifecycle.


CLI subcommands

See Command-line tools.


Full example

A kitchen-sink YAML covering every top-level section. Fields omitted here inherit their defaults.

# deltaglider_proxy.yaml

# Operator-authored admission chain (pre-auth gating)
admission:
  blocks:
    - name: deny-known-bad-ips
      match:
        source_ip_list: ["198.51.100.0/24"]
      action: deny

    - name: allow-public-zips
      match:
        method: [GET, HEAD]
        bucket: releases
        path_glob: "*.zip"
      action: allow-anonymous

# SigV4 credentials + IAM mode
access:
  iam_mode: gui               # or declarative
  access_key_id: admin
  secret_access_key: changeme

# Backends + per-bucket overrides
storage:
  default_backend: hetzner-fsn1
  backends:
    - name: hetzner-fsn1
      type: s3
      endpoint: https://fsn1.your-objectstorage.com
      region: fsn1
      force_path_style: true
      access_key_id: HETZNER_KEY
      secret_access_key: HETZNER_SECRET
    - name: aws-dr
      type: s3
      endpoint: https://s3.eu-west-1.amazonaws.com
      region: eu-west-1
      access_key_id: AWS_KEY
      secret_access_key: AWS_SECRET
  buckets:
    releases:
      backend: hetzner-fsn1
      compression: true
    db-archive:
      backend: aws-dr
      alias: acme-db-archive-prod
      compression: false
    downloads:
      public_prefixes: ["public/"]

# Process-level tunables
advanced:
  listen_addr: "0.0.0.0:9000"
  log_level: deltaglider_proxy=info,tower_http=warn
  max_delta_ratio: 0.75
  cache_size_mb: 2048
  metadata_cache_mb: 100
  codec_concurrency: 32
  config_sync_bucket: dgp-iam-sync
  event_delivery:
    enabled: true
    webhook_urls:
      - "https://audit.example.com/deltaglider"
    webhook_headers:
      authorization: "Bearer redacted-token"
  tls:
    enabled: true
    cert_path: /etc/ssl/certs/proxy.pem
    key_path: /etc/ssl/private/proxy-key.pem

Equivalent environment variables for container deployments:

DGP_LISTEN_ADDR=0.0.0.0:9000
DGP_MAX_DELTA_RATIO=0.75
DGP_MAX_OBJECT_SIZE=104857600
DGP_CACHE_MB=2048
DGP_METADATA_CACHE_MB=100
DGP_CODEC_CONCURRENCY=32
DGP_LOG_LEVEL=deltaglider_proxy=info,tower_http=warn
DGP_ACCESS_KEY_ID=admin
DGP_SECRET_ACCESS_KEY=changeme
DGP_BOOTSTRAP_PASSWORD_HASH=JDJiJDEyJENYbDVPRm84bDg2...
DGP_CONFIG_SYNC_BUCKET=dgp-iam-sync
DGP_S3_ENDPOINT=https://fsn1.your-objectstorage.com
DGP_S3_REGION=fsn1
DGP_S3_PATH_STYLE=true
DGP_BE_AWS_ACCESS_KEY_ID=HETZNER_KEY
DGP_BE_AWS_SECRET_ACCESS_KEY=HETZNER_SECRET
DGP_TLS_ENABLED=true
DGP_TLS_CERT=/etc/ssl/certs/proxy.pem
DGP_TLS_KEY=/etc/ssl/private/proxy-key.pem

Environment variable registry

Exhaustive list of every DGP_* variable the server reads. The unit test test_registry_completeness in src/config.rs enforces that this list and ENV_VAR_REGISTRY stay in sync.

Server / Advanced

VariableDefaultDescription
DGP_CONFIGautoPath to the YAML config file (.yaml / .yml)
DGP_LISTEN_ADDR0.0.0.0:9000HTTP listen address
DGP_LOG_LEVELdeltaglider_proxy=debug,tower_http=debugTracing filter (overridden by RUST_LOG)
DGP_BLOCKING_THREADS512Max tokio blocking threads
DGP_REQUEST_TIMEOUT_SECS300Per-request timeout (returns 504)
DGP_MAX_CONCURRENT_REQUESTS1024Tower concurrency limit
DGP_MAX_MULTIPART_UPLOADS1000Concurrent multipart upload cap
DGP_DEBUG_HEADERSfalseExpose fingerprinting headers
DGP_CORS_PERMISSIVEfalseEnable permissive CORS (dev only)

Delta engine

VariableDefaultDescription
DGP_MAX_DELTA_RATIO0.75Keep delta only if delta/original < ratio
DGP_MAX_OBJECT_SIZE104857600Max bytes eligible for delta (xdelta3 mem cap)
DGP_CACHE_MB100Reference cache size in MB
DGP_METADATA_CACHE_MB50FileMetadata cache size in MB (0 to disable)
DGP_CODEC_CONCURRENCYnum_cpus * 4 (min 16)Max concurrent xdelta3 subprocesses
DGP_CODEC_TIMEOUT_SECS60Per-subprocess timeout

Storage

VariableDefaultDescription
DGP_DATA_DIR./dataFilesystem backend data directory
DGP_S3_ENDPOINTS3 endpoint (activates S3 backend when set)
DGP_S3_REGIONus-east-1AWS region
DGP_S3_PATH_STYLEtrueUse path-style URLs (MinIO/LocalStack)
DGP_BE_AWS_ACCESS_KEY_IDBackend S3 access key
DGP_BE_AWS_SECRET_ACCESS_KEYBackend S3 secret key

Authentication

VariableDefaultDescription
DGP_AUTHENTICATION"none" for open access; absent = auto-detect
DGP_ACCESS_KEY_IDProxy SigV4 access key
DGP_SECRET_ACCESS_KEYProxy SigV4 secret key
DGP_BOOTSTRAP_PASSWORD_HASHautoBcrypt hash (legacy alias: DGP_ADMIN_PASSWORD_HASH)
DGP_BOOTSTRAP_PASSWORDPlaintext password for admin CLI only

Security

VariableDefaultDescription
DGP_TRUST_PROXY_HEADERSfalseTrust X-Forwarded-For / X-Real-IP
DGP_SESSION_TTL_HOURS4Admin session lifetime
DGP_CLOCK_SKEW_SECONDS300SigV4 clock skew tolerance
DGP_REPLAY_WINDOW_SECS2SigV4 replay detection window
DGP_SECURE_COOKIEStrueRequire HTTPS for session cookies
DGP_RATE_LIMIT_MAX_ATTEMPTS100Max auth failures before lockout
DGP_RATE_LIMIT_WINDOW_SECS300Rate-limit rolling window
DGP_RATE_LIMIT_LOCKOUT_SECS600Lockout duration

TLS / Config sync / Encryption at rest / Misc

VariableDefaultDescription
DGP_TLS_ENABLEDfalseEnable HTTPS
DGP_TLS_CERTauto self-signedPEM cert path
DGP_TLS_KEYauto self-signedPEM key path
DGP_CONFIG_SYNC_BUCKETS3 bucket for encrypted-DB multi-instance sync
DGP_ENCRYPTION_KEYSingleton-backend AES-256 key (64-char hex). Named backends use DGP_BACKEND_<NAME>_ENCRYPTION_KEY.
DGP_SSE_KMS_KEY_IDSingleton-backend SSE-KMS ARN/alias. Named backends use DGP_BACKEND_<NAME>_SSE_KMS_KEY_ID.

Consumed only by tests / build

VariableConsumer
DGP_BUILD_TIMEbuild.rs (compile-time timestamp)
DGP_BUCKEThistorical comment in tests; no longer read