# Releases

Versions and image promotion


How `erxes/erxes` versions are tagged, how Docker images and frontend bundles are published, and how to read a release as a self-hoster. For upgrading a deployment, see [Upgrades](https://erxes.io/docs/self-hosting-upgrades).

## Who cuts a release

Maintainers create releases from the shared integration branches; contributors never update release metadata. Pull requests are squash-merged and release notes are generated, not hand-written. The release itself is driven by `release-it`:

- `pnpm release` runs `release-it` (declared as a dev dependency, `^17.0.0`).
- [`.release-it.json`](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/.release-it.json) disables npm publishing, commits with the message `release ${version}` (visible in history as `release 3.1.9`), creates a git tag named `${version}` matching the `3.*` pattern, and publishes a GitHub release named `v${version}`.
- `@release-it/conventional-changelog` with the Angular preset rewrites `CHANGELOG.md` from conventional commit messages, which is why `feat(scope):` and `fix(scope):` matter. See [Contribute to codebase](https://erxes.io/docs/contribute-codebase).

## The changelog

Each [`CHANGELOG.md`](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/CHANGELOG.md) entry links the compare range, groups commits under `### Bug Fixes` and `### Features`, and keeps issue and commit links. The 3.1.9 entry looks like this:

```text
## [3.1.9](https://github.com/erxes/erxes/compare/3.1.8...3.1.9) (2026-09-17)

### Features

* **core:** add "Basic information" system fields to Settings → Properties (c8b7214)
* **core:** add member email activity log (#9350) (b65966c)
* **frontline:** use "Visible to create" for convert properties on every kind (f93184b)
```

## What CI publishes

Per-area workflows under `.github/workflows/` run builds on pull requests and publish artifacts only on pushes to `main` or version tags:

| Workflow files | Artifact | Tags |
| --- | --- | --- |
| [`ci-api-*.yml`](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/.github/workflows/ci-api-core.yml) (gateway, core-api, 11 plugin APIs) | Docker image `erxes/erxes-next-<service>` (`erxes-next-gateway`, `erxes-next-core-api`, `erxes-next-sales_api`, …) | `latest` and `YYYYMMDD-<short-sha>` on `main` pushes (`core-api` also publishes from `debug/core`) |
| [`ci-core-ui.yml`](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/.github/workflows/ci-core-ui.yml) | `erxes/erxes-next-ui` | `latest`, branch, short SHA, and semver tags via `docker/metadata-action` |
| `ci-service-*.yml`, `ci-saas-migrations.yml` | `erxes/erxes-next-logs`, `erxes/erxes-next-automations`, `erxes/erxes-next-migrations` | `latest` and date-SHA |
| [`ci-apps-*.yml`](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/.github/workflows/ci-apps-help-center.yml) | `erxes/frontline-widgets`, `erxes/posclient-front`, `erxes/help-center` | `latest` (`help-center` and `frontline-widgets` also get date-SHA or metadata tags) |
| [`ci-ui-*.yml`](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/.github/workflows/ci-ui-sales.yml) (plugin remotes) | No image; the Rspack Module Federation build is synced to Cloudflare R2 at `s3://erxes-next/<folder>/<name>_ui/` | Folder is `latest` on `main`, or the tag name on a version tag |

## The release workflow

Pushing a tag matching `[0-9]*` (or running the workflow manually with a `version` input) triggers [`release.yml`](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/.github/workflows/release.yml). For each of the 19 images in its matrix (the gateway, core-api, all plugin APIs, logs, automations, `erxes-next-ui`, `frontline-widgets`, `posclient-front`, and `help-center`), it runs:

```bash
docker buildx imagetools create -t "<image>:<version>" "<image>:latest"
```

So a version tag does not rebuild anything; it promotes whatever `:latest` was current on Docker Hub at that moment. [`sentry-release.yml`](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/.github/workflows/sentry-release.yml) runs on the same tag and creates a Sentry release `erxes-<version>` on `sentry.erxes.io`, attaches commits, uploads source maps from `frontend/core-ui/dist` and `frontend/plugins/*/dist`, and marks a deployment.

## Reading a release as a self-hoster

- Pin `erxes/erxes-next-<service>:3.1.9`-style tags rather than `latest`. The version tag is a copy of the `:latest` build that was current at release time, so the tag and `latest` can diverge immediately afterward.
- A product version does not prove every image has a matching tag; check the set you deploy with `docker buildx imagetools inspect erxes/erxes-next-core-api:3.1.9` (or `docker manifest inspect`).
- Read `CHANGELOG.md` for the fixes and features between your current version and the target, then check [Database Migrations](https://erxes.io/docs/self-hosting-migrations) before following [Upgrades](https://erxes.io/docs/self-hosting-upgrades).
- Frontend plugin code is served from the versioned R2 folder, not from an image; a release updates both the images and the `erxes-next/<version>/` bundle prefix.

