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

  • skopeo on PATH (the replication primitive). If it is absent, registry seed fails 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

--from

Source OCI reference (tag or digest). Required.

--to

Destination OCI reference (tag). Required.

--copy-policy

Also replicate the <tag>-lock / <tag>-policy sidecars. Requires tag-based --from and --to — the sidecar tags can’t be derived from a digest ref. (Copying referrers attached to a digest source is a later phase.)

--pub-key

Cosign public key (SPKI PEM) for fail-closed destination verification.

--skip-verify

Bypass verification (fail-open; dev only — prints a loud warning).

--allow-insecure-registry

Permit plain-HTTP registries (e.g. localhost).

--out

Directory for seed-manifest.json + seed.lock (default: .).

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 stageautonomy 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-search extension).

  • 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 binary not found in PATH

skopeo not installed

Install skopeo (see Prerequisites)

registry seed requires --pub-key or --skip-verify

fail-closed gate

Pass --pub-key, or --skip-verify for dev

--copy-policy requires tag-based --from and --to

--copy-policy with a digest --from/--to

Use tag refs with --copy-policy, or drop --copy-policy for a digest-pinned image-only seed

resolve manifest digest / empty content digest resolved

source/destination unreadable (auth, transient, missing)

Fix registry access; the seed fail-closes rather than emit an empty digest

destination verification failed (fail-closed)

signature/digest/fingerprint/semver mismatch

Re-seed from a trusted source; confirm the public key matches the signer

sidecar not replicated warning

source bundle has no -lock/-policy sidecar

Expected when the bundle ships no attached governance artifacts

plain-HTTP registry refused

TLS expected

Add --allow-insecure-registry for local/dev registries