- docs
- Publishing
- Publish for several platforms
Publish for several platforms
By the end of this page, one tag such as acme/mytool:1.0.0 resolves to the right build on every platform you publish. That covers linux, darwin and windows on amd64 and arm64, plus WebAssembly. You need one built bundle per platform and write access to a registry.
One tag, many manifests
Section titled “One tag, many manifests”The naive approach is one tag per platform, such as acme/mytool:1.0.0-linux-amd64. That pushes platform resolution onto every consumer’s install script. OCX uses OCI Image Indexes instead: one tag, several manifests, and OCX picks the right one at install time.
An image index is a manifest of manifests, with one descriptor per platform. When a consumer runs ocx package install acme/mytool:1.0.0, OCX fetches the index and finds the manifest for the consumer’s platform. It pulls only that manifest’s layers.
You do not assemble the index yourself. Push each platform separately under the same tag. When ocx package push sees a tag that already has a manifest, it merges the new platform into the existing index rather than replacing it.
Push one platform at a time
Section titled “Push one platform at a time”The ocx_mirror tool runs the same pattern from a YAML spec. By hand, the flow has three steps.
- Bundle each platform with
ocx package create. Pass-i acme/mytool:1.0.0 -p <platform>and let-o .infer the output name. Each call drops a<name>-<tag>-<os>-<arch>.tar.xzarchive, where<name>is the last segment of the OCI repository. It also drops a sibling<...>-metadata.jsonsidecar, whichocx package pushpicks up automatically. Bundle anatomy explains the sidecar and the inferred name. - Push the first platform with
--cascade. The flag keeps your rolling aliases (1.0,1,latest) in sync. Omit-m, because push reads the sidecar next to the layer. - Push every other platform with
--cascadetoo. OCX detects the existing manifest and merges the platform into the image index. It re-points the tag at the index digest and updates each rolling alias.
ocx package create build -i acme/mytool:1.0.0 -p linux/amd64 -m metadata.json -o .ocx package create build -i acme/mytool:1.0.0 -p linux/arm64 -m metadata.json -o .
ocx package push --cascade -p linux/amd64 -i acme/mytool:1.0.0 mytool-1.0.0-linux-amd64.tar.xzocx package push --cascade -p linux/arm64 -i acme/mytool:1.0.0 mytool-1.0.0-linux-arm64.tar.xzAfter both pushes, acme/mytool:1.0.0 resolves to an image index with two platform descriptors. Any rolling alias points at the same index digest. A consumer on Linux/arm64 fetches only the arm64 manifest’s layers, and an amd64 runner fetches only the amd64 ones. Add -p darwin/arm64, -p darwin/amd64 or -p windows/amd64 the same way for the rest of the matrix.
The recording also runs ocx index update and ocx package install after the second push. It shows the consumer side: one tag, the right platform’s layers fetched, and the binary on the candidate symlink ready for ocx package exec.
If your Linux builds differ by C library, publish glibc and musl variants under the same tag. The selection rules and the libc.glibc and libc.musl tags live on that page.
Share one metadata file
Section titled “Share one metadata file”The recommended pattern is one metadata.json that covers every platform. Env entries, dependencies and entrypoints apply uniformly, and the platform-specific bits live in the archive.
When platforms genuinely diverge, a different metadata file per platform is possible. Windows might need different env keys, or a platform might use different entry-point names. Hand-driven publishers pass --metadata <path> per push. The ocx_mirror spec accepts a per-platform override, where the metadata block names the default plus the overrides:
metadata: default: metadata.json platforms: darwin/amd64: metadata-darwin.json darwin/arm64: metadata-darwin.json windows/amd64: metadata-windows.json windows/arm64: metadata-windows.jsonRely on a stable index
Section titled “Rely on a stable index”Each per-platform manifest’s digest depends only on its own bytes. Pushing the same archive bytes twice gives the same manifest digest, so there is no platform-side rebuild churn. Re-running ocx package create produces a different archive each time, and stable archives explains why.
The index digest is a function of its descriptors. It changes only when you add a platform or push a new build for an existing one. That stability lets --cascade work across multi-platform releases. The rolling tag points at the index, the index points at per-platform manifests, and every layer caches independently.
Publish WebAssembly
Section titled “Publish WebAssembly”A .wasm file is not a native executable. It needs a runtime, and the same bytes run unchanged on every host that has one. That makes it a poor fit for any, which claims no platform requirement at all. A WASI 0.1 module and a WASI 0.2 component target different ABIs, so a consumer must say which one it wants.
OCX gives each WASI ABI its own platform, so the choice is expressed like any other platform choice:
| Platform | Artifact |
|---|---|
wasip1/wasm | A module targeting the WASI 0.1 (Preview 1) ABI |
wasip2/wasm | A component targeting the WASI 0.2 (Preview 2) ABI |
Publishing is the ordinary per-platform push. Nothing about the flow is wasm-specific:
ocx package create -i my/tool:1.0.0 -p wasip1/wasm -m metadata.json -o tool.tar.xz ./distocx package push -p wasip1/wasm -i my/tool:1.0.0 tool.tar.xzConsuming one always names the platform explicitly. No machine reports wasip1 or wasip2 as its host. An omitted --platform therefore resolves to the native host platform and will not find a wasm manifest:
ocx package install -p wasip1/wasm my/tool:1.0.0Depend on platform-specific builds
Section titled “Depend on platform-specific builds”A package built once with --platform any can still depend on a package that ships different manifests per platform. A shell script or Python entry point that wraps a native binary is the usual case. A dependency identifier carries only one digest, so which platform’s build does that digest name?
ocx package create resolves the dependency against the single --platform you declared for the bundle. It uses the directed compatibility relation that every platform decision in OCX routes through. A concrete --platform pins each dependency to the one compatible manifest digest. The any case needs two extra rules.
The any-deps rule
Section titled “The any-deps rule”An any-targeted package performs no platform-specific resolution, so it can only depend on packages that themselves offer an any manifest. This follows from the compatibility relation: an any requirement is satisfied only by an any offer.
{ "dependencies": [{ "identifier": "ocx.sh/acme/mytool:1.0" }] }ocx package create --platform any resolves this to a single manifest digest, bare on the identifier:
{ "dependencies": [ { "identifier": "ocx.sh/acme/mytool:1.0@sha256:aaaa..." } ]}Suppose ocx.sh/acme/mytool:1.0 ships only platform-specific manifests (linux/amd64, darwin/arm64 and so on) and no any build. Then create fails with exit 65, naming the dependency and listing what it does offer. There is no partial coverage to derive. A platform-agnostic package either leans on a platform-agnostic dependency, or it cannot depend on that package under --platform any.
Digest pins under any
Section titled “Digest pins under any”A leaf manifest carries no platform descriptor of its own. The platform lives in the image index entry one level up. A dependency pinned by a bare @digest therefore cannot be verified as any-offered from the digest alone.
ocx package create --platform any refuses that verification job. It resolves against an index, so a digest it did not resolve itself carries no evidence either way. It rejects a direct digest pin anywhere in an any-targeted bundle’s dependency list (exit 65), including one present before create ran. Pin the dependency through create, as in the any-deps rule, instead of writing a digest by hand.
ocx package push has the registry in hand, so it checks the stronger claim directly. It re-fetches the dependency’s image index by its advisory tag. It accepts the pin only if that index advertises the pinned digest as any. That means a flat manifest whose digest matches, or an index entry declaring platform any. Otherwise the push fails (exit 65), naming the dependency and the digest.
Concrete-targeted bundles are unaffected by either check. A direct digest pin is fine there, because the platform being published is known and an any claim was never in question.
Snapshot semantics
Section titled “Snapshot semantics”A pin is a snapshot of the dependency’s platform coverage when create ran, or push for the provenance check. It is not a live query. Suppose the publisher of ocx.sh/acme/mytool:1.0 later adds an any manifest. Your published package does not pick it up. Re-run ocx package create --platform any against a refreshed index, then ocx package push, to re-resolve.
See also
Section titled “See also”- Publish glibc and musl variants
ocx aboutreferenceocx package pushreference- Storage in depth: multi-layer packages
- Versioning in depth: platforms
- Build and push: cascade, layer reuse, BYO archives, dependency pin resolution
- Declaring dependencies: when to depend, visibility,
nameoverrides - Platforms reference: the grammar and the compatibility relation this page builds on
- Dependencies reference: sidecar field shapes, manifest-pin rule
ocx package createbuild receipt: the fallbackpushandtestread for a platform their own flags did not state- Repackage releases:
ocx_mirrorper-platform spec