Documentation
ReferenceArchitecture

Container Registry

The Tabbify registry is where your app images and WASM components live. It speaks the standard OCI Distribution API on /v2, but it has no public IP — it joins the private mesh and answers only on its mesh address. You push from one peer and pull from another through encrypted WireGuard tunnels, with per-tenant isolation enforced at the door.

How it is built

The registry is a thin Rust wrapper around Zot. The wrapper runs as PID 1, supervises a Zot child on loopback 127.0.0.1:5001 (anonymous, never exposed), joins the mesh tagged registry with a sticky identity, and reverse-proxies /v2 on its assigned ULA at port 5000. Zot does the storage; the wrapper does auth, namespace enforcement, and mesh integration. Large layers stream through both directions — nothing is buffered in memory.

Addressing

The registry binds [my_ula]:5000. The ULA is derived from the mesh coordinator and persisted under REGISTRY_DATA_DIR, so it stays stable across restarts:

[fd5a:1f02:xxxx:xxxx::1]:5000

Because the registry serves plain HTTP over the encrypted mesh, any Docker daemon pulling from it must list that address in insecure-registries (/etc/docker/daemon.json).

Authentication

When REGISTRY_AUTH_URL is set, every /v2 request is gated. The wrapper validates your tabbify JWT against auth and mints a short-lived (300s) HS256 registry token signed by a per-process secret. The hot path verifies that token locally — no per-request call to auth.

This is the standard OCI bearer flow. Log in with your tabbify JWT as the password (the username is ignored):

docker login [fd5a:1f02:xxxx:xxxx::1]:5000 -u x -p <tabbify-jwt>

Under the hood: a first /v2 hit returns 401 with a challenge pointing at /auth/token; the client exchanges your JWT there, gets back {token, access_token, expires_in: 300}, and retries with Authorization: Bearer <registry_token>. The wrapper calls auth's POST /v1/validate and reads the network claim (falling back to subject) as your tenant.

Per-tenant namespaces

Phase 1 enforces a hard rule: every repository path must start with <tenant>/. A token for tenant acme can touch /v2/acme/... and nothing else.

namespace::check("acme", "/v2/acme/app/manifests/v1")   -> Allow
namespace::check("acme", "/v2/globex/app/manifests/v1")  -> Deny   (403)
namespace::check("acme", "/v2/")                          -> Allow  (version check)
namespace::check("acme", "/v2/_catalog")                 -> Deny   (Phase 1)

Pushing images and WASM

Docker images push and pull exactly as you expect:

docker tag busybox [fd5a:1f02:xxxx:xxxx::1]:5000/myteam/demo:v1
docker push [fd5a:1f02:xxxx:xxxx::1]:5000/myteam/demo:v1

WASM components are stored as OCI artifacts under the same /v2 API with media type application/vnd.tabbify.wasm.component.v1. Use oras, not docker:

oras push --plain-http \
  --artifact-type application/vnd.tabbify.wasm.component.v1 \
  [fd5a:1f02:xxxx:xxxx::1]:5000/myteam/hello-wasm:v1 \
  hello.wasm:application/wasm

Storage

REGISTRY_STORAGE picks the backend: local (filesystem under REGISTRY_DATA_DIR/zot-cache, the always-green dev default) or s3 (set REGISTRY_S3_BUCKET and REGISTRY_S3_REGION). On AWS, credentials come from the EC2 instance role — no static keys in env. Both modes keep a local boltdb dedupe cache.

Running it

docker run -d \
  --device /dev/net/tun --cap-add NET_ADMIN \
  -e REGISTRY_STORAGE=local \
  -e REGISTRY_AUTH_URL=http://auth-service:8080 \
  -v tbf-registry:/var/lib/tabbify-registry \
  ghcr.io/tabbify-io/tabbify-registry

Mesh membership needs the TUN device and NET_ADMIN. Mount a volume at REGISTRY_DATA_DIR or you lose the sticky ULA on restart.

Where things stand: Zot supervision, mesh join, the streaming /v2 proxy, JWT auth, per-tenant namespaces, and both storage modes are live on AWS and verified end-to-end over the mesh. Leave REGISTRY_AUTH_URL unset for anonymous dev mode — no token, no namespace enforcement, local only. The catalog endpoint and ACLs beyond per-tenant are Phase 2; signed images and garbage collection are Phase 3.

See also The Node API, The deploy pipeline, and the CLI reference.