# How do I distribute a Go CLI to multiple platforms with GoReleaser and Homebrew?

> Let one tag-triggered workflow cross-compile and publish the GitHub Release while the homebrew_casks: block updates the tap; the tap needs its own PAT.

- Asked: 2026-06-11
- Answered: 2026-06-12
- Asked by: Devrim
- Tags: ci-cd, go
- Source: https://muhammetsafak.com/just-ask/multi-platform-go-cli-distribution-with-goreleaser-and-homebrew/
- Language: en-US
- Author: Muhammet Şafak

---
**Question:** We have a Go CLI tool (say, CommitBrief). I want both macOS (M1/M2/Intel) and Linux users to install it easily with `brew install`.

Using GoReleaser in GitHub Actions, how do I set up a CI/CD flow that builds binaries on every tag, uploads them to GitHub Releases, and auto-updates a private Homebrew Tap repo?


Short answer: this is exactly the problem GoReleaser solves — a `.goreleaser.yaml` plus a single tag-triggered workflow handles cross-compilation, the GitHub Release, and the Homebrew tap formula automatically.

## Short answer

All you really do is let go of the packaging chain; GoReleaser owns the rest. I covered writing the tool itself in the post about [my first CLI tool in Go](/blog/writing-my-first-cli-tool-in-go/); the question here is how that binary reaches a user's machine.

## Why

1. **Multi-platform is not something to manage by hand.** `darwin/arm64`, `darwin/amd64`, `linux/amd64`, `linux/arm64` — compiling, archiving, checksumming, and uploading each target manually is work you repeat on every release; GoReleaser reduces it to one config file.

2. **The tap is a separate repo, so the default token isn't enough.** The formula lives in the `homebrew-tap` repo; the default `GITHUB_TOKEN` can't write to another repo, so a separate token is required.

3. **A broken config blows up at the worst moment — release time.** A config error you only notice after pushing the tag leaves the release half-done; that's why validation belongs in the PR, not in the release.

## What to do

1. **Define the build matrix in `.goreleaser.yaml`.** Target `darwin/arm64`, `darwin/amd64`, `linux/amd64`, `linux/arm64`. If you want, use `universal_binaries` to produce a single universal macOS artifact; the M1/M2/Intel split disappears for the user.

2. **Set up a tag-triggered GitHub Actions workflow.** With `on: push: tags: ['v*']`, run `goreleaser release`: it cross-compiles, builds archives + checksums, and publishes the GitHub Release.

3. **Auto-update the tap with a `homebrew_casks:` block.** This block commits the cask to a separate `homebrew-tap` repo. Users then `brew install yourname/tap/cli`. What you need: a PAT scoped to push to the tap repo. (The older `brews:` block has been deprecated since GoReleaser v2.10; it still works, but use `homebrew_casks:` for new setups.)

4. **Stamp the version into the binary and validate the config in CI.** Use `ldflags` to stamp version/commit/date so `cli --version` reports correctly. Add `goreleaser check` to the pipeline — if the config is broken you catch it in the PR, not at release time.

**Bottom line:** push a tag and let GoReleaser do the rest — Releases + tap formula ship from one command, and manual packaging is gone. This is the de facto standard in the Go ecosystem; set up tag discipline and a separate tap token, and the flow runs itself. I cover the broader picture of packaging and distributing a CLI in the hub post; fit that distribution model onto this workflow.

## Related Reading

- [Packaging and Distributing a CLI Tool Written in Go](/blog/packaging-and-distributing-a-go-cli-tool/) — Blog
- [Does my channel buffer size choice hide backpressure problems, and how do I decide it?](https://muhammetsafak.com/just-ask/channel-buffer-size-choice-hide-backpressure-problems-decide/) — Just Ask
- [My Go worker in a container never receives SIGTERM and won't shut down gracefully—is this a PID 1 problem?](https://muhammetsafak.com/just-ask/go-worker-never-receives-sigterm-pid-1-problem/) — Just Ask
- [Should I run several dependent operations with errgroup so the first error cancels the rest?](https://muhammetsafak.com/just-ask/should-i-run-several-dependent-operations-with-errgroup-so-the-first/) — Just Ask
