5. Publish a release
A new version heading in CHANGELOG.md, pushed to main, triggers a release
through GitHub Actions. Set up the host and repository once, then use the
publish steps for each release.
Set up Cloudflare R2
Use a domain in the same Cloudflare account as the bucket.
- In Storage & databases → R2 object storage, create a bucket.
- Open the bucket’s Settings → Custom Domains → Add and attach your release
hostname, for example
cd.example.com. - In your domain’s Rules, create a cache rule matching that hostname and
set Bypass cache. The root
versionfile must stay current. - From the R2 overview, open Manage API tokens and create a token with Object Read & Write access to this bucket. Save the access key ID and secret access key; also note your account ID and bucket name.
In the project block of scripts/build.sh, set:
RELEASE_URL="https://cd.example.com/"Keep the trailing /. To share a bucket, use a path such as
https://cd.example.com/my-app/; publication then stays under my-app/.
R2_BUCKET is always just the bucket name.
Path segments may contain letters, digits, -, _, ., and ~. Do not use
empty segments, . or .., URL escapes, credentials, a query, or a fragment.
Cloudflare’s R2 guide
has more detail on connecting the hostname. Other S3-compatible hosts require changes to the publication code under
scripts/ci/.
Configure GitHub Actions
In your application’s GitHub repository:
Open Settings → Actions → General → Workflow permissions, select Read and write permissions, and click Save.
Open Settings → Secrets and variables → Actions → Secrets and add:
Repository secret Value R2_ACCESS_KEY_IDR2 token access key ID R2_SECRET_ACCESS_KEYR2 token secret access key R2_ACCOUNT_IDCloudflare account ID R2_BUCKETBucket name, without a path In the Variables tab, add the repository variable
CI_ENABLEDwith valuetrue. CI stays disabled until this is set.
If the permissions setting is unavailable, check the organization policy. GitHub’s workflow settings documentation covers the available controls.
Keep .github/workflows/release.yml at that exact path, on main. Its path and
repository are part of the signing identity trusted by installed applications.
You can edit its contents.
Publish
Check all production targets locally:
./scripts/build.sh --prod-allAdd a version heading at the top of
CHANGELOG.md, above previous releases:## [v1.0.0] - 2026-09-06 Initial release.For later releases, use a new version greater than the current one.
Commit the application changes and changelog, then push or merge them to
main. Open the repository’s Actions tab to see the release workflow.
The workflow checks whether the changelog version already has a Git tag. For a
new, untagged version, it runs the Linux and Windows tests, then builds and
uploads the release. After verifying the uploaded files, it updates the release
host’s version file so installers can find the new version. It pushes the Git
tag last.
A later push with the same tagged version skips the full test suites and reuses the published binaries. Changed installers are tested before publication. Pull requests run the full validation jobs.
Release internals explains the workflow, publication order, and recovery rules.
Verify the release
Check that the release host serves your new version:
curl -fsSL https://cd.example.com/versionUse your configured RELEASE_URL, including any path prefix. Then follow
Install and operate on a test machine.
After publishing a second release, run <APP> update from an older installation.
With update.apply retained, confirm the update and check that the app starts
on the new version. With the dashboard retained, you can also decline the CLI
prompt, refresh the dashboard, and apply the update from its notice.
Retry an interrupted release
For a runner, network, or upload interruption, use Re-run failed jobs on the original workflow run, or:
gh run rerun RUN_ID --failedThe publisher checks which files were already uploaded and verified, then continues with the remaining steps. Successful test jobs do not need to run again. Keep the original source and version for the retry. Do not create the tag by hand or push a dummy commit to restart publication.
If the code or tests need a fix, commit it with a new version heading. Never replace published bytes or move an existing tag.
For a repeated integrity error, inspect the logs and remote objects before retrying again.
Investigate a repeated integrity error
Read the CI error and inspect the bucket before changing anything.
A complete but invalid release prefix that was never promoted. First prove
it: no root pointer naming it, no promotion marker, no Git tag. Then delete the
entire releases/<version>/ prefix and rerun the same workflow.
An incomplete or invalid prefix that was promoted or tagged. Restore the exact original signed objects from trusted storage. Never build different bytes under a version somebody may already be running. If they cannot be restored, treat it as a release incident and publish a new forward version.
An invalid root installer pair with no matching verified staging. Look at the script and bundle before touching them. Either deliberately restore a known-good pair, or remove both objects and rerun after reviewing the candidate that will replace them.
A Git tag already on a different commit. Do not let automation force-move it. Published tags stay immutable; correct the release with a new version instead.
An invalid promotion marker. Restore or reconstruct it only from trusted release records, its commit decides the tag target and its timestamp decides retention.
Preserve remote objects and logs while you investigate.
If you change the release scripts or installers, run the installer and release tests before publishing.