Skip to content

Release Automation

Automate multi-architecture builds and releases with GitHub Actions and GoReleaser.

One Tag, Full Release

Push a git tag, get binaries for all platforms, container images for amd64/arm64, and an auto-generated changelog. GoReleaser handles the complexity.


GitHub Actions Workflow

name: Build and Push

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Set up QEMU
        run: |
          sudo apt-get update
          sudo apt-get install -y qemu-user-static

      - name: Login to GHCR
        uses: redhat-actions/podman-login@v1
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Generate image metadata
        id: meta
        run: |
          IMAGE=ghcr.io/${{ github.repository }}
          VERSION=${GITHUB_REF_NAME#v}
          MAJOR=$(echo $VERSION | cut -d. -f1)
          MINOR=$(echo $VERSION | cut -d. -f1-2)
          SHA=${GITHUB_SHA::7}

          TAGS="${IMAGE}:${VERSION},${IMAGE}:${MAJOR}.${MINOR},${IMAGE}:${MAJOR},${IMAGE}:${SHA}"

          echo "tags=${TAGS}" >> $GITHUB_OUTPUT
          echo "version=${VERSION}" >> $GITHUB_OUTPUT

      - name: Build multi-arch image
        id: build
        uses: redhat-actions/buildah-build@v2
        with:
          image: ghcr.io/${{ github.repository }}
          tags: ${{ steps.meta.outputs.tags }}
          platforms: linux/amd64,linux/arm64
          containerfiles: ./Dockerfile
          build-args: |
            VERSION=${{ github.ref_name }}
          labels: |
            org.opencontainers.image.source=${{ github.event.repository.html_url }}
            org.opencontainers.image.revision=${{ github.sha }}
            org.opencontainers.image.version=${{ steps.meta.outputs.version }}

      - name: Push to GHCR
        uses: redhat-actions/push-to-registry@v2
        with:
          image: ${{ steps.build.outputs.image }}
          tags: ${{ steps.build.outputs.tags }}
          registry: ghcr.io

GoReleaser Configuration

For automated binary releases, use GoReleaser:

# .goreleaser.yaml
version: 2

before:
  hooks:
    - go mod tidy

builds:
  - id: myctl
    binary: myctl
    main: ./main.go
    env:
      - CGO_ENABLED=0
    goos:
      - linux
      - darwin
      - windows
    goarch:
      - amd64
      - arm64
    ldflags:
      - -s -w
      - -X main.version={{.Version}}
      - -X main.commit={{.ShortCommit}}
      - -X main.date={{.Date}}

archives:
  - id: myctl
    formats:
      - tar.gz
      - zip
    name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
    files:
      - LICENSE
      - README.md

checksum:
  name_template: 'checksums.txt'
  algorithm: sha256

changelog:
  use: github
  sort: asc
  filters:
    exclude:
      - '^docs:'
      - '^test:'
      - '^chore:'

dockers:
  - image_templates:
      - "ghcr.io/myorg/myctl:{{ .Version }}-amd64"
    use: podman
    build_flag_templates:
      - "--platform=linux/amd64"
      - "--label=org.opencontainers.image.title={{ .ProjectName }}"
      - "--label=org.opencontainers.image.version={{ .Version }}"
    dockerfile: Dockerfile

  - image_templates:
      - "ghcr.io/myorg/myctl:{{ .Version }}-arm64"
    use: podman
    build_flag_templates:
      - "--platform=linux/arm64"
    goarch: arm64
    dockerfile: Dockerfile

podman_manifests:
  - name_template: "ghcr.io/myorg/myctl:{{ .Version }}"
    image_templates:
      - "ghcr.io/myorg/myctl:{{ .Version }}-amd64"
      - "ghcr.io/myorg/myctl:{{ .Version }}-arm64"
  - name_template: "ghcr.io/myorg/myctl:latest"
    image_templates:
      - "ghcr.io/myorg/myctl:{{ .Version }}-amd64"
      - "ghcr.io/myorg/myctl:{{ .Version }}-arm64"

Release Workflow with GoReleaser

name: Release

on:
  push:
    tags:
      - 'v*'

permissions:
  contents: write
  packages: write

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/setup-go@v5
        with:
          go-version: '1.23'

      - name: Install Podman
        run: |
          sudo apt-get update
          sudo apt-get install -y podman

      - name: Set up QEMU
        run: |
          sudo apt-get install -y qemu-user-static

      - name: Login to GHCR
        uses: redhat-actions/podman-login@v1
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Run GoReleaser
        uses: goreleaser/goreleaser-action@v6
        with:
          version: latest
          args: release --clean
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Makefile Integration

.PHONY: release snapshot

VERSION ?= $(shell git describe --tags --always --dirty)

release:
    goreleaser release --clean

snapshot:
    goreleaser release --snapshot --clean

image-build:
    podman build --build-arg VERSION=$(VERSION) -t myctl:$(VERSION) .

image-push:
    podman push ghcr.io/myorg/myctl:$(VERSION)

Automate releases: one tag push creates binaries, images, and changelogs.

Comments