Distribute packages via an OCI registry
Hull packages push to and pull from any OCI distribution-spec registry — GHCR,
Docker Hub, Quay, Harbor, Artifactory, ECR, GAR, ACR, self-hosted Zot or
Distribution. Each package version becomes an OCI artifact: hull stores the
*.hull.tgz archive as the artifact blob under a thin manifest.
Choose OCI when you already run a container registry, want cloud IAM integration, or want immutable, content-addressable artifacts. For static HTTP hosting instead, see Repositories.
Authenticate
Registry credentials are stored by hull login, keyed by host, in
~/.config/hull/credentials.json. (There is no hull registry login — the
hull registry command has only push and pull.)
echo "$GITHUB_TOKEN" | hull login ghcr.io -u myuser --password-stdin
hull login registry.example.com -u u -p p
Login succeeded for ghcr.io
hull login is non-interactive and needs one credential: -u/--username with
-p/--password (or --password-stdin), or --token, or --api-key.
Subsequent push and pull authenticate automatically. Remove a credential with
hull logout ghcr.io.
Push a package
Build the archive, then push it. Two commands can push:
hull package ./my-app -d ./build
# Dedicated OCI push:
hull registry push ./build/my-app-1.2.3.hull.tgz oci://ghcr.io/example/charts/my-app:1.2.3
Pushed ./build/my-app-1.2.3.hull.tgz to oci://ghcr.io/example/charts/my-app:1.2.3
# Or the multi-target publish command:
hull publish ./build/my-app-1.2.3.hull.tgz --oci oci://ghcr.io/example/charts/my-app
Published my-app@1.2.3 to OCI registry oci://ghcr.io/example/charts/my-app
An untagged reference is tagged with the package’s own version from hull.yaml.
Pull a package
There are two pull paths. hull registry pull is OCI-only, takes a full
reference including the tag, and can verify a cosign signature first. It
writes the archive to -d/--destination; it does not resolve version
constraints or untar.
hull registry pull oci://ghcr.io/example/charts/my-app:1.2.3 -d ./pulled
Pulled oci://ghcr.io/example/charts/my-app:1.2.3 to ./pulled/my-app-1.2.3.hull.tgz
hull pull accepts an oci:// reference too, resolves a SemVer --version
against the registry’s tags, and can unpack with --untar. Its destination
flag is --destination (no -d shorthand):
hull pull oci://ghcr.io/example/charts/my-app --version "^1.2.0" \
--destination ./pulled --untar
Pulled and extracted: ./pulled/my-app
Install from OCI
hull install takes a local package directory — it does not fetch remote
references. Pull and unpack first, then install from the directory:
hull pull oci://ghcr.io/example/charts/my-app --version 1.2.3 \
--destination ./pulled --untar
hull install my-app ./pulled/my-app -n prod --create-namespace
release my-app installed (revision 1)
Verify a cosign signature on pull
hull registry pull verifies a cosign signature on the artifact before it
touches disk — fail-closed, so an unsigned or wrongly-signed artifact is never
pulled. Supply a public key, or a keyless identity + issuer:
# Key-based:
hull registry pull oci://ghcr.io/example/charts/my-app:1.2.3 \
--cosign-key cosign.pub -d ./pulled
# Keyless (Sigstore):
hull registry pull oci://ghcr.io/example/charts/my-app:1.2.3 \
--cosign-identity 'https://github.com/example/ci/.github/workflows/release.yml@refs/heads/main' \
--cosign-issuer 'https://token.actions.githubusercontent.com' \
-d ./pulled
cosign signature verified for oci://ghcr.io/example/charts/my-app:1.2.3
Pulled oci://ghcr.io/example/charts/my-app:1.2.3 to ./pulled/my-app-1.2.3.hull.tgz
Cosign signing itself is external — sign with the cosign CLI after pushing:
hull registry push ./build/my-app-1.2.3.hull.tgz oci://ghcr.io/example/charts/my-app:1.2.3
cosign sign --key cosign.key ghcr.io/example/charts/my-app:1.2.3
For PGP .prov provenance instead of cosign, see Signing.
Consume a layer from OCI
A package can source a composition layer from an OCI registry. In hull.yaml:
layers:
- name: my-layer
source: oci://ghcr.io/example/charts/my-layer
version: ^2.0.0
hull dependency update ./my-app resolves the constraint against the registry
and locks the result in hull.lock:
# hull.lock
layers:
- name: my-layer
source: oci://ghcr.io/example/charts/my-layer
resolvedVersion: 2.4.1
digest: sha256:7e2a90d8c5...
Later hull dependency build ./my-app fetches the locked version. See
hull dependency.
Use an insecure or plaintext registry
For a local registry without TLS, add --plain-http to hull registry push
and hull registry pull; for a self-signed certificate, use
--insecure-skip-tls-verify:
hull registry push ./build/my-app-1.2.3.hull.tgz \
oci://localhost:5000/charts/my-app:1.2.3 --plain-http
hull registry pull oci://localhost:5000/charts/my-app:1.2.3 --plain-http
The same behaviour can be set globally — as flags on any command
(--oci-plain-http, --oci-insecure-skip-tls-verify) or as environment
variables, which is convenient in CI:
| Env var | Effect |
|---|---|
HULL_OCI_PLAIN_HTTP=1 |
use HTTP instead of HTTPS for OCI |
HULL_OCI_INSECURE_SKIP_TLS=1 |
skip TLS verification for OCI |
Sending basic-auth credentials over plaintext HTTP additionally requires
--allow-plaintext-auth (or HULL_ALLOW_PLAINTEXT_AUTH=1).
Troubleshooting
unauthorized: authentication required— log in:hull login <host>.x509: certificate signed by unknown authority— the registry uses an internal CA; for a quick local test use--insecure-skip-tls-verify(never in production).no matching version for constraint— the constraint matched no tag; list the registry’s tags to see what is published.
See also
hull registry push·hull registry pullhull pull·hull publishhull login— store registry credentials- Repositories — static HTTP distribution
- Signing — PGP and cosign verification