Skip to content

Publishing Releases

Changing configuration and making it live are separate steps in ConfigHub. Editing a Unit reaches no cluster; publishing a Release is what makes a change live. A Release bundles the Units in a Space that are not Unreleasable, captured at a point in time and published for the Space's release Target, and serves them as an immutable, read-only OCI artifact for your GitOps operator to pull. See integrating with GitOps operators for how the bundle reaches the cluster.

Releases are published per Space, so you generally release a whole variant at once.

Commands

The cub release subcommands publish, list, get, withdraw, and delete Releases. For the complete flag reference, see the cub release CLI docs.

Publish

cub release publish <space-slug>

Creates a Release of <space-slug>, bundling the Units of the Space that are not Unreleasable, for the Space's release Target, and making it available via the OCI server. By default each Unit is captured at its head Revision; pass --revision to instead pin each Unit to the highest-numbered Revision carrying a Tag. A Unit the Tag marks no Revision of is left out of the Release, and listed as skipped, when it has never been released; once a Unit has been released, the Tag has to mark one of its Revisions, since a Release without it would delete it from the Target.

Each Unit's Revision is selected by Tag, so the value given to --revision has to resolve to one Tag. A Tag itself does; so do the boundaries of a ChangeSet and a ChangeOrder, which is how a whole change is released rather than a revision number that means something different in every Unit:

Form What it selects
<slug> or Tag:<slug> the Revision that Tag marks
ChangeSet:<slug> where a closed ChangeSet ended
Before:ChangeSet:<slug> the state it started from
ChangeOrder:<slug> the ChangeOrder's release Tag: the Space as the change left it
Before:ChangeOrder:<slug> the state before it

A revision number, a Revision ID, and the named revisions each pick out a Revision of one Unit, so they are not accepted.

# Bundle each Unit at the Revision tagged "v1.2.0"
cub release publish --revision v1.2.0 my-space

# See what a Release of a promoted change would bundle, then publish it
cub release publish --revision ChangeOrder:web-base/release-v452 --dry-run my-space
cub release publish --revision ChangeOrder:web-base/release-v452 my-space

# Release the state a promotion started from, to roll it back
cub release publish --revision Before:ChangeOrder:web-base/release-v452 my-space

--label, --annotation, and --delete-gate set the Release's metadata at publish time; cub release update can change it afterwards. --dry-run publishes in a transaction that is rolled back: it reports the Release that would be published, with the Units it would bundle and skip, or the error a publish would return, and publishes nothing.

Publishing advances each bundled Unit's LastReleasedRevisionNum to the revision captured in the bundle. UnreleasedChanges compares a Unit's head revision against it, so it tells you what has changed since the last publish.

What blocks a publish

A Unit with an outstanding Validation Error is not ready to be released, and blocks the publish. This is not terminal: the check re-runs the Unit's Triggers, so once the underlying problem is fixed, the publish succeeds on the next attempt. Validation Errors are evaluated against the revision being bundled, so --revision v1.2.0 checks the gates on the v1.2.0 revisions specifically.

A publish with --revision ChangeOrder:<slug> is a Release for that ChangeOrder, and records it in the Release's ChangeOrderID. It is bundled at the ChangeOrder's release Tag, which marks every Unit of the Space a Release delivers: the change where it applied, a revision resolve made on top of it, or one moved to by hand with cub unit tag ChangeOrder:<slug> --move, and every other Unit as it is deployed. When the ChangeOrder is governed by a ChangeWorkflow, the publish must also satisfy the ReleasePrerequisites of the Stage the Space belongs to, such as an approval of exactly the revisions being bundled. They are evaluated after the bundle is assembled, over the revisions it contains, and a refusal names the requirement and the revision that falls short, and leaves no Release behind:

Failed: HTTP 422: unable to publish a release of change order 'release-v452' in stage 'prod': requires release-manager: 1 Approval attestation(s) from eligible attesters who did not write the change; web revision 3 has 0 of 1

Recording what is missing, here with cub variant approve, and publishing again is how it clears. Publishing a Release for a ChangeOrder also updates the ChangeOrder's Stage, since Released and Final prerequisites read which Spaces have released the change. Before:ChangeOrder:<slug> names the state before the change, so a Release of it is not a Release for the ChangeOrder.

