Skip to content

Release Checklist

github-actions[bot] edited this page Oct 8, 2026 · 6 revisions

Release checklist

Marmot is released by pushing a vX.Y.Z tag. The tag triggers release.yml, which:

  1. checks that the tag matches version in package.json;
  2. builds docker/Dockerfile for linux/amd64 and linux/arm64, each on a native runner (ubuntu-24.04 / ubuntu-24.04-arm), and pushes the multi-arch image to GHCR with OCI labels and annotations (docker/metadata-action):
    • final release v0.1.0 → ghcr.io/thinkhumandotdev/marmot:0.1.0, :0.1 and :latest;
    • prerelease v0.2.0-rc.1 → only :0.2.0-rc.1 (no :0.2, no :latest);
  3. smoke-tests the published image: checks the multi-arch manifest, pulls it through docker/docker-compose.yml (+ docker-compose.smoke.yml for ports) and runs scripts/smoke-test.sh – the same checks as the CI compose job (health endpoints through Caddy, socket.io handshake, security headers, worker boot, container healthchecks, a single MARMOT_ROLE=all container with a clean shutdown);
  4. publishes the Helm chart (charts/marmot) to oci://ghcr.io/thinkhumandotdev/charts/marmot with the release version (see Kubernetes);
  5. creates the GitHub Release with notes generated by git-cliff from cliff.toml, marked as a prerelease for -rc/-beta tags and for every 0.x version (Marmot is pre-release until v1.0). The image tags above don't change for 0.x: a final 0.x release still gets :X.Y and :latest.

Nothing is tagged or pushed automatically from a developer machine; the steps below are manual.

