Guide / deploy

Deploy a release, step by step

Use this walkthrough when a Git repository owns an artifact or kit and you want a repeatable, auditable release from hubbound.json.

Before you start

You need a HubBound CLI with deploy support, a project directory containing hubbound.json, a Git worktree with an origin remote, and credentials from hubbound auth login for a real release.

Deploy reads exactly --path/hubbound.json. It does not walk up to a parent manifest, so choose --path deliberately when you run it from a subdirectory.

  • Use a v2 project manifest. hubbound init creates the starting shape.
  • Use exact SemVer versions such as 1.2.0 for deployments and dependency pins; do not use latest in a v2 project dependency map.
  • Make sure origin is HTTPS without embedded credentials and HEAD is a 40- or 64-character hexadecimal commit SHA.
  • Keep the release bundle to at most 250 files, 2 MiB per file and 10 MiB logical total size.

1. Define the deployment

A deployment is a named release recipe. An artifact has kind, path, tag, version, visibility, artifact_type, content and files. A kit has kind, path, tag, version, visibility and members; it must not define artifact_type, content or files.

The path is relative to the manifest root and identifies the package root for files. The deployment name is the local selector you pass to hubbound deploy; it is not the public tag.

{
  "schema_version": 2,
  "name": "security-tools",
  "dependencies": {
    "artifacts": { "vendor/shared-rule": "1.4.0" }
  },
  "deployments": {
    "auditor": {
      "kind": "artifact",
      "path": "packages/auditor",
      "tag": "vendor/security-auditor",
      "version": "1.2.0",
      "visibility": "public",
      "artifact_type": "SKILL",
      "description": "Security review skill",
      "content": { "entrypoint": "SKILL.md" },
      "files": ["SKILL.md", "references/**", "scripts/**"]
    }
  }
}

2. Run the local preflight

Start with dry-run. It validates the manifest, resolves the selected deployments, checks Git provenance and expands file patterns without creating a remote release, uploading bytes or committing anything.

Omit deployment names to validate every entry. Pass one or more names to narrow the run. When a selected kit references local artifact deployments, those artifacts are included automatically and prepared before the kit.

$ hubbound auth login

$ hubbound deploy --path . --dry-run --json

$ hubbound deploy auditor --path . --dry-run

$ hubbound deploy auditor --path . --dry-run --json > deploy-preflight.json

--path <dir> Exact directory containing hubbound.json. Default: .; no ancestor discovery.
[deployment...] Optional local names. No names means all deployments; names must exist in deployments.
--dry-run Local-only manifest, Git and file validation. No backend mutation.
--json Machine-readable result with state, counts, commit SHA and release identifiers when available.
--allow-dirty Allows tracked, untracked or deleted files in the worktree and marks source.dirty=true.

3. Make the file snapshot pass

files uses relative patterns and supports **. Every artifact needs at least one pattern, including MCP artifacts whose runtime configuration lives in content. A pattern that matches nothing is an error.

The snapshot keeps regular, non-symlink UTF-8 text files. It rejects unsafe or sensitive material before the first remote mutation, so fix the package rather than trying to bypass the gate.

  • Maximum 250 files, 2 MiB per file and 10 MiB logical total size.
  • Excluded automatically: .git, hubbound.json, .env files, backup names ending in ~ or .bak, and .pem/.key/.crt private material.
  • Rejected formats include images, archives, PDFs, executables, fonts and other binary signatures.
  • Entrypoints must be relative to the artifact package and cannot be absolute or escape with ..; use the same path in content and files.

4. Publish the release

When dry-run is clean, run the same selection without --dry-run. The client prepares the release with the authenticated control plane, receives the missing content-addressed digests and presigned upload URLs, uploads only those bytes, and then commits the release.

The presigned PUT is a different trust boundary: use the exact headers returned by the backend and do not add Authorization or DPoP headers. The client sends an idempotency key so a retry can safely identify the same release attempt.

$ hubbound deploy auditor --path . --json

$ hubbound deploy security-kit --path . --allow-dirty --json

artifact Prepare metadata, upload missing CAS blobs, then commit the artifact version.
kit Prepare the exact composition. Local artifact deployments are prepared first; the kit itself does not upload duplicate blobs.
--wait-timeout <duration> Maximum wait for a configured public-registry watcher; default 5m.
--yes Accepted for compatibility; the current flow has no interactive confirmation prompt.

5. Read the result correctly

Save the JSON output in CI or an audit log. A successful commit means the authenticated release control plane accepted the immutable version. It does not automatically prove that an anonymous public install can see it.

Treat release_state and publication_state as separate evidence. The current CLI wiring may report publication status as unavailable when no public watcher is configured; that is different from a failed commit.

  • committed: the backend accepted the release commit.
  • active: the public registry projection is available for anonymous discovery/install.
  • unavailable: this client has no watcher evidence for the public projection; verify through the release/registry control plane separately.
  • For a retry, compare the deployment name, tag, version, commit SHA and idempotency key before assuming another release was created.

Common failures and fixes

Most errors are intentionally early. The message tells you which contract failed; correct the manifest or package and rerun dry-run before attempting a real deploy.

hubbound.json not found Pass --path to the directory that contains the manifest; deploy does not search parents.
deployment not found Use the key under deployments, not the public tag, or omit names to process all.
dirty worktree Commit the intended files, or explicitly use --allow-dirty when source.dirty=true is acceptable.
pattern matched no files Check the deployment path and pattern relative to that package root. Remember files must contain at least one match.
unsafe/binary/too large Remove the file from files, convert it to safe UTF-8 text, or split the package within the documented limits.
public status unavailable The commit may still be valid; publication monitoring is separate and may not be wired in this checkout.