Releases
A release is a version of your app: the exact images and files that ship together as, say, 2.4.1. Publishing one records each image by its digest, so what a customer gets for 2.4.1 can't change afterwards, even if you later push a different build to the same tag.
Releases do three things:
- They give your customers a page for each version, with release notes, pull commands that fetch exactly the released images, and the files you attached.
- They tell Instances what "the newest version" is, so you can see who is behind.
- They are what a customer on an air-gapped network bundles with the Breakwater CLI (see Air-gapped bundles).
Releases and their files are on every plan. The customer API, bundle events and the air-gapped panel on the customer's release page are part of the Growth, Scale and Enterprise plans.
Publishing a release
Open a product and click New release in its Releases card. Members, admins and owners can publish; viewers can read releases and download their files.
| Field | Rules |
|---|---|
| Version | 1 to 64 characters from letters, digits and . _ + -, starting with a letter or digit, such as 2.4.1 or 2.4.1+build.5. Unique within the product. new is reserved. |
| Release notes | Optional Markdown, up to 64 KB. Customers see them with the release. |
| Images | 1 to 50 images (see below). |
| Files | Optional, up to 20 files, 1 MB each and 5 MB in all. |
A version can never be used again for that product, not even after you delete the release. This keeps 2.4.1 from meaning two different things to a customer who remembers the first.
Images
Each image is one of:
- One of the product's repositories, plus a tag. Breakwater looks up the digest the tag points at when you publish. A multi-platform image is recorded as its index, so every platform is part of the release.
- An outside image, such as
postgres:16.4, for things your app needs that you don't host. It must be on a public registry (anonymous pulls) and must have a tag; a bare digest isn't enough, thoughpostgres:16.4@sha256:...is fine. Breakwater looks up its digest when you publish. Short names are expanded, sopostgres:16.4is recorded asdocker.io/library/postgres:16.4. Only HTTPS registries are used, and redirects aren't followed. Outside images stay on their own registry: Breakwater records them but doesn't store or serve them.
A Zarf package in a product repository can be one of a release's images: add its repository and the package version as the tag (see Zarf packages).
If you add no images, Breakwater uses every repository of the product that has a tag named exactly like the version. If you attach a Compose file and add no images, it reads them from the file instead (see below). If neither finds anything, publishing fails and says so.
Filling images from a Compose file
On the form, Fill from a Compose file reads a compose.yaml (or docker-compose.yml) and adds a row for each service's image:. Images under registry.breakwaterapp.com/<your-slug>/... must be repositories of this product, or the form says which service doesn't match; images on any other registry are treated as outside images. A digest the file pins (image: acme/server:2.4.1@sha256:...) must match what the tag points at now, or publishing fails and names the service. Images that use variables, such as ${TAG}, can't be resolved: write the full name and tag.
If you attach a file named compose.yaml, compose.yml, docker-compose.yaml or docker-compose.yml through the API without listing images, the same reading applies.
Files
Files are for what an install needs next to the images: a Compose file, a Helm values file, an env template. Names are 1 to 100 characters from letters, digits and . _ -, unique within the release. Breakwater stores each file's size and SHA-256 and shows them on the release page and in the API.
Anything that's a secret doesn't belong in a release file: customers can read every file of a release they can see, and the files are kept in Breakwater's database, not the registry.
Images stay put
Publishing a release pins its images in the registry, so they stay available for as long as the release exists:
- Pins keep the images and their storage. Registry garbage collection never removes an image a release pins, even if you delete its tag, and that storage counts toward your usage until the release is deleted. Delete releases you no longer need to give it back.
- Withdrawn releases keep their pins. Withdrawing hides a release from customers; it doesn't release the storage, and you can restore it.
- Deleting is what frees the pins. The pins are removed shortly after you delete a release.
While a release is published, the registry also protects what it recorded:
- Released tags are locked. You can't move a tag a published release recorded to a different image, or delete it. Pushing the same image to the tag again works, and a new tag works. The error looks like this:
tag acme/server:2.4.1 is part of release 2.4.1 of Acme Server; push a new tag or withdraw the release first
- Pinned manifests can't be deleted. Deleting a manifest by digest is refused while a release still pins it, or while it's a platform manifest of a multi-platform image a release pins. The error is
manifest sha256:... is part of a release; delete the release first. This holds for withdrawn releases too. - A push during publishing withdraws the release. Tags lock when the release is published. If a push moves one of its tags while it's still publishing, Breakwater withdraws the release and says which tag moved. Its pins still hold the images it was published with, so you can restore it if those are what you meant, or publish a new version. Through the API this is a
422whose body also carries the withdrawn release'srelease_id, and the audit trail shows the publish and the withdrawal. - Floating tags stay free. Tags no release recorded, such as
latest, move as usual.
Tags starting with bw-release- are reserved: Breakwater uses them for pins, and you can't push or pull them.
If a tag has moved since a release was published, its page says Tag moved since release. Customers still get the recorded digest.
Who sees a release
A customer sees a published release when they have an active license for the product whose tag pattern allows the tag of every one of the release's images from your repositories. Outside images don't count towards this. A release whose repositories were deleted or removed from the product is marked incomplete and customers don't see it until you fix that. The release page shows how many customers it's available to.
A customer who can see a release can pull exactly that release's images by digest, even after a tag has moved on to a different image.
Withdraw, restore and delete
Admins and owners manage a release's life from its page.
| Action | What happens |
|---|---|
| Withdraw | Customers stop seeing the release and its images stop being granted through it. Its tags are no longer locked. It keeps its pins. |
| Restore | A withdrawn release is published again. |
| Delete | Only for a withdrawn release. Its images and files are removed and its pins are cleaned up. A tombstone stays so the version can't be reused. |
Each is recorded in the audit log.
Instances and releases
Instances are judged against releases: an instance is behind when a release newer than the version it reports is available to its customer. See Instances. A product with no releases shows no "available" badges.
Air-gapped bundles
Some customers run where nothing reaches the internet. With the Breakwater CLI they pick a release, the CLI pulls its images and files with their pull token and writes one file they can carry across, and loads it into their own registry on the other side. They use the same pull tokens and licenses as everyone else, so there's nothing for you to do beyond publishing releases.
Offline installs can't check in, so Breakwater records bundle events: when a customer starts and finishes a bundle, with the release, platforms, CLI version, size and the address they ran it from. They show on:
- the release page, in its Bundles card, with how many customers have bundled it;
- the customer's page;
- the Instances page, in its Bundles card.
Bundle events and the customer API behind them are part of the Growth, Scale and Enterprise plans. On other plans the portal shows no Bundles cards, and the customer's release page hides the whole air-gapped panel, Docker commands included. Their digest-pinned docker pull commands under Images still work.
Through the API
Releases are in the Vendor API, nested under products, and keyed by version rather than by uid:
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/vendor/products/{uid}/releases |
List the product's published and withdrawn releases, newest first |
POST |
/api/v1/vendor/products/{uid}/releases |
Publish a release |
GET |
/api/v1/vendor/products/{uid}/releases/{version} |
Get a release |
POST |
/api/v1/vendor/products/{uid}/releases/{version}/withdraw |
Withdraw it |
POST |
/api/v1/vendor/products/{uid}/releases/{version}/restore |
Restore it |
DELETE |
/api/v1/vendor/products/{uid}/releases/{version} |
Delete a withdrawn release (409 if it isn't withdrawn) |
GET |
/api/v1/vendor/products/{uid}/releases/{version}/files/{name} |
Download a file |
GET |
/api/v1/vendor/bundle_events |
List bundle events. Filters: customer_id, product_id, release_id (uids). Growth plans and up; otherwise 403 with {"error": "plan_required"} |
Publishing takes a body like this. images and files are optional, and each file gives content (text) or content_base64, not both:
{
"release": {
"version": "2.4.1",
"notes": "Fixes the importer.",
"images": [
{"repository_id": "repo_def456", "tag": "2.4.1"},
{"external": "postgres:16.4"}
],
"files": [
{"name": "compose.yaml", "content": "services:\n ..."}
]
}
}
A release comes back with its status (published or withdrawn), incomplete and incomplete_reasons, its images (kind, repository_path, tag, digest, reference, media_type, size) and its files (name, size, sha256, and a url to download). Problems are 422 with {"error": "Validation failed", "details": [...]}. The API Explorer has the full schema.
Webhooks
Three events cover releases (see Webhooks):
release.published, when a release is published or restored (data.restoredtells which);release.withdrawn;release.bundled, when a customer finishes a bundle.
Payloads carry file names, sizes and checksums, never file contents.