Aller au contenu

Releases Go avec GoReleaser

GoReleaser génère changelog, binaires multi-arch, archives, images Docker et charts Helm depuis un fichier de config. Tout se déclenche sur git push --tags.

Avant le release, le CI

GoReleaser intervient une fois le tag poussé. Ce qui tourne avant, sur chaque push et PR (test, lint, scan de vulnérabilités, durcissement des workflows), est détaillé dans CI GitHub Actions durcie pour un projet Go.

Flow

Flow GoReleaser

Installation

# macOS
brew install goreleaser

# Linux (binaire direct)
curl -sfL https://goreleaser.com/static/run | bash

Configuration de base

.goreleaser.yml
version: 2

before:
  hooks:
    - go mod tidy
    - go mod verify

builds:
  - id: mon-binaire
    binary: mon-binaire
    env:
      - CGO_ENABLED=0
    goos:
      - linux
      - darwin
      - windows
    goarch:
      - amd64
      - arm64
    ignore:
      - goos: windows
        goarch: arm64
    flags:
      - -trimpath
    ldflags:
      - -s -w
      - -X main.version={{.Version}}
      - -X main.commit={{.Commit}}
      - -X main.date={{.Date}}

archives:
  - formats: [tar.gz]
    name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
    format_overrides:
      - goos: windows
        formats: [zip]
    files:
      - README.md
      - LICENSE*

checksum:
  name_template: checksums.txt

release:
  draft: false
  prerelease: auto

Réduire la taille des binaires

Trois flags suffisent pour gagner ~33% :

Flag Effet
-s Supprime la table des symboles
-w Supprime les infos de debug DWARF
-trimpath Supprime les chemins locaux embarqués dans le binaire
CGO_ENABLED=0 Désactive CGO → binaire statique pur

Résultat mesuré sur gopen : 3,0 Mo → 2,0 Mo (-33%).

Compression UPX (Linux uniquement)

UPX compresse le binaire ; il se décompresse au lancement.

Gains mesurés sur la layer binaire de kubearch (ghcr.io/pixibixi/kubearch) :

Arch Sans UPX (v0.2.0) Avec UPX (v0.3.1) Gain
amd64 14,91 MB 8,34 MB -44%
arm64 6,61 MB
upx:
  - enabled: true
    ids: [mon-binaire]
    compress: best
    lzma: true
    goos: [linux]

UPX et macOS/Windows

Sur macOS, UPX casse la notarisation Apple. Sur Windows, les antivirus le signalent. À limiter à goos: [linux].

Signature des artefacts (cosign)

Cosign signe en mode keyless, sans clé à gérer. L'identité vient de l'OIDC GitHub Actions ; chaque signature est enregistrée dans le transparency log de Sigstore.

.goreleaser.yml

signs:
  - cmd: cosign
    signature: "${artifact}.sigstore.json"
    args:
      - sign-blob
      - "--bundle=${signature}"
      - "${artifact}"
      - "--yes"
    artifacts: checksum   # signe checksums.txt, qui couvre tous les binaires/archives

docker_signs:
  - cmd: cosign
    args:
      - sign
      - "--yes"
      - "${artifact}"
    artifacts: manifests   # signe les manifests multi-arch, pas les images individuelles

Pourquoi signer le checksum plutôt que chaque archive ?

checksums.txt contient les SHA256 de tous les artefacts. Signer ce fichier couvre tout : on vérifie la signature une fois, puis le SHA256 de chaque archive.

GitHub Actions — permissions requises

Deux ajouts par rapport au workflow de base :

permissions:
  contents: write
  packages: write
  id-token: write   # requis pour la signature OIDC keyless
      - name: Install cosign
        uses: sigstore/cosign-installer@v3

Vérifier les artefacts (côté utilisateur)

# 1. Vérifier la signature du checksum
cosign verify-blob \
  --certificate-identity 'https://github.com/monorg/mon-repo/.github/workflows/release.yml@refs/tags/vX.Y.Z' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
  --bundle checksums.txt.sigstore.json \
  checksums.txt

