Skip to main content
All changes flow through a staging environment before reaching production. There are no release branches — everything merges to main.

How It Works

  1. Merge to main — continuous-delivery-stg.yml builds a version.sha.beta image, deploys it to staging (stg.activepieces.com), and runs the E2E suite against staging (e2e-staging).
  2. By hand — continuous-delivery-cloud.yml (the Cloud gate) picks the newest main commit whose e2e-staging passed, runs the E2E matrix and the upgrade test on the image staging built from it, and waits for one approval.
  3. On approval — the gate deploys that same image to the canary, then to the whole production fleet.
  4. Self-hosted — release-self-hosted.yml, by hand. The weekly continuous-delivery-release.yml is disabled.

Environments

Hotfix Workflow

  1. Merge the fix into main.
  2. Run continuous-delivery-cloud.yml by hand with hotfix_prs set to the PR numbers, for example 17001,17005. The gate cherry-picks only those PRs, oldest first, onto the commit Cloud runs (branch hotfix/cloud/<run id>-<attempt>), builds a version.sha.pick image, and runs the E2E matrix and the upgrade test on it. The rest of main waits for the next full gate run.
  3. The run summary names what Cloud runs and what this run adds. The approval and the deploy are the same as any other Cloud deploy.
The run stops when a PR is not merged into main, is already on Cloud, does not apply cleanly, or changes a migration (set allow_migrations only when the migration needs nothing Cloud lacks). A hotfix deploy records no Linear release; the next full gate run does. That run deploys main only if main contains every picked PR. To ship all of main at once instead, leave hotfix_prs empty: the gate takes the newest commit that passed e2e-staging, or the commit named in sha. If Cloud is down and the gate is too slow, use emergency-cloud-deploy.yml. To go back to an earlier image, use continuous-delivery-rollback.yml.

Self-Hosted Release

Use release-self-hosted.yml (see below). The weekly continuous-delivery-release.yml still reads the old release-candidate tag and is disabled.

GitHub Actions Workflows

To run any workflow manually, go to the repo’s Actions tab, select the workflow, and click Run workflow.

continuous-delivery-stg.yml — Staging

Manual dispatch choices

  • deploy-staging — Re-deploy the latest main to staging. Use this after an infra change.

continuous-delivery-cloud.yml — Cloud Gate

The gate has no schedule yet. A daily run (Sunday to Thursday, 8 AM UTC) comes back when the E2E suite covers more of the product; the cut job already stops a scheduled run when every Cloud host runs the candidate. Then, for the candidate:
  1. e2e-matrix.yml and upgrade-test.yml run on the candidate’s version.sha.beta image. Nothing is built again.
  2. The report job posts the e2e-matrix and upgrade-test commit statuses on the candidate SHA. A red upgrade test says whether the candidate broke or the setup failed before the upgrade.
  3. The run summary lists the tickets between the commit Cloud runs and the candidate.
  4. The approve job waits for approval in the production GitHub Environment. It starts only when every check passed. A newer candidate cancels an older one that still waits.
  5. Under the promote-production lock, the deploy job reads the Cloud version again and refuses to deploy when the canary and the fleet run different images, when Cloud changed after the cut, or when the candidate lacks a PR that Cloud runs (a downgrade, or a fix shipped through another path). Then the breaking-migration check, the canary, the fleet, and the Linear “Cloud” release.
If the gate refuses because the canary and the fleet differ (for example after emergency-cloud-deploy.yml, which deploys only the fleet), deploy the fleet’s tag to the canary from mrsk/prod: kamal deploy --version <tag> --config-file=config/app-canary.yml --skip-push, then the same with config/worker-canary.yml. The canary has no workflow of its own since ENG-1021: the gate’s deploy job runs the breaking-migration check, deploys the canary, then the fleet, under the promote-production lock that continuous-delivery-rollback.yml also uses. See the Canary Deployment playbook.

release-self-hosted.yml

Manual dispatch only. Prompts for a release tag (e.g., 0.79.2 or 0.79.2-hotfix.1). Builds multi-platform images (amd64 + arm64), pushes to Docker Hub and GHCR, and creates a git tag. Can be run from any branch — useful for patching older versions. This is the only release path that takes a free-text version — the scheduled continuous-delivery-release.yml and the Cloud images both derive the version from package.json (node --print "require('./package.json').version") and never ask a human. The free-text input exists precisely because off-cycle patches carry deploy-only suffixes (-hotfix.N, -rc.N) that don’t live in package.json. Because package.json is the single runtime source of truth (apVersionUtil.getCurrentRelease reads it, and it’s baked into the image from source), a mistyped tag would publish an image whose real version disagrees with its tag — invisible until it’s mixed with a correctly-built peer and the worker↔app version gate silently withholds jobs. The Verify tag matches package.json step fails the release when the tag’s base X.Y.Z (suffix stripped) doesn’t equal package.json, so bump package.json (or fix the tag) before releasing. Because the bump commit for an off-cycle release lives on its release branch, main’s package.json would otherwise stay stale, and the next weekly release then reads the old version at the release-candidate ref and refuses to overwrite the already-existing tag (this is how 0.85.4/0.85.5 came to report 0.85.2). To close that loop, a sync-version-to-main job runs after every successful release: if main’s version is behind the released base version, it opens an automated PR (branch automated/sync-main-<tag>, one per tag so a hotfix never overwrites a pending base-release PR, pushed and created with the automated-PR token so CI runs on it) setting package.json to the base version and the docker-compose.yml image pins to the full released tag. Merge that PR before the next release. The same job runs tools/scripts/breaking-changes-rollover.ts: every entry on the hidden docs/install/reference/breaking-changes-unreleased.mdx page that already existed at the released tag is moved under ## <tag> on the public Breaking Changes page, so the PR opens whenever the version or the breaking-changes pages need syncing, not only when the version is behind. If neither needs a change, the job no-ops. The weekly continuous-delivery-release.yml runs the same rollover in its sync-main job, so both release paths file entries under the tag that shipped them. The job lives in the workflow definition at the dispatched ref, so releases run from branches cut before it existed won’t open a sync PR. Check main’s version manually in that case.

publish-embed-sdk.yml

Publishes the embed SDK bundle (packages/ee/embed-sdk) to https://cdn.activepieces.com/sdk/embed/<version>.js. Runs on pushes to main that change packages/ee/embed-sdk/package.json, on a daily schedule, and on manual dispatch (main only). The check job compares the package version against the CDN and publishes only when that version is missing: a 200 skips, a 404 publishes, anything else fails the run. Bumping version in packages/ee/embed-sdk/package.json is the entire release procedure for the SDK; the daily schedule retries failed publishes, and already-published versions are never overwritten. Failures notify the on-call Discord channel. Requires the CDN_S3_ENDPOINT, CDN_S3_BUCKET, CDN_S3_ACCESS_KEY_ID, CDN_S3_SECRET_ACCESS_KEY, and DISCORD_ON_CALL_WEBHOOK secrets.

Changelog

Before each weekly release, ensure the draft GitHub release has accurate notes. PRs should be labeled correctly so the auto-generated changelog categorizes them properly.