---
title: "How Queen ships"
description: "The registries a release touches, which two of them CI publishes and which four a human publishes, where the version number lives, and which parts of the older release note no longer hold."
---

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

# How Queen ships

A release touches the broker image, five client SDKs and the CLI, across five package registries
plus GitHub Releases. Two of those channels are published by CI on a trigger; the rest are
published by a person running a command. Knowing which is which is the point of this page.

## Where the version lives

`server/server.json` is the single source. Three separate things read it:

- `server/build.rs` extracts `version` and exports it as `QUEEN_VERSION`, which is what
  `GET /health`, the boot log and `queenctl ping` report. A version bump therefore forces a
  rebuild of the broker.
- `build.sh` uses `name` and `version` to tag and push the image locally.
- `.github/workflows/docker-build.yml` reads both with `jq` for the same purpose in CI.

Everything else carries its own version, or none at all.

| Artifact | Where its version lives |
| --- | --- |
| Broker image | `server/server.json` |
| JavaScript client | `clients/client-js/package.json` |
| Python client | `clients/client-py/pyproject.toml` |
| PHP/Laravel client | nowhere: `composer.json` has no `version` field; Packagist reads the git tag |
| Go client | nowhere: the git tag *is* the version |
| `queenctl` | nowhere: the tag is the version, injected at build time with `-ldflags` |
| C++ client | nowhere: header-only, taken at whatever tag you check out |
| Dashboard (`app/`) | `app/package.json`, private, never published separately |

## The versioning rule

**The broker and the SDKs are version-aligned at 1.0.0.** One `MAJOR.MINOR.PATCH` for the
broker image, npm, PyPI, Packagist, the Go module tag and the CLI tag, so a client version tells
you which broker it was released against without a compatibility matrix.

> **Note**
>
> The numbers in the tree are now aligned on 1.0.0. The broker (`server/server.json`), the proxy
> (`proxy/Cargo.toml`) and the dashboard (`app/package.json`) carry the prerelease `1.0.0-beta.1`;
> the JavaScript and Python clients carry `1.0.0`. The Go module, the CLI and the Packagist
> package take their version from git tags, so they are aligned by tagging at release time and
> not by editing a file. `README.md` points at `ghcr.io/queen-mq/queen:latest`, but its prose
> still carries 0.16-era numbers.

## What CI publishes

### The container images

`.github/workflows/docker-build.yml` publishes **two** images to GHCR, as a matrix over the two
Dockerfiles:

| Image | Dockerfile | Version from |
| --- | --- | --- |
| `ghcr.io/queen-mq/queen` | `Dockerfile` | `server/server.json` |
| `ghcr.io/queen-mq/queen-proxy` | `proxy/Dockerfile` | `proxy/Cargo.toml` |

Both build for `linux/amd64` and `linux/arm64` from the repository root: the proxy crate embeds
`../server/webapp/dist`, so its context cannot be `proxy/`. Each gets three tags: its own
version, `sha-<short>`, and `latest`. The broker also receives `QUEENCTL_VERSION` and
`QUEENCTL_COMMIT` as build arguments so that `queenctl version` inside the container matches what
the broker reports.

Authentication is the workflow's own `GITHUB_TOKEN` under `permissions: packages: write`, so there
are no registry secrets to rotate. Layer caching is the GitHub Actions cache, **scoped per image**
(`scope=${{ matrix.image }}`); a shared scope would make the two matrix legs evict each other's
layers and cold-build every run.

Triggers are a push to the **`release` branch**, a **`v*` tag**, or `workflow_dispatch`. A pull
request against `release` builds both images without pushing.

