Registry Seeding for Edge / Air-Gapped Sites (Operator Guide)¶
autonomy registry seed replicates a bundle from a source registry into a
destination (edge / air-gapped) registry, verifies the destination fail-closed,
and emits a digest-pinned seed manifest you can carry as an audit artifact.
ADK is the governance layer, not the transport: image replication is
delegated to skopeo (preserving
multi-arch manifest lists), and verification reuses the same 4-step pipeline as
autonomy bundle verify. ADK does not implement a custom
registry, transport protocol, or replication system.
Prerequisites¶
skopeoonPATH(the replication primitive). If it is absent,registry seedfails fast with an install hint (ErrSkopeoNotFound).Network reachability from the seeding host to both registries (this host is the bridge; the destination edge registry need not reach the source).
A destination OCI registry (Zot is the documented edge target; any standards-compliant registry works — Harbor, GHCR, ECR,
registry:2).For verification (default): the bundle’s cosign public key (SPKI PEM).
The flow¶
1. skopeo copy --all replicate the image, preserving the multi-arch manifest list
2. (--copy-policy) replicate the <tag>-lock / <tag>-policy sidecars
3. resolve digests pin the immutable source + destination content digests
4. bundle verify (4-step) fail-closed signature / digest / fingerprint / semver of the destination
5. emit artifacts seed-manifest.json + a digest-pinned seed.lock, into --out
Usage¶
# Seed from a tag, copying the lock/policy sidecars, verifying the result.
autonomy registry seed \
--from registry.upstream.example.com/robot-behavior:v1 \
--to localhost:5000/robot-behavior:v1 \
--copy-policy \
--pub-key ./cosign.pub \
--out ./seed
# Seed from an immutable digest (image only — see the --copy-policy note below)
autonomy registry seed \
--from registry.upstream.example.com/robot-behavior@sha256:... \
--to localhost:5000/robot-behavior:v1 \
--pub-key ./cosign.pub --out ./seed
Flag |
Purpose |
|---|---|
|
Source OCI reference (tag or digest). Required. |
|
Destination OCI reference (tag). Required. |
|
Also replicate the |
|
Cosign public key (SPKI PEM) for fail-closed destination verification. |
|
Bypass verification (fail-open; dev only — prints a loud warning). |
|
Permit plain-HTTP registries (e.g. |
|
Directory for |
Verification is fail-closed. You must pass --pub-key, or explicitly opt
out with --skip-verify. Running with neither is an error.
Air-gapped workflow¶
On a connected staging host, seed into a local registry and capture the seed
directory; carry the registry’s storage (or a registry:2 data volume) plus the
seed directory across the air gap on removable media; on the disconnected side,
serve the registry and feed the destination ref into the existing
autonomy bundle stage → autonomy policy load flow.
Because every reference is digest-pinned, the disconnected node verifies against
an immutable digest, never a mutable tag.
The seed manifest¶
seed-manifest.json is an audit artifact recording the immutable provenance of
the seed:
{
"schema_version": "seed.v1",
"source_ref": "registry.upstream.example.com/robot-behavior:v1",
"source_digest": "sha256:...",
"dest_ref": "localhost:5000/robot-behavior:v1",
"dest_digest": "sha256:...",
"platforms": ["linux/amd64", "linux/arm64", "linux/riscv64"],
"copied_policy": true,
"signing_key_ref": "./cosign.pub",
"verified": true,
"seeded_at": "2026-05-25T00:00:00Z"
}
seed.lock carries the destination as registry/repo@sha256:<digest> so
downstream activation pins the exact content.
Verification semantics¶
The destination is verified with the 4-step bundle pipeline (signature, digest, BLAKE3 fingerprint, policy semver) before the seed is declared complete. Any failed step aborts the seed and no manifest is written — the seed is not “done” unless the destination is trustworthy.
Catalog seeding (multiple bundles in one pass)¶
autonomy registry seed-catalog seeds every bundle listed in a catalog file,
composing registry seed per entry — the typical air-gapped case (seed a whole
site’s bundles from one transport). Each entry is replicated and verified into
its own subdirectory of --out (carrying its own seed-manifest.json +
seed.lock), and a catalog-level catalog-seed-manifest.json rolls up every
entry’s immutable result.
# site-a.yaml
version: 1
entries:
- from: registry.upstream.example.com/robot-behavior:v1
to: localhost:5000/robot-behavior:v1
copy_policy: true
pub_key: ./cosign.pub
- from: registry.upstream.example.com/nav-stack:v2
to: localhost:5000/nav-stack:v2
pub_key: ./cosign.pub
autonomy registry seed-catalog --catalog ./site-a.yaml --out ./seed
Registry-wide knobs (--allow-insecure-registry, --skip-verify,
--keep-going, --out) are flags; the file carries only the per-bundle
entries (from, to, copy_policy, pub_key). Default is fail-fast (stop
on the first entry that fails, no catalog manifest emitted); --keep-going
attempts every entry, writes the roll-up recording each outcome, and still
exits non-zero if any entry failed (fail-closed overall).
Bootstrapping an edge registry (Zot)¶
If a fresh edge site has no registry to seed into yet, autonomy registry bootstrap-zot generates a ready-to-run Zot config —
the documented edge target. It only generates artifacts (BYO runtime); it
does not pull or start anything.
autonomy registry bootstrap-zot --out ./edge --port 5000
docker compose -f ./edge/docker-compose.zot.yml up -d # operator runs this
autonomy registry seed --to localhost:5000/<bundle>:<tag> --from ... --pub-key ...
It writes two files into --out:
zot-config.json— a minimal Zot config (storage with GC + dedupe, HTTP address/port, optional--enable-searchextension).docker-compose.zot.yml— a compose service that mounts the config + a host data directory (--data-dir) and serves on--port.
The generated compose references a pinned, multi-arch Zot image by default,
so it runs out-of-the-box on both linux/amd64 and linux/arm64 edge hosts and
is reproducible (no moving :latest). Override it with --image for a minimal
variant, a different version, or a digest pin. The generated compose is
operator-editable scaffolding — add TLS/auth before exposing it beyond the host.
Offline transport with Zarf (registry package)¶
For sites with no network bridge to the source at all — “seed a disconnected
site from a USB stick” — autonomy registry package generates a
Zarf package definition from the same catalog file.
Zarf bakes the catalog’s source images into a single zarf-package-*.tar.zst
tarball you carry across the air gap; on the edge host zarf package deploy
pushes them into the in-cluster registry.
Like bootstrap-zot, ADK generates the definition only (BYO Zarf runtime); it
does not pull images or build the tarball.
autonomy registry package --catalog ./site-a.yaml --out ./zarf --name site-a --version 1.0.0
zarf package create ./zarf # bakes the images into a tarball
# carry zarf-package-*.tar.zst across the air gap, then on the edge host:
zarf package deploy zarf-package-*.tar.zst # pushes into the edge registry
The generated zarf.yaml lists the catalog’s source (from) refs as the
component images — what Zarf pulls and bakes; Zarf rewrites them to the
destination registry at deploy time. --name is sanitized to a Zarf-legal
package name; --version defaults to 0.0.0.
Edge-to-edge sync (registry sync)¶
Once one edge node is seeded, autonomy registry sync re-seeds a peer directly,
without a round-trip to the upstream source. It builds one seed per repo (from
A/<repo> to B/<repo>) and composes seed-catalog. The incremental delta is
provided naturally by skopeo, which skips blobs the destination already holds
(both ends dedupe by content digest), so only changed layers cross the wire.
autonomy registry sync \
--from-registry edge-a.local:5000 \
--to-registry edge-b.local:5000 \
--repos robot-behavior:v1,fleet/nav-stack:v2 \
--pub-key ./cosign.pub --out ./sync
Verification is fail-closed exactly like registry seed: pass --pub-key, or
explicitly --skip-verify. Each repo is verified against the same key. The
--copy-policy, --allow-insecure-registry, --keep-going, and --out flags
behave as they do for seed-catalog.
Referrer-based catalog discovery (registry publish-index)¶
registry seed attaches each bundle’s governance sidecars per-bundle. To make a
whole seeded set discoverable from one place, autonomy registry publish-index publishes the catalog roll-up (catalog-seed-manifest.json) as
an OCI artifact referrer on a parent index ref. A single
Referrers-API
query against the index then lists the entire catalog.
autonomy registry publish-index \
--manifest ./seed/catalog-seed-manifest.json \
--to localhost:5000/site-catalog:v1
On registries without the Referrers API (e.g. registry:2) it falls back to a
<tag>-catalog sidecar tag (site-catalog:v1-catalog). It is fail-closed
about what it publishes: the file must be a valid catalog roll-up
(schema_version: catalog-seed.v1); an arbitrary JSON file is rejected rather
than published under the catalog artifact type.
Scope¶
All of the following are implemented under autonomy registry:
seed— single source → single destination, sidecar-tag replication, fail-closed verify, digest-pinned seed manifest.seed-catalog— multi-bundle one-pass seeding + roll-up manifest.bootstrap-zot— edge-registry config + compose generation.package— Zarf package definition for single-tarball offline transport.sync— edge-to-edge peer re-seeding (delta via skopeo).publish-index— catalog roll-up published as a Referrers-API-discoverable OCI artifact (sidecar-tag fallback on legacy registries).
Troubleshooting¶
Symptom |
Cause |
Fix |
|---|---|---|
|
skopeo not installed |
Install skopeo (see Prerequisites) |
|
fail-closed gate |
Pass |
|
|
Use tag refs with |
|
source/destination unreadable (auth, transient, missing) |
Fix registry access; the seed fail-closes rather than emit an empty digest |
|
signature/digest/fingerprint/semver mismatch |
Re-seed from a trusted source; confirm the public key matches the signer |
sidecar |
source bundle has no |
Expected when the bundle ships no attached governance artifacts |
plain-HTTP registry refused |
TLS expected |
Add |