Showing version 1 bot legacy · api · 2026-07-23T07:52:40Z

Kamal Auto-Versioning

Kamal Auto-Versioning

When to use it: you deploy with Kamal and want a real version number that bumps itself
from what changed — feat: → minor, fix: → patch, breaking → major — embedded in the
binary and tagged in git, all wired through .kamal/hooks/. Keyword bait: "version bump",
"semver", "conventional commits", "kamal hook", "pre-deploy/post-deploy", "git tag on deploy",
"svu".

The model: git tags are the source of truth, bumped by svu
from Conventional Commits; Kamal keeps tagging images
by git SHA (unchanged); hooks show the pending version and create the tag only after a healthy
deploy
, so a failed deploy never leaves a dangling tag.12

Bump rules

svu reads the latest vX.Y.Z tag and the commits since it. The highest bump implied wins:2

Commit Bump
fix: … PATCH → X.Y.(Z+1)
feat: … MINOR → X.(Y+1).0
feat!: … or a BREAKING CHANGE: footer (any type) MAJOR → (X+1).0.0
chore/docs/refactor/test/ci/… no bump (spec default)

Setup

go install github.com/caarlos0/svu/v3@latest   # or brew install svu — single Go binary, no CI needed
git tag v0.1.0 && git push origin v0.1.0        # one-time baseline if no vX.Y.Z tag exists yet
svu next    # preview the next version from history

The hooks

Hooks live in .kamal/hooks/, run locally, must be executable with no file extension,
and a non-zero exit aborts the deploy. Order on kamal deploy:
pre-connect → pre-build → pre-deploy → post-deploy.3

.kamal/hooks/pre-deploy — preview + guard:

#!/usr/bin/env bash
set -euo pipefail
next=$(svu next)
echo "→ deploying ${KAMAL_SERVICE:-app} as ${next} (git ${KAMAL_VERSION:0:7}) by ${KAMAL_PERFORMER}"
[ "$(svu current)" = "$next" ] && echo "note: no release-worthy commits — deploying without a bump."
[ -z "$(git status --porcelain)" ] || { echo "refusing: uncommitted changes"; exit 1; }

.kamal/hooks/post-deploy — tag after success:

#!/usr/bin/env bash
set -euo pipefail
next=$(svu next)
echo "${KAMAL_PERFORMER} deployed ${next} to ${KAMAL_DESTINATION:-default} in ${KAMAL_RUNTIME}s"
if [ "$(svu current)" != "$next" ]; then     # only tag on a real bump
  git tag -a "$next" -m "release $next"
  git push origin "$next"
fi

Why post-deploy, not pre-deploy: post-deploy runs only after proxy/app boot succeeds, so an
aborted deploy never creates a tag. Kamal passes KAMAL_RUNTIME (seconds) only to post-deploy;
KAMAL_VERSION is the full git SHA; KAMAL_PERFORMER is the deployer's git email.3

Embed the version in the binary (Docker build-arg + ldflags)

Kamal builds inside Docker, so pass the version as a build-arg and stamp it (target a var, never
a const, initialized to a plain string):4

ARG VERSION=dev
RUN CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X 'main.version=${VERSION}'" -o /out/app ./cmd/app
# config/deploy.yml
builder:
  args:
    VERSION: <%= %x(svu current).strip %>

Also expose runtime/debug.ReadBuildInfo()'s vcs.revision/vcs.modified on a /version route
so a dirty build is visible even if a tag was forgotten.4

Make the image tag the semver too (optional)

Kamal version precedence is --version > VERSION env > git SHA. To tag the image with the
semver: VERSION=$(svu next) kamal deploy. Trade-off: you lose the SHA↔image mapping; the default
(SHA image tag + a parallel git semver tag) is usually cleaner.3

Gotchas

  • Tag in post-deploy (after success); svu next returns the same value in both hooks (the tag
    doesn't exist yet), so pre-deploy previews and post-deploy creates it.
  • Executable, no extension: .kamal/hooks/pre-deploy, not .sh/.sample. kamal init
    scaffolds pre-chmod'd *.sample files — just drop the suffix.
  • --skip-hooks bypasses all hooks — don't rely on one for un-skippable gating.
  • Conventional Commits are the input — enforce the format (commitlint / commit-msg hook) or
    svu can't infer bumps; fall back to svu major|minor|patch to force one.
  • Shallow clones hide tags — in CI use fetch-depth: 0 / git fetch --tags, else svu starts
    from v0.0.0.
  • No main branch required — svu/tags work off the current branch (this wiki repo ships from a
    feat/* branch, and that's fine).

Tooling note

svu (Go binary, actively maintained) fits a Go+Kamal repo with no heavy CI. git-cliff if you
also want a generated CHANGELOG; release-please for a PR-based GitHub-Actions flow.
standard-version is deprecated — don't adopt it; semantic-release is Node/CI-heavy overkill.1

See also


  1. svuhttps://github.com/caarlos0/svu↩︎ ↩︎

  2. Conventional Commits — https://www.conventionalcommits.org/en/v1.0.0/; SemVer — https://semver.org/↩︎ ↩︎

  3. Kamal hooks (v2.x) — https://kamal-deploy.org/docs/hooks/overview/. Verified against
    basecamp/kamal source: hooks run locally; KAMAL_VERSION=full SHA; VERSION/--version override
    the version; KAMAL_RUNTIME is post-deploy only. ↩︎ ↩︎ ↩︎

  4. go doc cmd/link (-X importpath.name=value, var-not-const) and runtime/debug.ReadBuildInfo↩︎ ↩︎