A Release of a ChangeOrder can also deliver revisions from before the change: a Unit's revisions after its last released one, up to where the change starts. They are there when an earlier change was promoted on from this Space without being released here. --prior-revisions Error refuses such a Release and names them, with the ChangeOrder each belongs to; Include, the default, releases them. The ChangeOrder's ReleasePriorRevisions sets the default.

cub release publish --revision ChangeOrder:web-base/release-v452 --prior-revisions Error --dry-run my-space
Failed: HTTP 422: unable to publish a release of change order 'release-v452': it would also release revisions from before the change, which its release PriorRevisions Error refuses: web: revisions 6 (change order release-v451), 7 (no change order)

Withdraw

cub release withdraw <release-id>

Takes a Release out of service: it is no longer published for download, so its OCI endpoint stops serving it. The Release itself is retained — withdrawing is un-publishing, not deleting — so it remains listable and inspectable, and the record of what was published survives.

The Release is located by its globally-unique ID, so --space is not required. If any Unit in the Release's Space that is not Unreleasable has an outstanding DestroyGate, the withdrawal is blocked until the gate clears. The check reads the Space as it is now, not the Release's bundle: a Unit added since the Release was published counts, and one removed or made unreleasable since does not.

Delete

cub release delete <release-id>

Removes the Release and its stored bundle, and the release-N Tag it made; a Tag it adopted stays. This is the destructive form; use withdraw when the intent is to stop serving a Release rather than to erase it. The Release is located by its ID, so --space is not required.

A Release marks the Revisions it bundled, and its Tag marks them too, so the delete is refused until --detach removes the marks: cub release delete --detach <release-id>. See deleting referenced entities.

Get

cub release get --space <space-slug> <release-num>

Gets details about a Release, by the number cub release list shows for it in its Space, or by its ID. It shows everything recorded about the Release: its Space, whether it is published, its Tag, the ChangeOrder it was published for, its Target and who published it, the number of Units it bundled and any it skipped, its bundle name, digests and size, its full live status, and its Labels, Annotations, DeleteGates and Permissions. The live status is shown as sync, health and the operation applying the Release, each followed by the reporting tool's own word for it when that differs, such as Argo CD's Error for Failed, then the message, the reporter, the data source and when the status was observed.

Instead of a number or ID, you can identify a Release within a specific --space by OCI reference:

  • --oci-reference latest — the newest Release published for the Space's release Target.
  • --oci-reference sha256:... — a specific manifest digest.
  • --bundle-digest sha256:... — a specific bundle content digest.

The Release counts the Units it bundled but does not list their Revisions. Each Revision a Release bundled records the Release's ID in its Releases, so list them with:

cub revision list --space <space-slug> --where "Releases ? '<release-id>'"

Compare these lists, rather than Unit counts, when checking whether two Releases bundled the same configuration: two Releases can bundle the same number of Units at different Revisions.

List

cub release list --space <space-slug>

Lists the Releases in a Space, newest first: each one's number, the Tag on the Revisions it bundled, the ChangeOrder it was published for, whether it is published, the first 12 hex digits of its manifest digest, the sync and health its live status reports, and when it was published. A Tag or ChangeOrder in another Space is shown as <space>/<slug>. -o wide adds the operation applying the Release, the Release ID and the whole manifest digest. Without --space it lists the newest Release of each Space. Supports the standard --where, --filter, and --contains selectors:

# Find a Release by bundle digest across the whole organization
cub release list --where "Digest = 'sha256:...'"

In the web UI

The Releases page, in the web UI's top bar, shows the Releases of every Component as a heatmap: a row per Component, a column per day over the last 14, 30 or 90 days. Each cell holds the Releases the Component's Spaces published that day, colored by the worst live status among them and shaded by how many there were; hover over a cell to see them.

Select a Component to list its Releases over the same days, newest first, with their Space, Tag, ChangeOrder, sync and health, as cub release list shows them. The heatmap narrows to the Component and its neighbors to leave room for the list. Each ChangeOrder links to its page, and selecting a Release's number shows its details, as cub release get does.

Getting started

In order to publish a Release, you must have a Target and a Space with Units ready for deployment. This can be done by the following:

cub space create target-space # holds the Target
cub target create --space target-space oci-example # creates the Target
cub space create --release-target target-space/oci-example app-example # creates the Space

cub variant upload --component app-example --variant base --space app-example ./app-example/

