> ## Documentation Index
> Fetch the complete documentation index at: https://www.activepieces.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Releases & Deployment

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

| Environment | URL | Purpose |
| - | - | - |
| Preview | `branch-name.activepieces.com` | Per-PR ephemeral environment for review. *Requires the `preview` label on the PR.* |
| Staging | `stg.activepieces.com` | Internal testing |
| Canary | `canary.activepieces.com` | Gets each Cloud deploy a few minutes before the rest of production |
| Production | `cloud.activepieces.com` | Live customers |

## 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. No step checks that its candidate contains every picked PR, so check it before you approve that run.

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

| Trigger | What happens |
| - | - |
| **Push to `main`** | Builds a `version.sha.beta` image and deploys to staging. |
| **Manual dispatch** | Presents a dropdown with one choice (see below). |

#### Manual dispatch choices

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

### `continuous-delivery-cloud.yml` — Cloud Gate

| Trigger | What happens |
| - | - |
| **Manual dispatch** | Picks the newest `main` commit with a passed `e2e-staging` check, or the `main` commit named in `sha`, which must also have a passed `e2e-staging`. `skip_migration_check` lets a breaking migration through. |

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, or when Cloud changed after the cut. It does not check that the candidate contains every hotfix Cloud runs. 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](https://www.activepieces.com/docs/handbook/engineering/playbooks/canary-deployment.md).

### `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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.