1. Prepare

  • main is green in CI (Postgres and MongoDB, Docker image & compose smoke).

  • Milestone issues for the version are closed or moved.

  • Migrations: the Postgres chain in src/migrations/postgres applies cleanly to an empty database (NODE_ENV=production pnpm migrate) and to a database at the previous release (upgrade path). No migration file was edited by hand.

  • pnpm generate:types produces no diff.

  • Dependencies: pnpm audit has no unaddressed high/critical findings; Dependabot PRs are merged or consciously deferred.

  • Configuration docs: every key in src/env.ts is in .env.example and Configuration (tests/int/docs.int.spec.ts enforces it); new instance settings are documented.

  • Documentation: pages marked (landing in the current release) in docs/ describe features that actually merged; remove the markers (and the page from LANDING_PAGES in tests/int/docs.int.spec.ts) or move the feature to planned in Comparison.

  • THIRD_PARTY_NOTICES.md lists every file ported from Uptime Kuma or kan.bn.

  • Manual smoke test of the release candidate image (docker build -f docker/Dockerfile -t marmot:rc .):

    • fresh compose stack with Postgres: setup wizard, HTTP monitor with live heartbeats, a test notification, a published status page opened anonymously, an accepted invitation;
    • the same with docker-compose.mongo.yml;
    • upgrade path: start the previous release, add data, swap the image tag, docker compose up -d; history is intact and the worker reconnects;
    • backup and restore according to Deployment;
    • OIDC login against a test provider when the release touched src/auth/.
  • Choose the version (SemVer; while 0.x, breaking changes bump the minor).

  • On an up-to-date main:

    git switch main && git pull --ff-only
    pnpm release:prepare 0.1.0

    scripts/release.sh sets package.json version, the default image tag (${MARMOT_VERSION:-0.1.0}) in docker/docker-compose.yml, the image tag of the Railway template images (deploy/railway/*/Dockerfile), the Helm chart version and appVersion (charts/marmot/Chart.yaml), rebuilds the GitHub Action bundle (action/dist) and regenerates CHANGELOG.md with pnpm dlx git-cliff@2 (set GIT_CLIFF to use another binary). It prints the next commands and does not commit, tag or push.

  • Review CHANGELOG.md. Commit messages are the source: fix wording by editing cliff.toml (grouping, skips) rather than the generated file, or accept small hand edits for this release only. Call out breaking changes and required manual steps (new required env vars, migrations that take long).

2. Release PR

  • Commit on a branch and open a PR titled chore(release): vX.Y.Z (this exact form is skipped by the changelog):

    git switch -c chore/release-0.1.0
    git add package.json docker/docker-compose.yml charts/marmot/Chart.yaml deploy/railway action/dist CHANGELOG.md
    git commit -m "chore(release): v0.1.0"
    git push -u origin chore/release-0.1.0
  • Wait for CI, squash-merge.

3. Tag

  • Tag the merge commit on main and push the tag:

    git switch main && git pull --ff-only
    git tag -a v0.1.0 -m "v0.1.0"
    git push origin v0.1.0
  • Watch the Release workflow (Actions tab): prepare → build (one job per platform) → image → smoke → chart → release. If smoke fails, the image is already on GHCR but no GitHub Release is created; fix forward with a patch version rather than moving the tag.

  • If the run failed because of the workflow itself (not the code being released), fix the workflow on main and re-run the release without moving the tag: Actions → Release → Run workflow on main with the tag (v0.1.0). It builds and releases the tagged commit with main's workflow.

4. Verify the published image

  • First release only: in GHCR (Packages → marmot → Package settings) set the visibility to Public and link the package to the repository, otherwise docker pull requires a login. Do the same for the charts/marmot package (the Helm chart).

  • Clean docker compose up smoke on a machine (or VM) without a checkout or cached images (while the repository is private, copy docker/docker-compose.yml, docker/Caddyfile and docker/.env.example from the tag instead of using the raw URLs):

    mkdir marmot-smoke && cd marmot-smoke
    base=https://raw.githubusercontent.com/ThinkHumanDotDev/marmot/v0.1.0/docker
    curl -fsSL -O "$base/docker-compose.yml" -O "$base/Caddyfile"
    curl -fsSL "$base/.env.example" -o .env
    sed -i "s/^PAYLOAD_SECRET=.*/PAYLOAD_SECRET=$(openssl rand -hex 32)/" .env
    docker image rm ghcr.io/thinkhumandotdev/marmot:0.1.0 2>/dev/null || true
    docker compose pull
    docker compose up -d --wait
    curl -fsS http://localhost/api/health                       # {"ok":true,...}
    curl -fsS 'http://localhost/socket.io/?EIO=4&transport=polling'   # contains "sid"
    docker compose logs worker | grep 'worker booted'
    docker compose ps                                           # web, realtime healthy

    Open http://localhost, complete the first-run setup, create an HTTP monitor and see heartbeats arrive live. Then clean up with docker compose down -v.

  • On an arm64 host (Apple Silicon, Raspberry Pi, Graviton) repeat at least docker compose up -d --wait and the health check; CI only runs the published image on amd64.

  • docker buildx imagetools inspect ghcr.io/thinkhumandotdev/marmot:0.1.0 lists linux/amd64 and linux/arm64; :latest and :0.1 point at the same digest (final releases only).

5. GitHub Action

The GitHub Action is versioned with the repository: the release tag is its version (uses: thinkhumandotdev/marmot/action@vX.Y.Z), so there is nothing to publish separately.

  • The GitHub Action workflow is green on the release commit: the committed bundle (action/dist/index.mjs) matches its sources and the action passes against the mocked API.
  • In a scratch repository, a workflow with uses: thinkhumandotdev/marmot/action@vX.Y.Z (mode: run against a test instance) passes for an up monitor and fails for a down one; mode: apply on a pull_request shows the plan in the job summary.
  • While Marmot is 0.x, do not create moving major tags (v0); from v1.0 on, move v1 to each v1.Y.Z release so @v1 users get fixes.
  • GitHub Marketplace listing: only actions whose action.yml is at the root of a public repository can be listed, so a listing needs a dedicated thinkhumandotdev/marmot-action repository that mirrors action/ per release (not set up yet).

6. Announce

  • Review the generated GitHub Release notes; add upgrade notes or highlights at the top if needed.
  • Update deployment docs if the upgrade needs manual steps.

Clone this wiki locally