Enterprise container publication
The OCC CLI release publishes matching versioned command line binaries through a separate protected workflow.
container-publish.yml prepares the existing
controller (Dockerfile, target runtime) and combined gateway/Agent runtime
(deploy/runtime/Dockerfile) as OCI archives containing both linux/amd64 and
linux/arm64. Each image has one multi-platform index digest; Docker selects
the matching architecture when pulling it. Chart publication is opt-in through
publish_chart: true; it packages the OCC Helm chart under the same OCE release
version. It does not deploy Kubernetes
resources or change the existing CI test matrix.
Source visibility
No-push preparation supports private or public source in
openclaw/openclaw-enterprise only when this workflow receives publish: false
(PUBLISH is the exact string "false"). The trusted main workflow, immutable
source SHA, successful exact-source CI, and approved base-image checks still apply.
Actual GHCR publication and recovery require the public
openclaw/openclaw-enterprise repository and public GHCR image packages. Optional
chart publication requires the public chart package too. Docker Hub promotion
copies from public GHCR and keeps the separately authorized Docker Hub
destinations private.
GitHub warns that granting a public repository Actions access to private packages can expose those packages to forks. Do not use private GHCR packages for real publication from this public repository. Bootstrap can create or verify private marker packages only so an operator can finish package setup; make the packages public before source images or the chart publish.
Operator setup
Configure publication settings and native runners before dispatching. That page owns environment policies, variables, secrets, and package requirements. Then confirm package linkage and use the marker bootstrap for missing packages.
No-push preparation validates the approved base image and trusted source CI. Publication also checks package access, identity, and public visibility. Missing settings, inaccessible metadata, or conflicting linkage stop publication. GitHub may omit repository metadata; follow the linkage checks below.
Confirm package linkage
GitHub's package response
may omit repository or return null even for a connected GHCR package. The
workflow always verifies package identity and public visibility. An explicit
repository must match the public Enterprise repository; conflicting metadata
stops publication.
During package setup, open Package settings for each destination and confirm
its connected repository is openclaw/openclaw-enterprise, its visibility is
Public, and that repository has the required Manage Actions access
grant. Source labels alone do not prove linkage. When GitHub omits repository
metadata, the publisher relies on this configured package access and still
checks package identity, public visibility, and immutable tag contents. It does
not claim to verify omitted linkage through the API.
To migrate an existing environment, first merge this manual-publication change and wait for its main-push CI. Then apply the current publication configuration. Old runs execute their original workflow code; use a new recovery dispatch to publish retained artifacts under the updated policy.
Bootstrap GHCR packages
After configuring the environment and both destination variables, merge the
reviewed bootstrap workflow and wait for
its exact main-push CI run to succeed. An operator must first confirm that the
organization permits creation of container packages under those names.
Dispatch Bootstrap Enterprise Container Packages on main with that
ci_run_id. The manual dispatch authorizes the run.
The workflow uses its short-lived GITHUB_TOKEN to push scratch image markers
and a nondeployable Helm chart marker. Its temporary image contexts and chart
contain no checkout files or credentials. Existing public or private packages
remain unchanged when they match the expected package and repository metadata;
conflicting repository metadata fails. An authenticated metadata 404 permits only
this harmless push; it does not prove the package is absent. Other metadata
errors stop the run. After each push, the metadata lookup retries only 404
responses up to five times at two-second intervals for registry propagation;
persistent 404 responses fail. The remote marker digest must verify before
bootstrap succeeds. Confirm package linkage during setup before real-image
publication.
The job summary records package coordinates and marker digests. The unique
bootstrap-<run-id>-<attempt> image tags and chart version are not deployable. Bootstrap
does not change visibility or access grants; if verification fails, inspect the
package settings and fix the reported cause before retrying. A partial result is retained,
and a subsequent dispatch leaves valid existing packages untouched. The ordinary
publisher still requires verified public packages before copying any source.
See the bootstrap execution flow.
Public GHCR image pulls do not require a workstation login. Authenticated pulls are still required for private marker packages before they are made public and for separately authorized private mirrors. Workflow package permissions do not grant a workstation credential additional scopes.
Check native images without publication
Use Check Native Container Images
to build and smoke-test controller and runtime images on native AMD64 and ARM64
runners. Configure the native runners and base image.
Select a ref containing this workflow version and its manual source_sha input.
Replace both placeholders. Set expected_sha to the full 40-character commit
SHA recorded independently during source review:
selected_ref='REPLACE_WITH_BRANCH_OR_TAG'expected_sha='REPLACE_WITH_REVIEWED_FULL_COMMIT_SHA'gh workflow run container-check.yml --repo openclaw/openclaw-enterprise \ --ref "$selected_ref" -f source_sha="$expected_sha"The workflow checks out github.sha and compares it with source_sha. A
mismatch, including a branch moved between review and dispatch, fails before
image builds. An omitted or empty source_sha defaults to github.sha, with
no independent check. Reusable callers must supply it.
The workflow does not push images to a registry or install a release. Repository readers can download its artifacts; review the image contents and audience, especially for public source.
Retain native export evidence
Before using exports, require a successful terminal workflow with all four
Prepare <controller|runtime> (<amd64|arm64>) jobs and both
Smoke runtime (<amd64|arm64>) jobs successful. Controller preparation includes
smoke and qualified upload. Runtime preparation supplies build digests; its
separate smoke jobs verify the handoff, run startup tests, clean up and upload
qualified artifacts. All four image/architecture outcomes are required.
After smoke failure, rerun the whole native workflow. A smoke-only retry cannot consume the previous attempt's build artifact.
Download the selected artifacts before their one-day retention expires.
Their names are platform-<controller|runtime>-<amd64|arm64>-<run-id>-<attempt>.
The intermediate built-runtime-* artifacts are unqualified.
Keep the original download and artifact metadata, including its ID and SHA-256
digest, together with the run, attempt, source SHA and successful job records.
Compare the downloaded ZIP's checksum with the artifact metadata; this checksum
identifies the ZIP bytes, not an OCI image digest.
Extract the verified ZIP into a private directory and leave it unchanged during
verification. Select the expected values from the reviewed source and producer
job records. Use the Digest output from Build native OCI image without
publication (steps.build.outputs.digest) and the pinned Node base used by that job.
For a direct Check Native Container Images dispatch, leave both CI fields empty.
For example, check its runtime export from the repository root:
SOURCE_SHA="$expected_sha" GITHUB_WORKFLOW_SHA="$workflow_sha" \GITHUB_RUN_ID="$run_id" GITHUB_RUN_ATTEMPT="$attempt" \CI_RUN_ID='' CI_ATTEMPT='' NODE_BASE_IMAGE="$pinned_node_base" \IMAGE=runtime PLATFORM=linux/arm64 IMAGE_DIGEST="$build_digest" \node scripts/ci/container-release.mjs verify-platform "$extracted_layout"For a manual CI run that requests native images, use that CI run’s own ID
and attempt. For Enterprise Containers, use the validated source CI run ID
and attempt. The command checks platform.json and the reachable OCI
bytes offline and prints the platform, build root, manifest and config digests.
Preserve these identities through transport. ZIP verification and the six-job
success check above are separate requirements. Registry and deployment checks
use their own evidence. For local use, follow the
populated controller return rehearsal.
Prepare and publish
- Merge the reviewed workflow and source changes to
main. Wait for that exact main push run of.github/workflows/ci.ymlto finish successfully, including itsCI Requiredjob. A PR merge SHA or a matching check name is insufficient. - Manually dispatch Enterprise Containers, selecting branch
main, its full currentsource_sha, and thatci_run_id. Leavepublishfalse for no-push preparation. If main moved, select the new SHA and its own completed CI run. - To publish, dispatch with
publishtrue after reviewing the SHA, CI run, and package setup. Leaveimage_tagblank to updatelatestfor both images, or enter a custom tag to update that tag instead. The tag can contain up to 128 ASCII letters, digits, underscores, periods, and hyphens; it must start with a letter, digit, or underscore. Tags beginning withsha-orbootstrap-are reserved, case-insensitively. The selected alias is mutable and an existing tag can be replaced. Each platform builds natively; runtime smoke loads that export on a fresh native runner, while controller smoke remains with its build. Preparation verifies the loaded config against the export using Docker's image ID on the legacy store or the verified config digest on the containerd store, where the image ID identifies a manifest or index. After both platforms pass startup smoke, the workflow assembles and seals those exports from the same run and attempt into one archive per image. The publisher rechecks the source, CI attempt, environment branch policy, and package visibility, then copies each sealed archive with all child manifests using Skopeo and verifies the remote index digest. - Use the
image@sha256:...references in the image job summary andcontainer-publication-<run-id>-<attempt>receipt for deployment. Each image receives an immutablesha-<source-sha>tag, the selected mutable alias, and, only withpublish_chart: true, an OCE version tag matching the chart. The image-onlycontainer-publicationreceipt records the source tags and alias for promotion and recovery. Existing source and version tags cannot be replaced by different bytes. Publication does not create a Git tag, GitHub release, or deployment.
Image publication defaults to publish_chart: false and needs no chart package.
For a versioned chart release, also select publish_chart: true and follow
chart publication and installation. With publish: false,
preparation never publishes images or a chart regardless of publish_chart.
Images and the optional chart have separate jobs. A chart failure leaves the
verified images and their successful job intact, but fails the combined run.
The workflow holds the shared publication lock from preparation through both
publication jobs; preparation-only runs use independent groups. A queued run can
be superseded before it starts, but an active release retains the lock.
The final summary reports both outcomes. Chart publication produces its own
chart-publication-<run-id>-<attempt> receipt.
The multi-platform publisher requires both architectures in every seal. Earlier amd64-only tags retain their original bytes and digests; building this workflow requires a new source SHA. Old single-platform archives are not accepted by the current recovery or promotion validator. See the publication execution flow.
OCI archives are retained for seven days and publication receipts for 30 days.
Uploaded archives follow the repository's
workflow-artifact access:
repository readers can download them. Public-source preparation can therefore
expose the built images to public repository readers even with publish: false.
No registry push does not mean artifact confidentiality; review the image
contents and artifact audience before dispatch.
Preparation pins image timestamps to the source commit and removes exporter-only annotations from the assembled multi-platform index. Repeating a build with the same resolved inputs therefore produces the same registry image identity. A rerun can still be rejected if mutable external package inputs resolve to different bytes. Do not delete or overwrite existing tags to evade that rejection. Publication of the two images is not transactional; on a partial failure inspect each recorded registry digest before deciding on recovery. The publisher's concurrency lock serializes these workflow writes, not external registry administrators. Both source tags are verified before either alias moves, but the two alias updates are not atomic. A failure may leave them pointing to different publications; inspect both packages and use digest references until a later successful publication updates the selected alias for both images. Recovery restores source tags only and does not move aliases.
Recover a partial publication
Preserve the original seven-day OCI artifacts. Do not rerun their producer: its attempt identity must remain unchanged. If those archives have expired, this recovery path cannot reconstruct their exact bytes.
After merging reviewed recovery code and waiting for its successful main-push CI,
dispatch Resume Enterprise Container Publication
on main with:
source_sha: the original image source SHA.preparation_run_idandpreparation_attempt: the original Enterprise Containers run. Validation, all four native preparation jobs, both runtime smoke jobs, and both image assembly jobs must succeed. For older runs that assembled each image in its preparation job, validation and both image jobs must succeed.workflow_ci_run_id: successful main-push CI for the selected recovery workflow commit. This is separate from the original image's CI evidence in its seal.
Native preparations that ran smoke inside Prepare jobs are unsupported by the
current recovery gate. They require fresh full qualification, which does not
reconstruct their original bytes.
Review the original digests and current public package settings before dispatch.
Recovery validates artifact IDs and downloads the exact
artifact IDs from the original run, verifies seals and archive hashes, and
rechecks original source CI as well as recovery workflow CI. The original source
commit must remain in main history. It must match the recovery workflow commit
or be one of its ancestors. The approved Node base must still match the original
seals.
An existing source tag must match both package metadata and its remote manifest digest; recovery leaves it untouched even when package metadata lags. Only an authenticated manifest-unknown response permits copying an unlisted tag; other inspection failures stop the run. Missing tags receive the original OCI bytes, with digest preservation and remote verification. Conflicting tags, expired or changed producer evidence, or failed checks stop the run. Neither recovery nor ordinary publication automatically rolls back a copy. GitHub metadata GET transport failures are retried up to a set limit (normally three attempts) and report the failing endpoint; authorization failures and other HTTP errors still fail immediately, except the bootstrap's explicit 404 retry.
Success produces container-recovery-publication-<run-id>-<attempt> with a
publication.json receipt containing original image seals, preparation artifact
IDs/digests, and separate recovery publication run/attempt/workflow/CI identity.
Both remote digests must match before the receipt is written. Its 30-day retention
matches ordinary receipts. This recovery receipt is not accepted by the existing
Docker Hub promotion workflow, which requires an ordinary successful producer.
No build, visibility change, tag replacement, or deployment occurs during recovery.
For a separately authorized copy of ordinary publication digests to private Docker Hub repositories, use Docker Hub promotion.
Proof boundaries
Four existing CI lanes gate the selected source: Images and Packaging, and the three Image Runtime Startup lanes. Preparation reuses their controller/runtime startup tests against the newly prepared bytes: source CI alone cannot prove a subsequent build with newly resolved npm transitives. Loading does not rebuild an image. The assembler checks each platform export against its smoke receipt before including it in the final archive. Skopeo preserves manifest digests during publication. These are not provider, cluster, or registry end-to-end tests.
Local gate and recovery tests: node --test tests/integration/container-{release,resume,promote}.test.mjs.
Recovery tests use HTTP and transport fixtures; they prove gate ordering, original
identity, conflict rejection, unchanged existing images, and receipt behavior.
The registry integration
uses real Skopeo and a disposable registry to check alias replacement and both
platforms. Neither test proves a live GHCR transfer.
Workflow syntax: actionlint .github/workflows/*.yml.
Actual no-push builds and the first public GHCR transfer still require
their respective authorized hosted runs; configuration and unit tests alone
do not prove them.
