Skip to content

Runbook: releasing

Both apps release the same way: green tests, commit, kamal deploy, verify, record. Deploys build the Docker image locally and push it to Docker Hub; Kamal builds from a clean git clone of HEAD, so uncommitted changes never ship.

Website (keferboeck.com)

  1. Verify on dev first: the site runs permanently via launchd on port 3002 (com.georg.dev.keferboeck); restart with launchctl kickstart -k gui/$UID/com.georg.dev.keferboeck.
  2. If articles or pages changed: node scripts/generate-sitemap.js (also refreshes the AI advisor index) and commit the result.
  3. Commit and push. Then kamal deploy from the repo root (~15–20 min; the builder uses the plain docker driver on purpose, see incidents).
  4. Verify: curl -s -o /dev/null -w "%{http_code}" https://keferboeck.com/en-gb plus the pages the release touched, in both locales.

Portal (hub.keferboeck.com)

  1. bundle exec rspec — the full suite must be green; critical paths are contract.
  2. Commit and push. kamal deploy (~2 min; the entrypoint runs db:prepare, so migrations apply on boot).
  3. Verify: https://hub.keferboeck.com/admin/... for the touched screens; watch kamal app logs -f if anything looks off.
  4. New env secrets: add to .kamal/secrets (reads from git-ignored files) and the env.secret list in config/deploy.yml before deploying.

GitHub release notes (step by step)

Every verified production deploy ends with a tagged GitHub release, so the release history lives where the code lives. Each app repo carries bin/release:

  1. Deploy and verify (steps above). Do not tag an unverified deploy.
  2. From the repo root run bin/release.
  3. Tags are CalVer: v2026.09.11, with .2, .3 appended for repeat releases on the same day. An explicit tag can be passed: bin/release v1.4.0.
  4. The notes are generated from every commit subject since the previous tag (commit messages are written to be readable for exactly this reason), under a ## Changes heading.
  5. Edit on GitHub afterwards if a release deserves prose: highlights first, generated commit list underneath.
  6. The baseline is v2026.09.11 on each repo (website keferboeck-next-react, portal keferboeck-portal, shops solidus-demos): a hand-written summary of everything in production at the time tagging began.

Rules: one release per deploy, never batch days together; a rollback gets its own release tagged from the rolled-back SHA with a note saying why; releases are immutable like ADRs — correct with a new one.

Where else a change is recorded

  1. The ClickUp task is completed with the mandatory fields (problem, Solution, Automated Tests, QA Steps) — the canonical per-change record.
  2. Client-visible changes additionally get a portal Document (category release) assigned to the affected company, written for humans.
  3. Architecture-level changes update this knowledge base in the same PR (definition of done).

Rollback

kamal rollback <version> from the app repo (versions are git SHAs; kamal app images lists what the droplet still holds). Portal migrations are additive by convention, so a one-version rollback is safe without a schema down-step.