# 2. Vérifier l'intégrité des binaires
sha256sum --ignore-missing -c checksums.txt

# 3. Vérifier l'image Docker
cosign verify \
  --certificate-identity 'https://github.com/monorg/mon-repo/.github/workflows/release.yml@refs/tags/vX.Y.Z' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
  ghcr.io/monorg/mon-image:vX.Y.Z

Images OCI multi-arch (ko)

ko est un builder d'images OCI natif Go : il cross-compile le binaire et l'emballe dans une image distroless sans Dockerfile, sans Docker daemon, sans buildx. GoReleaser v2 l'intègre nativement via la clé kos:.

kos:
  - id: mon-binaire
    repositories:
      - ghcr.io/monorg/mon-image
    base_image: gcr.io/distroless/static:nonroot
    platforms:
      - linux/amd64
      - linux/arm64
    tags:
      - "{{ .Version }}"          # 1.2.3
      - "{{ .Major }}.{{ .Minor }}" # 1.2
      - "{{ .Major }}"             # 1
      - '{{ if not .Prerelease }}latest{{ end }}'
    bare: true   # ghcr.io/monorg/mon-image:1.2.3 (sans suffixe binaire)

Ko génère automatiquement un manifest multi-arch et publie des SBOMs par image. Il utilise le GITHUB_TOKEN pour s'authentifier sur ghcr.io — aucun docker login ni step buildx requis dans le workflow CI.

Pas de Dockerfile nécessaire

Pour un binaire Go pur (CGO_ENABLED=0), ko remplace entièrement le duo Dockerfile.release + docker_manifests. Pour les builds locaux de développement (avec un vrai Dockerfile multi-stage), on peut garder un Dockerfile séparé.

Visibilité GHCR

La première image publiée est privée par défaut sur GHCR, même pour un repo public. À rendre public manuellement : GitHub → Packages → mon-image → Package settings → Change visibility.

Helm chart vers OCI

GoReleaser ne gère pas nativement le push Helm, mais on l'ajoute en step post-GoReleaser dans le workflow :

- name: Push Helm chart to OCI
  run: |
    CHART_VERSION="${GITHUB_REF_NAME#v}"
    helm registry login ghcr.io \
      --username "${{ github.repository_owner }}" \
      --password "${{ secrets.GITHUB_TOKEN }}"
    helm package charts/mon-chart \
      --version "$CHART_VERSION" \
      --app-version "$CHART_VERSION"
    helm push "mon-chart-${CHART_VERSION}.tgz" oci://ghcr.io/monorg/charts

GitHub Actions

.github/workflows/release.yml
name: Release

on:
  push:
    tags:
      - 'v*'

permissions:
  contents: write
  packages: write   # push vers ghcr.io
  id-token: write   # signature cosign keyless

jobs:
  goreleaser:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0   # GoReleaser a besoin de l'historique complet pour le changelog

      - uses: actions/setup-go@v5
        with:
          go-version: stable

      - name: Install cosign
        uses: sigstore/cosign-installer@v3

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

fetch-depth: 0

Sans fetch-depth: 0, GitHub Actions fait un clone superficiel. GoReleaser ne peut pas générer le changelog car il n'a pas accès aux commits précédents.

Commandes utiles

# Valider la config sans déclencher de release
goreleaser check

# Tester localement (snapshot = pas de tag requis, pas de push)
goreleaser release --snapshot --clean

# Release manuelle (si pas de CI)
git tag -a v1.2.3 -m "Release v1.2.3"
git push origin v1.2.3

Changelog

GoReleaser génère le changelog entre deux tags. On peut filtrer et regrouper :

changelog:
  sort: asc
  filters:
    exclude:
      - '^docs:'
      - '^test:'
      - '^chore:'
  groups:
    - title: Features
      regexp: '^feat:'
      order: 0
    - title: Bug Fixes
      regexp: '^fix:'
      order: 1
    - title: Performance
      regexp: '^perf:'
      order: 2
    - title: Others
      order: 999