This article will guide you through the process of automating your Go project releases with GoReleaser and GitHub Actions. Tags will be generated by Semantic Release. All of this will be tied together so any merges to main will automatically deploy with an auto-generated changelog, and can then be installed via a Homebrew Tap.
1. Setup GitHub repos
The first thing you need is a second Github repository to store the Homebrew Formula. The name of this must start with homebrew-
(this is explained more here). I'd recommend a format that is your current project name and just prepending homebrew-
to it.
For example, I have github.com/hadlow/gopublish
and github.com/hadlow/homebrew-gopublish
.
2. Create access tokens
Next thing is to setup is your GitHub personal access token. This is used to allow Semantic Release and GoReleaser to create tags, push file changes etc.
To create a token, go to GitHub and click on your profile picture, in the top right. Then go to Settings and then Developer Settings on the left. Then click Personal access tokens and Fine-grained tokens, then Generate new token.
Name your token something descriptive. I've gone with GH_TOKEN_GO_PUBLISH
. Now scroll down and give it access to both the main repository and the homebrew-
repository.
For the permissions, I use the following:
- Actions: Read and write
- Commit statuses: Read and write
- Contents: Read and write
- Deployments: Read and write
- Environments: Read-only
- Secrets: Read-only
- Variables: Read-only
You can now go ahead and create the token. Copy this token as we'll need it in the next step.
3. Add access token to environment
Go to your repository settings and go to Environments. Create a new environment called 'Release' and add an environment secret. Call it GH_TOKEN
and paste in your access token created in the previous step.
4. Add Semantic Release
Semantic-release will automatically generate the Git version tags for our releases. If you use conventional commits, then tags will follow semantic versioning standards. Tags will be generated whenever we commit to our main branch.
Before adding Semantic release, we'll need a GitHub Actions file. Create a file named release.yml and put it in your main repository in the .github/workflows directory. Paste in the following:
name: release
on:
push:
branches:
- main
jobs:
main:
runs-on: ubuntu-latest
environment:
name: Release
steps:
- name: Checkout
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Semantic Release
uses: cycjimmy/semantic-release-action@v4
with:
extra_plugins: |
@semantic-release/git
@semantic-release/exec
@semantic-release/changelog
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
- name: Run GoReleaser
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
HOMEBREW_GITHUB_API_TOKEN: ${{ secrets.GH_TOKEN }}
run: |
curl -sL https://git.io/goreleaser | bash
Next, in the root of your repo, create a file named .releaserc.json
and paste in:
{
"branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"@semantic-release/github",
[
"@semantic-release/git",
{
"assets": ["CHANGELOG.md"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
]
]
}
5. Configure GoReleaser
Next we want to add the GoReleaser configuration. Create a file in the root of your repo named .goreleaser.yaml
. Paste in the following:
version: 2
project_name: <YOUR PROJECT NAME>
before:
hooks:
# You may remove this if you don't use go modules.
- go mod tidy
# you may remove this if you don't need go generate
- go generate ./...
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
archives:
- format: tar.gz
name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
# use zip for windows archives
format_overrides:
- goos: windows
format: zip
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
brews:
-
name: <YOUR PROJECT NAME>
commit_author:
name: <YOUR GITHUB USERNAME>
email: <YOUR GITHUB EMAIL>
commit_msg_template: "Brew formula update for {{ .ProjectName }} version {{ .Tag }}"
directory: Formula
description: "<UPDATE DESCRIPTION>"
license: "<UPDATE LICENSE>"
install: |
bin.install "<YOUR PROJECT NAME>"
test: |
system "#{bin}/<YOUR PROJECT NAME> --version"
# Repository to push the generated files to.
repository:
owner: <YOUR GITHUB USERNAME>
name: homebrew-<YOUR PROJECT NAME>
branch: main
token: "{{ .Env.GITHUB_TOKEN }}"
Update all of the placeholders where necessary.
6. Push changes
You can now commit and push these changes. You should see something running in your Actions tab. Take a look at these steps, they should be building and releasing your Go application.
Now take a look at your repo, it should have a tag created by semantic release.
And then, in your homebrew-
repo, you should see a Formula folder.
7. Installing our application
To install, run the Homebrew tap command using the format brew tap <GITHUB USERNAME>/<GITHUB REPO>
, for this example it would look something like:
brew tap hadlow/gopublish
You should see an output that looks something like this:
==> Tapping hadlow/gopublish
Cloning into '/opt/homebrew/Library/Taps/hadlow/homebrew-gopublish'...
remote: Enumerating objects: 12, done.
remote: Counting objects: 100% (12/12), done.
remote: Compressing objects: 100% (8/8), done.
remote: Total 12 (delta 2), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (12/12), done.
Resolving deltas: 100% (2/2), done.
Tapped 2 formulae (14 files, 9.8KB).
Then we can install with:
brew install gopublish
Which will then give us:
==> Fetching hadlow/gopublish/gopublish
==> Downloading https://github.com/hadlow/gopublish/releases/download/v1.0.1/gopublish_Darwin_arm64.tar.gz
==> Downloading from https://objects.githubusercontent.com/github-production-release-asset-2e65be/92130980
################################################################################################### 100.0%
==> Installing gopublish from hadlow/gopublish
🍺 /opt/homebrew/Cellar/gopublish/1.0.1: 5 files, 1.5MB, built in 0 seconds
==> Running `brew cleanup gopublish`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
Then when I run gopublish, it works!
> gopublish
Hello, world
Wrapping up
And that's pretty much it. You can refine all of this further to suit your needs, but I wanted to keep it as simple as possible for the sake of making this guide easier to follow.
If you have any questions, I'd be glad to answer them in the comments!
Top comments (3)
Why did you decide to use semantic-release instead of goreleaser?
Semantic-release is mainly there to make automatic tagging for versions easier, then GoReleaser runs on the tag created by semantic-release
goreleaser.com/cookbooks/semantic-...
Ahh I see, completely missed that. Thank you