Skip to content

feat: infer upstream registry from containerd ns= query parameter #1791

@bumarcell

Description

@bumarcell

Problem

When containerd is configured to use a registry mirror, it appends a ?ns=<registry> query parameter to the request URL. For example:

GET /v2/library/nginx/manifests/latest?ns=docker.io

This ns parameter tells the mirror which upstream registry the request is intended for.

Currently, dfdaemon's proxy only reads the X-Dragonfly-Registry header to determine the upstream registry. If that header is missing, it falls back to config.proxy.registry_mirror.addr, which is a single static value.

This creates a significant limitation: it is impossible to use a single containerd _default/hosts.toml catch-all configuration to route all registries through dfdaemon. Instead, each registry (docker.io, ghcr.io, gcr.io, etc.) needs its own hosts.toml file with the X-Dragonfly-Registry header explicitly set. This is cumbersome to maintain and doesn't scale well.

Example of the current workaround

Each registry requires its own hosts.toml:

# /etc/containerd/certs.d/docker.io/hosts.toml
[host."http://127.0.0.1:4001"]
  capabilities = ["pull", "resolve"]
  [host."http://127.0.0.1:4001".header]
    X-Dragonfly-Registry = ["https://index.docker.io"]

# /etc/containerd/certs.d/ghcr.io/hosts.toml
[host."http://127.0.0.1:4001"]
  capabilities = ["pull", "resolve"]
  [host."http://127.0.0.1:4001".header]
    X-Dragonfly-Registry = ["https://ghcr.io"]

Desired behavior

A single _default/hosts.toml should be sufficient:

# /etc/containerd/certs.d/_default/hosts.toml
[host."http://127.0.0.1:4001"]
  capabilities = ["pull", "resolve"]

Dfdaemon should automatically infer the upstream registry from the ns query parameter.

Proposed Solution

In dragonfly-client/src/proxy/mod.rs, in the make_registry_mirror_request() function (or the equivalent location where the upstream registry is resolved), add a fallback:

  1. Check for the X-Dragonfly-Registry header (existing behavior).
  2. If the header is missing, extract the ns query parameter from the request URI.
  3. Use the ns value as the upstream registry (prepending https:// if needed).
  4. Fall back to config.proxy.registry_mirror.addr only if neither the header nor the ns parameter is present.

Pseudocode

let registry = if let Some(header) = request.headers().get("X-Dragonfly-Registry") {
    header.to_str()?.to_string()
} else if let Some(ns) = extract_query_param(request.uri(), "ns") {
    format!("https://{}", ns)
} else {
    config.proxy.registry_mirror.addr.clone()
};

Why This Matters

  • Simpler containerd configuration: One _default/hosts.toml instead of N per-registry files
  • Dynamic registry support: New registries work automatically without config changes
  • Alignment with containerd conventions: The ns parameter is a standard part of containerd's mirror protocol and other registry mirrors already use it
  • Backward compatible: The X-Dragonfly-Registry header still takes priority; this only adds a new fallback

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions