Connect your clients: Docker, containerd, GitOps
Content has landed in your zone — promoted continuously by a passthrough instance, or imported from removable media in an isolated zone. Either way, this page is the end of the journey: pointing the machines that consume it at the right place. Everything here applies to both modes.
Why docker.io/x becomes registry.zone/docker.io/x
Section titled “Why docker.io/x becomes registry.zone/docker.io/x”Tobby relocates every ingredient under its nominal source host:
<zone-registry>[/<base-prefix>]/<canonical-source-host>[_<port>]/<repository-path>docker.io/bitnami/wordpress is pullable at
registry.zone.example/docker.io/bitnami/wordpress, with an unchanged
digest. The rule (ADR-0013, FR-035) buys three
things at the cost of longer names:
- No collisions. A flattened
registry.zone/bitnami/wordpresscannot telldocker.io/foo/barfromghcr.io/foo/bar— a correctness bug and a repository-confusion attack surface in one. - Predictability. Given a recipe and a destination, the location of every ingredient is computable with no extra metadata — audits, cleanup tooling, and Tobby’s own differential comparison all rely on the same pure function.
- Cascade invariance. The path is derived from the host written in the recipe, not the host contacted, so it is identical in every zone of a multi-hop chain.
Hosts are canonicalized (lowercased; index.docker.io and
registry-1.docker.io fold to docker.io), and a port’s : becomes
_ — lab.example.com:5000 relocates under lab.example.com_5000/.
Names are never truncated: a destination that cannot take a relocated
name fails explicitly, before the push.
The mapping table, per recipe
Section titled “The mapping table, per recipe”You never compute those paths by hand. Each recipe exposes its source→destination table — every ingredient’s nominal reference, its relocated repository, tag, and pinned digest (FR-065):
- UI: the recipe’s mapping view at
/recipes/{recipe}/mapping, with copyable references. - API:
GET /api/v1/recipes/{recipe}/mapping— the same data as JSON, one entry per resolved version, ingredients included.

containerd mirrors for K3s and RKE2
Section titled “containerd mirrors for K3s and RKE2”For runtime image pulls, you do not have to touch a single chart
value: containerd rewrites references at pull time. On each K3s/RKE2
node, write /etc/rancher/{k3s,rke2}/registries.yaml by hand from the
mapping table:
mirrors: docker.io: endpoint: - "https://registry.zone.example" rewrite: "^(.*)$": "docker.io/$1" ghcr.io: endpoint: - "https://registry.zone.example" rewrite: "^(.*)$": "ghcr.io/$1"configs: registry.zone.example: auth: username: puller password: "…"With that in place, a pod referencing docker.io/bitnami/wordpress
pulls from the zone registry, and the chart deploys as published —
values untouched, digests intact.
What mirrors do not cover
Section titled “What mirrors do not cover”Two classes of reference bypass containerd and must name relocated paths explicitly, using the mapping table:
- GitOps chart sources. An Argo CD
repoURLor FluxHelmRepositorypointing atoci://registry.zone.example/...must use the relocated chart path — the Helm client does not go through node-level mirrors. - Admission policies. Rules matching image references (Kyverno, Gatekeeper, signature-verification admission) see the reference as written in the pod spec. Decide which side of the rewrite your policies match, and keep it consistent.
One more caveat for verifiers: cosign signatures embed the origin
reference in critical.identity.docker-reference. Tobby copies
signatures bit-exact alongside the content, so a policy engine that
matches docker-reference against the pulled location will see
docker.io/... while pulling from registry.zone.example/....
Configure the policy against the nominal reference. Details in
content trust.
Tobby never rewrites chart values — that would break digests and signatures, the exact thing it exists to preserve.
Logging in
Section titled “Logging in”The embedded registry and the zone-facing surfaces use the instance’s own accounts (FR-076) — the same ones as the UI:
docker login registry.tobby.zone.examplehelm registry login registry.tobby.zone.exampleoras login registry.tobby.zone.exampleCreate dedicated read-only accounts or CI tokens for consumers; see authentication and RBAC.
OS packages over HTTP: the /files/ endpoint
Section titled “OS packages over HTTP: the /files/ endpoint”A FileSet ingredient can package an apt or rpm repository, and Tobby
can serve its verified content read-only over HTTP under
/files/<name>/… (FR-047) — making a bare host installable with no
other infrastructure. Serving is off by default and enabled per
FileSet:
files: filesets: - name: debs # served under /files/debs/ ref: registry.example.com/filesets/site-packages version: "1.4.0" # empty = highest semver present locally platform: linux/amd64 # only needed for multi-platform FileSets anonymous: true # opt-in unauthenticated readsOnly what the store holds and verified is served; range requests are
supported; there is no upload surface. Reads require the viewer role by
default. anonymous: true exists for the bootstrap case — a bare host
that cannot authenticate before it has installed anything — and is never
silent: every anonymously served FileSet is named in a permanent UI
banner and reported by the API, like every other relaxation of the
secure default (FR-075).
# /etc/apt/sources.list.d/zone.list on a client hostdeb [trusted=yes] https://tobby.zone.example/files/debs stable mainPackage-manager trust of the repository metadata is your distribution’s mechanism; what Tobby guarantees is that the served tree came from a FileSet that passed signature verification on its way into the store.
Packing files that have no recipe
Section titled “Packing files that have no recipe”Sometimes the files you need to serve have no upstream to fetch them from:
a handful of vendor drivers, a locally built repository, a bundle handed
over on a disk. tobby fileset pack turns a directory into a FileSet — a
standard OCI image whose single layer is the directory’s file tree — and
imports it into the store, pinned by its digest:
tobby fileset pack ./apt-repo debs:1.0.0Packing is reproducible: the same directory always produces the same digest, so packing twice transfers nothing the second time. Timestamps and ownership are deliberately not carried — only the file tree, its permissions and its symbolic links. The directory is refused, naming the entry, when it holds something a FileSet extraction would have to refuse anyway: a symbolic link leaving the directory, a setuid bit, a device node or a socket, or a name the image layer format reserves.
Two limits, stated because they are the point:
- A packed FileSet is unsigned. Tobby holds no signing key, so it is recorded as a manual import of local origin and every listing says so. It is content you vouch for, not content the trust roots vouch for.
- Serving it is a separate, explicit step. Packing puts it in the
store; nothing is served until it is named under
files.filesetsand the instance is restarted. The command prints the exact block to add.
The command reads the host’s filesystem with the rights of whoever runs it.
The same operation on the interface and the API (POST /filesets/pack) is
restricted to administrators and confined to the directories
files.packRoots names — with no entry configured, the form is not offered
at all. This is the only way local files enter a store, and it is
deliberately not an upload endpoint.
Next: bring content in outside any recipe — one-off imports — or jump to operating over time.