# Go Release

> 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 Renovate 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 Renovate coverage to a Go repo. Not for scaffolding a new Go project or its Makefile.

- Skill: `bitwise-media-group/go-release` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add bitwise-media-group/go-release`
- Raw SKILL.md: https://api.skillmd.com/api/skills/bitwise-media-group/go-release/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- License: MIT
- Author: bitwise-media-group (https://skillmd.com/u/bitwise-media-group)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/bitwise-media-group/go-release

---


# 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; Renovate keeps everything —
including the action pins — fresh. This layers on the layout and Makefile from the `go-project`
skill.

For a `bitwise-media-group` repo, prefer the org's centralized, SHA-pinned CI/security/release/merge
callers over the bespoke `ci.yaml`/`release.yaml` below — see the `actions-reusable-workflows`
skill. The templates here are for a Go repo outside that org infrastructure.

## 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. Renovate 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. Renovate

Copy [templates/renovate.json5](templates/renovate.json5) to `.github/renovate.json5`: a 7-day
cooldown before any update is proposed, minor + patch bumps grouped into one PR per ecosystem
(majors arrive as individual PRs). `tools/go.mod` gets its own group — Renovate's gomod manager
finds nested `go.mod` files with no extra config, unlike Dependabot's per-directory ecosystem
entries — so developer-tool bumps never ride along with application dependency PRs, and the
`github-actions` rule 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).