The Target lives in its own Space (target-space) to sidestep an ordering problem: a Space's --release-target must already exist when the Space is created, so the Target can't live in the Space it releases. How a GitOps operator authenticates to pull the Release is covered in GitOps.

The cub variant upload step uploads ./app-example/ as Units in the Space app-example, as the base variant of the app-example Component, which it creates if it does not exist. Note the --release-target option on the Space creation: it sets Space.ReleaseTargetID, the Target every Release of the Space is published for. A Unit's Target is not set on the Unit: a Release bundles every Unit of the Space that is not Unreleasable, whose read-only TargetID is the Space's release Target (an Unreleasable Unit's is null), so uploaded Units are picked up by the Release without any per-Unit configuration. Kubernetes/YAML Units are releasable when created, and Units of other toolchains, such as an AppConfig Unit, are unreleasable; cub unit create --unreleasable and cub unit update --unreleasable (or --unreleasable=false) change that for a Unit. Changing the Space's release Target later, with cub space update --release-target, changes where all of its releasable Units go.

Now that we have a Space populated with Units ready for deployment, we can publish a Release. Use the following:

cub release publish app-example

This will bundle all Units and make them available at the Space's OCI repository, oci://<host>:<port>/space/app-example on the server's OCI endpoint, which for https://hub.confighub.com is oci://oci.hub.confighub.com:443/space/app-example.

The created Release will be available at that value plus the OCI tag latest, or its manifest digest, available via the following command:

cub release list --space app-example -o wide

Or get the Release by its number, 1 for the first one published:

cub release get --space app-example 1

Together, the repository URL and latest or a manifest digest can be used to configure an OCI client such as ArgoCD or FluxCD synchronizing the image as an application to be applied to a cluster.

Releasing Updates

Once we have an OCI client synchronizing Releases, we can publish updates from edited Units by running publish once again, like so:

cub release publish app-example

This will bundle the Units at their head Revision, so any updates made, and any Units with no updates since the initial Release, will be bundled. If you want to pin a Release to a specific set of Revisions instead, create a Tag and apply it to those Revisions, like so:

cub tag create --space app-example v0.1.0
cub unit tag --space app-example --where "Slug LIKE '%'" app-example/v0.1.0

cub unit tag requires a selection (--unit, --where, or --filter) and tags each selected Unit's head Revision by default. The --where "Slug LIKE '%'" above matches every Unit in the Space; narrow it (for example --unit my-unit or a label match) to tag only a subset. Then publish a Release pinned to that Tag:

cub release publish --revision v0.1.0 app-example

From there, assuming the OCI client is using the latest OCI tag, the configuration can be automatically synchronized, otherwise, the manifest digest will need to be used.

Taking a Release out of service

If a Release was made erroneously or is later found to have issues, withdraw it. It is un-published — no longer served from its OCI endpoint, so no client can pull it — while the Release itself is retained, so what was published stays on the record:

cub release withdraw <ReleaseID>

Withdrawing does not roll anything back. A GitOps operator following the latest OCI tag needs something to converge on, so publish the previous state as a new Release. --revision Before:ChangeOrder:<slug> names the state a promoted change started from, so publishing it takes the change out of service right away:

cub release publish --revision Before:ChangeOrder:web-base/release-v452 my-space

That Release does not change the Units: their heads still hold the change, so the next publish at head would release it again. To keep it out, abort the ChangeOrder and demote it from each Space that took it, which restores each Unit to before the change with cub unit update --restore Before:ChangeOrder:<slug> --change-order <slug> over the whole Space, and then publish the restored revisions. A change made in a ChangeSet is taken out the same way with Before:ChangeSet:<slug>.

To remove a Release and its stored bundle entirely, rather than to stop serving it, use cub release delete <ReleaseID>. A Destroy Gate on a releasable Unit of the Space blocks withdrawal until it clears.

See tracking and promoting changes for undoing a change that has been promoted into several Spaces.

When a Release breaks something, cub release list --space <space> shows the sync and health reported for each Release, if something reports live status, and the revisions it bundled say what changed: list them as in Get, and use cub unit blame to see who set a value and under which ChangeOrder. Publishing the earlier state, as above, restores service without waiting for a fix; a fix forward is a new change made and released the usual way. A change made directly in the cluster to restore service is overwritten by the next Release unless it is brought back into ConfigHub: cub k8s refresh brings one resource's cluster-side changes back into its Unit.