Skip to main content
Docs navigation

Host your own registry

An emisar pack registry is a static file tree over HTTPS — host it in a bucket, nginx, MinIO, or an artifact store. packctl builds the tree, and each pack installs from an immutable URL pinned to the hash you reviewed.

Registry paths become v1 compatibility surfaces. Read Compatibility and deprecation before you publish a stable internal endpoint.

Distribution and trust are separate. The runner checks the supplied sha256 hash at install and again when it loads the pack, and the control plane auto-trusts only entries from its configured catalog. Other packs remain pending until an admin trusts their exact version and hash.

Use your coding agent

The author-pack skill walks your agent through authoring, publishing, and installing from your own registry.

paste into your agent
Author an emisar action pack, publish it to my own pack registry, and install it hash-pinned on a runner. Use the author-pack skill: https://raw.githubusercontent.com/AndrewDryga/emisar/main/skills/author-pack/SKILL.md

Works in Claude Code, Codex, or any agent that reads Markdown skills — read the skill first .

This page starts from packs you already authored — the format is in Author your own pack, and every field is in the Pack reference.

1. Get packctl#

packctl builds and publishes registries. It is deliberately a separate binary from emisar, so your fleet hosts never carry publish code or publisher credentials — only the workstation or CI job that builds the registry does. It shares the runner's pack loader, so published content hashes match emisar pack validate byte-for-byte.

shell
go install github.com/andrewdryga/emisar/runner/cmd/packctl@latest

2. Build the registry tree#

--base-url is wherever you will host it. Tarball URLs in the catalog join onto it:

shell
packctl catalog build --packs ./packs --out ./dist \
  --base-url https://packs.acme.internal

The output is the whole registry: immutable versioned and content-addressed artifacts, plus two mutable pointers:

v1/catalog.json                                  latest catalog (mutable pointer)
v1/catalog/<sha256>.json                         immutable catalog snapshot
v1/suggest.json                                  lean suggest index (mutable pointer)
v1/schemas/*.vN.schema.json                      immutable versioned schemas
v1/packs/<id>/<version>/<sha256>/pack.tar.gz     immutable pack tarball

3. Host it anywhere#

packctl can publish directly to GCS. It protects immutable objects with upload preconditions, so existing objects are never overwritten, and it updates the two mutable pointers last:

shell
GOOGLE_OAUTH_ACCESS_TOKEN=$(gcloud auth print-access-token) \
  packctl catalog publish --dir ./dist --bucket acme-pack-registry

For S3, MinIO, nginx, or another static host, sync the plain file tree with your preferred tool. Upload immutable objects first, then the two mutable pointers:

shell
aws s3 sync ./dist s3://acme-pack-registry \
  --exclude "v1/catalog.json" --exclude "v1/suggest.json"
aws s3 cp ./dist/v1/suggest.json s3://acme-pack-registry/v1/suggest.json
aws s3 cp ./dist/v1/catalog.json s3://acme-pack-registry/v1/catalog.json

4. Install fleet-wide#

Install the pack from its immutable tarball URL, pinned to the hash from catalog.json — the runner rejects a different hash. Use the same URL and hash on every runner, and update by installing the new version's immutable URL.

shell
sudo emisar pack install \
  https://packs.acme.internal/v1/packs/billing-tools/0.4.0/<sha256>/pack.tar.gz \
  --hash sha256:<reviewed>

Trust the exact pack version and hash on the Packs page. Actions use account trust, not the download host.

5. Publish the next version#

Always build against the currently-published catalog so history carries forward. --previous enforces immutability: a pack whose files changed for an already-published id@version fails the build — bump the version instead. On a critical fix, republish, then revoke trust on the old version in Packs — the control plane refuses new dispatches for it, and the runner separately checks the delivered hash against its installed files.

shell
curl -fsS https://packs.acme.internal/v1/catalog.json -o current.json
packctl catalog build --packs ./packs --out ./dist \
  --base-url https://packs.acme.internal --previous current.json

What your registry changes#

  • emisar works unchanged. Runners advertise installed packs no matter where they came from, and trust, policy, approvals, and audit treat a private pack exactly like a published one.
  • Pack downloads can stay inside your network. Mirror the public registry tree, or build one from your own checkout of the public packs/ source.
  • Suggestions and the console still work. emisar pack suggest --catalog takes your registry's v1/catalog.json URL: it matches this host against your packs and prints each one's immutable install line, hash and all. The console lists your private packs like any others, because a runner advertises every installed pack.
  • Install by URL, not by name. Name-based commands — pack install redis, pack update, pack diff — read the public registry's layout, which this tree does not have. Install from the immutable tarball URLs in step 4.
  • To retire a version, revoke its trust. Revoke it in Packs, and the control plane refuses dispatch for that version.

Last reviewed August 18, 2026