- docs
- Publishing
- Publish packages
Publish packages
Build a deterministic archive, attach metadata that declares its environment, and push both to any OCI registry.
The publisher journey
Section titled “The publisher journey”This section has a page for each step and for each decision around it. Follow the journey to the pages you need, or start with the first package at the end of this page. Most packages start as an upstream binary release on GitHub or a vendor’s download page. The pages below take you from “I have a binary” to “consumers can install it”, grouped by task.
Prepare the package
Section titled “Prepare the package”- Bundle a release: what goes in the archive, whether to repack the upstream layout and which compression to pick.
- Declare dependencies: when to depend on another package instead of bundling it, how to pin by digest and which edge visibility to use.
- Declare the environment: which variables to declare and how to mark each as
private,publicorinterface. - Add named launchers: when to ship launchers instead of exposing
bin/, and how to pick names that do not collide.
Check and push
Section titled “Check and push”- Test a package before you push it: run the install pipeline in a temporary directory, with no registry round trip and no new digest.
- Write script tests for a package: assert on a package’s behavior with a Starlark script.
- Publish a package to a registry: the first push,
--cascadefor rolling tags and layer reuse by digest. - Publish for several platforms: push per platform under one tag and let OCX assemble the OCI Image Index.
- Publish glibc and musl variants: publish Linux builds that differ by C library.
Make it resolvable
Section titled “Make it resolvable”- Announce a package: claim the namespace once, then publish tags into the index on every release.
- Announce from CI: the credential postures for a GitHub or GitLab job, and who reviews what.
Secure and maintain releases
Section titled “Secure and maintain releases”- Sign a release: keyless Sigstore signing from a laptop or CI, or a key pair instead.
- Attestations: attach an SBOM or another attestation to a package.
- Promote packages between environments: move a tested build from one registry to the next without rebuilding.
- Snapshot tracks: publish nightly or per-change builds and delete them again.
- Maintain patches: ship fixes on top of a published package.
Wrap tools you do not own
Section titled “Wrap tools you do not own”- Repackage an upstream release: turn a GitHub Release, Homebrew bottle or vendor archive into an OCX package, by hand or with
ocx_mirror.
Decision flowchart
Section titled “Decision flowchart”Common questions you will hit while publishing, and the page that answers each:
- Should I repack the upstream archive or use
strip_components? Bundle a release: strip components - Which env vars should consumers see, and which only my launchers? Declare the environment: choosing visibility
- When do I need named entry points instead of
PATH += bin/? Add named launchers: when to declare - When do I need a
nameoverride on a dependency? Declare dependencies: name override - How do I verify a package works before pushing it to the registry? Test a package before you push it
- How do I make rolling tags (
1.0,1,latest) follow new releases? Publish a package to a registry: cascade - How do I publish for amd64, arm64 and darwin under one tag? Publish for several platforms
- I pushed the package but
ocx package install acme/widgetcannot find it. Announce a package: claiming the namespace - Which credential does my CI job need to write the index? Announce from CI: choosing a posture
- How do I avoid re-uploading a base layer that is already in the registry? Publish a package to a registry: layer reuse
- How do I add a README or logo to a published package? Repackage an upstream release: describe
Publish your first package
Section titled “Publish your first package”You have a compiled binary, a Python script or any other executable, and you want consumers to run it after ocx package install. The path takes three steps, and Publish a package to a registry covers each one in detail.
First, lay out the working directory. The bundle contents live under build/, and the metadata sidecar sits next to it. The sidecar is not part of the archive. ocx package push uploads it as the OCI manifest config blob, and OCX restores it beside content/ at install time.
mytool/workdir (cwd for the commands below)build/bundle contents, which becomecontent/after installbin/mytoolyour binary, Python script or shell wrapper
metadata.jsonOCI config sidecar, not part of the archive
Second, write metadata.json with one public env entry that prepends the package’s bin/ to PATH:
{ "$schema": "https://ocx.sh/schemas/metadata/v1.json", "type": "bundle", "version": 1, "env": [ { "key": "PATH", "type": "path", "value": "${installPath}/bin", "visibility": "public" } ]}Third, bundle build/ into a deterministic archive and push it to a registry you can authenticate against. The push attaches metadata.json as the manifest config blob.
ocx login ghcr.ioocx package create build -p linux/amd64 -m metadata.json -o mytool-1.0.0.tar.xzocx package push -p linux/amd64 -m metadata.json \ -i ghcr.io/me/mytool:1.0.0 mytool-1.0.0.tar.xzConsumers reach the package with ocx package install ghcr.io/me/mytool:1.0.0, and the binary lands on their PATH once they pass --select or run ocx package select later, which makes the package current.
See also
Section titled “See also”- Metadata reference: every field and every constraint
- Command-line reference: package commands:
create,push,pull,test,description pushanddescription pull - Storage in depth: the three-tier CAS that makes layer reuse work
- Environments in depth: composition order, the edge filter and conflicting constants
- User guide: the consumer side of every decision documented here