Skip to content

Releases and images

A release is a git tag vX.Y.Z. Pushing one runs .github/workflows/release.yml, which publishes three images and a GitHub release. Nothing else publishes images: a push to main builds none.

What a tag publishes

Image Platforms Built from
silentswarm-leader linux/amd64, linux/arm64 docker/Dockerfile.leader
silentswarm-frontend linux/amd64, linux/arm64 docker/Dockerfile.frontend
silentswarm-fellow linux/amd64 docker/Dockerfile.fellow (torch wheels with CUDA)

The leader and frontend also build for arm64 because a leader is a small box: a Raspberry Pi or an Ampere VM is enough. The fellow is amd64 only, where the CUDA wheels are.

Each image is tagged X.Y.Z, X.Y and latest, under ghcr.io/<repository owner>/ unless the repository variables IMAGE_REGISTRY and IMAGE_NAMESPACE name another place (a registry other than GHCR also needs the secret IMAGE_REGISTRY_TOKEN). Every image carries an SBOM and a provenance attestation (buildx --sbom --provenance=mode=max); inspect them with docker buildx imagetools inspect <image> --format '{{ json .SBOM }}'. Whether a package is public is a setting on the package in GitHub, not in the workflow.

The version is baked in as SWARM_IMAGE_VERSION. The leader reports it in GET /api/v1/health ("version") and in the footer of the signed-in pages, and a fellow reports it at registration, so the Nodes view shows which release each machine runs. A local build leaves it empty and the installed package's version stands in.

Cutting a release

  1. Set version = "X.Y.Z" in pyproject.toml and appVersion: "X.Y.Z" in deploy/helm/silentswarm/Chart.yaml (the images the chart installs by default). Bump the chart's own version too when the chart changed.
  2. In CHANGELOG.md, move the entries under ## [Unreleased] to a new ## [X.Y.Z] - YYYY-MM-DD section. It becomes the release notes.
  3. Commit, then tag and push: git tag vX.Y.Z && git push origin vX.Y.Z.

Before tagging, check the keyless MCP sign-in with the current TypeScript MCP SDK, which most MCP clients build on (the Python SDK runs in the test suite):

npm i --prefix /tmp/mcp-sdk @modelcontextprotocol/sdk@latest
MCP_SDK_DIR=/tmp/mcp-sdk node scripts/mcp_oauth_client_check.mjs

It signs in to a throwaway leader as a client would, calls tools and refreshes, for /mcp and a toolset URL. Put the SDK version it prints, and any client apps you signed in with by hand, in the client table of docs/06_user_guide/mcp.md ("Which clients are checked").

Check steps 1 and 2 before tagging with python3 scripts/release_notes.py vX.Y.Z: it prints the notes, or says what disagrees. The workflow runs the same check first and builds nothing when it fails.

What the workflow checks before it pushes

Each image is first built for amd64 and loaded into the runner, and scripts/smoke_image.sh runs against it:

  • leader: /api/v1/health answers, with the tag's version;
  • frontend: /docs/ is served, with the canonical URL written at start, by a process that is not root;
  • fellow: import torch works and fellow --help runs.

Only then is the multi-platform image built and pushed. The same script works on a local build:

docker build -f docker/Dockerfile.leader --build-arg SWARM_IMAGE_VERSION=0.0.0 -t leader:try .
scripts/smoke_image.sh leader leader:try 0.0.0