> **Note**
>
> A GHCR package is **private** the first time it is published. Flip it once, by hand, under
> [the org's packages](https://github.com/orgs/queen-mq/packages) → the package → Package settings.
> Until then `docker pull` fails for everyone but the org.

### The CLI

`.github/workflows/release-cli.yml` fires on a tag matching `clients/client-cli/v*` and runs
GoReleaser v2, which cross-compiles `queenctl` for linux, darwin and windows on amd64 and arm64
(windows/arm64 excluded), produces `tar.gz` archives (`zip` on windows) with checksums and
pre-built shell completions, and attaches them to a GitHub Release. Version, commit and build
date go in through `-ldflags`, so `queenctl version` reports the tag.

The directory-prefixed tag shape is required by Go's module proxy for a nested module, and
GoReleaser wants bare semver, so the workflow strips the prefix into `GORELEASER_CURRENT_TAG`
and runs with `--skip=validate`. The bare semver tag does not physically exist at the release
commit, only the prefixed one does.

## What a human publishes

Four channels have no workflow. Each needs a command run by a maintainer.

| Channel | Package | How |
| --- | --- | --- |
| npm | `queen-mq` | `npm publish` from `clients/client-js` |
| PyPI | `queen-mq` | `clients/client-py/publish.sh`, which builds the wheel and sdist and uploads with twine |
| Go modules | `github.com/smartpricing/queen/clients/client-go` | push a `clients/client-go/vX.Y.Z` tag; the module proxy picks it up |
| Packagist | `smartpricing/queen-mq` | push a `vX.Y.Z` tag; a webhook makes Packagist pull it |

The C++ client needs nothing published: consumers take `clients/client-cpp/queen_client.hpp` at
the tag.

Two tag shapes are load-bearing and easy to get wrong. Both nested Go modules need the
directory-prefixed form (`clients/client-go/v1.0.0` and `clients/client-cli/v1.0.0`) because
that is how Go's module proxy resolves a module that is not at the repository root. Without the
prefix, `go get` reports no matching versions. The tags already in the repository follow both
shapes, alongside the plain `vX.Y.Z` broker tags.

## Order

The broker tag and image go first, because every SDK is released *against* a broker; then npm,
because most downstream consumers wait on it; then PyPI; then the Go module tag; then the plain
`vX.Y.Z` tag that Packagist and the C++ header hang off; then the CLI tag, which triggers its own
workflow.

## Rollback, and the one place additivity stops

Schema application is idempotent by construction. `server/src/schema.rs` applies `schema.sql`
and all 34 stored-procedure files at every boot, in lexical order, under a session advisory lock,
and every statement is `CREATE OR REPLACE`, `IF NOT EXISTS` or `ADD COLUMN IF NOT EXISTS`. So
re-applying is always safe, and rolling the broker image back to a previous 1.x is safe at the
database level.

There is one hard exception, and it is the boundary into 1.0.
`server/sql/procedures/040_log_drop_legacy.sql` runs before the log-engine schema and
**drops** the retired `seg_*` engine: every `queen.seg_*` function, discovered dynamically over
`pg_proc` so no straggler survives, then the legacy tables, then the seg-era columns folded into
`queen.partition_consumers`. It is written to be a no-op on a database that never had them and a
full teardown on one that did.

> **Danger**
>
> Booting a 1.0 broker against a database that was serving the retired segments engine removes
> that engine's objects. There is no data migration: the log engine is a greenfield replacement.
> Rolling *back* to a segments-era broker after that boot is not possible at the database level.
> Take a `pg_dump` before the first 1.0 boot against an existing database.

## What the older release note gets wrong

`developer/14-release.md` is the pre-1.0 checklist and is still in the tree. Read it for the
publishing mechanics of npm, PyPI and Packagist, which are unchanged. Everything below it says
no longer holds.

| It says | Actually |
| --- | --- |
| the version is `0.14.1.beta.1` in `server/server.json` | it is `1.0.0-beta.1`, shipping as `1.0.0` |
| the release covers "the C++ broker binary, libqueen, and five client SDKs" | there is no C++ broker and no `lib/`. The image carries the Rust broker, the dashboard and `queenctl` |
| the pre-release checklist runs `cd lib && make test-contention` | that directory does not exist. The gate is `test/run.sh`. See [Testing](/internals/contributing/testing) |
| user-facing docs live in `docs/*.html`, and a version bump has to edit `docs/index.html` and `docs/quickstart.html` | that site is retired. Documentation is `webdoc/`, and it is built and gated by its own workflow |
| the version lives "in nine places" including those HTML files | the live places are `server/server.json`, `proxy/Cargo.toml`, `app/package.json`, `clients/client-js/package.json`, `clients/client-py/pyproject.toml`, `README.md`, and the image tag shown in these docs |
| a rolled-back procedure can be re-applied from `lib/schema/procedures/` | procedures live in `server/sql/procedures/` and are embedded in the binary, so the binary and the procedures roll together: you cannot roll one back independently |
| `helm/`, `helm_queen/` and `proxy/helm/` are release artifacts to update | those directories are gitignored and untracked. Nothing in CI reads them and they are not shipped |
| the schema is "always additive between versions", so a rollback is always safe | true within 1.x, false across the 0.16-to-1.0 boundary. See the warning above |

One workflow is dead in the same way. `.github/workflows/cpp-server-build.yml` triggers on the
`release` branch and runs `make deps` and `make build-only` in `server/`, targets that were
removed with the C++ broker; it cannot succeed. And `.github/workflows/cli.yml` pins its
end-to-end broker to `smartnessai/queen-mq:0.14.3`, so the CLI's end-to-end suite currently
tests against a pre-log-engine broker.

## Post-release

- Smoke-test the published packages from a clean directory rather than a local link: install
  `queen-mq` from npm and from PyPI, and `go get` the client module at the new version.
- Write release notes covering schema changes, new environment variables and anything removed.
  A removed route or environment variable is a major-version change.
- Re-check that the published image tag in these docs matches what was pushed. It appears in
  [Start here](/start) and [Self-hosting](/selfhost).

Source: https://queenmq.com/internals/contributing/release/index.mdx
