---
name: go-release
description: Release engineering for Go projects — GoReleaser v2 with version ldflags into an internal/version package, SBOMs and multi-arch container images, tag-triggered GitHub Actions releases, CI running build/vet/test -race/govulncheck with SHA-pinned actions, and Dependabot coverage for gomod, tools, npm, and Actions. Use when adding GitHub Actions CI for a Go repository; writing a release workflow that publishes Go binaries, SBOMs, and multi-arch container images; setting up GoReleaser for a Go project; stamping version metadata into a Go binary at build time; or adding Dependabot coverage to a Go repo. Not for scaffolding a new Go project or its Makefile.
license: MIT
---

# Go release engineering

Tag-driven releases: pushing a `vX.Y.Z` tag builds cross-platform archives, checksums, SBOMs, and
multi-arch container images via GoReleaser. CI gates every push; Dependabot keeps everything —
including the action pins — fresh. This layers on the layout and Makefile from the `go-project`
skill.

## 1. Stamp version metadata into `internal/version`

```go
// Package version exposes build metadata stamped into the binary at link time.
// Keep these as vars (not consts) so -ldflags "-X ..." can rewrite them; an
// unbuilt or `go run` binary reports the defaults.
package version

var (
	Version   = "dev"     // semver release tag, e.g. v1.2.3
	Commit    = "none"    // short git SHA
	BuildDate = "unknown" // RFC3339 UTC build timestamp
)
```

Both `make build` and GoReleaser inject the same vars at the same import path:

```text
-ldflags "-s -w -X <module>/internal/version.Version={{ .Version }} ..."
```

Expose the metadata through `--version` output or a `GET /version` endpoint.

## 2. GoReleaser config

Copy [templates/goreleaser.yaml](templates/goreleaser.yaml) to `.goreleaser.yaml` and replace the
`myapp` / `OWNER/REPO` placeholders. What it encodes:

- Static, reproducible binaries: `CGO_ENABLED=0`, `-trimpath`, `-s -w`, for linux + darwin ×
  amd64 + arm64.
- `tar.gz` archives shipping the `LICENSE`, a `checksums.txt`, and an SBOM per archive —
  generated by the `syft` pinned in `tools/go.mod` and invoked via `go tool`, so nothing needs to
  be on `PATH`.
- A changelog that excludes `docs:` / `test:` / `chore:` / `ci:` conventional-commit prefixes.
- `dockers_v2` multi-arch images (buildx) pushed to GHCR with OCI annotations.

Validate locally with `make snapshot` (`goreleaser release --snapshot --clean`) — full build, no
publish.

## 3. CI workflow

Copy [templates/ci.yaml](templates/ci.yaml) to `.github/workflows/ci.yaml`. Every push and pull
request runs `go build`, `go vet`, `go test -race`, and `govulncheck`. Conventions:

- The Go version comes from `go.mod` (`go-version-file`), never hard-coded in the workflow.
- Every action is pinned to a full commit SHA with the tag in a trailing comment — a moved tag
  can never change what runs. Dependabot keeps the pins fresh.
- `permissions: contents: read` — the default token does nothing else.

## 4. Release workflow

Copy [templates/release.yaml](templates/release.yaml) to `.github/workflows/release.yaml`. It
triggers on `v*` tags, checks out with `fetch-depth: 0` (GoReleaser needs full history for the
changelog), sets up QEMU + buildx for multi-arch images, logs into GHCR with the built-in
`GITHUB_TOKEN`, and runs `goreleaser release --clean` — SBOMs come from the pinned `syft` via
`go tool`, so there is no separate syft install step. Cutting a release is exactly: push a semver
tag.

## 5. Dependabot

Copy [templates/dependabot.yaml](templates/dependabot.yaml) to `.github/dependabot.yaml`: daily
checks with a 7-day cooldown, minor + patch bumps grouped into one PR per ecosystem (majors arrive
as individual PRs). `tools/go.mod` gets its own `gomod` entry — Dependabot does not descend into
nested modules — so developer-tool bumps never ride along with application dependency PRs, and the
`github-actions` entry keeps the workflow SHA pins fresh. For the rationale behind each knob and
the full ecosystem matrix (`docker`, `docker-compose`, `uv`, …), see [reference.md](reference.md).
