Using GoReleaser with GitLab: Multi-Arch Builds, Cosign, and SBOM Generation

A complete guide to building and releasing binaries for AMD64 and ARM64, securing containers, and generating SBOMs.

gitlab containers ci-cd
2025-01-26
Thomas Kooi

In modern software development, multi-architecture support is becoming a standard requirement. Whether you’re targeting ARM64 environments, like Apple’s M1/M2 chips, or traditional AMD64 infrastructure, automating multi-architecture builds ensures consistent deployment across different platforms.

At ContainerInfra, we use GoReleaser and GitLab CI to automate the creation of Docker images and binaries for both AMD64 and ARM64. This setup supports macOS and Linux systems, allowing our developers to work across multiple environments without the need for manual configuration or additional tools.

Why Multi-Architecture Builds are Important

The growing use of ARM64—driven by energy-efficient ARM-based devices and Apple’s M1/M2 systems—has made cross-platform builds essential. Here’s why supporting both AMD64 and ARM64 matters:

Simplified Developer Workflows

With native support for ARM64 systems, developers using Apple Silicon devices can build and test locally without workarounds or emulation. Native builds on ARM64 devices, such as Apple’s M1/M2, take full advantage of the hardware’s architecture, resulting in faster build times and more efficient local testing compared to emulated environments. This means developers can stay focused on coding and testing without being bogged down by issues related to compatibility or other differences between local and production environments.

Preparation for Future Hardware Shifts

Supporting multiple architectures not only ensures your software remains compatible as ARM64 adoption increases, but it also aligns with broader sustainability goals. ARM64 processors are designed to be more energy-efficient than traditional x86_64 (AMD64) processors, which means they consume less power while delivering comparable or even superior performance in certain workloads.

ARM64-based devices, such as Apple’s M1/M2 and ARM-based cloud instances, can significantly lower the energy footprint of your applications. This is particularly impactful for large-scale or edge deployments. More energy-efficient infrastructure translates to lower operational costs, especially for compute-heavy workloads.

Adapting your software to support both ARM64 and AMD64 prepares it for the growing shift toward sustainable, energy-conscious infrastructure without sacrificing compatibility or performance.

Combining GoReleaser and GitLab CI

GoReleaser is a versatile tool for automating the release of Go projects, covering tasks such as building binaries, generating changelogs, packaging, and containerizing applications. When integrated with GitLab CI, it creates a fully automated pipeline for building and releasing multi-architecture Docker images.

At ContainerInfra, we use GoReleaser in combination with GitLab CI to generate both AMD64 and ARM64 container images as part of our CI/CD process. This setup offers several advantages:

  • Consistent Build Process: GoReleaser ensures that builds for both architectures follow the same configurations and standards, reducing discrepancies between platforms.
  • Automation from Code to Release: By integrating GoReleaser with GitLab CI, we’ve eliminated the need for manual intervention during the build and release steps. Each GitLab pipeline run handles the entire process—from compiling the code to signing and publishing multi-architecture container images.
  • Simplified Cross-Platform Support: Whether it’s a cloud deployment using AMD64 servers or a performance-sensitive environment running on ARM64-based devices, we can deploy with confidence across different infrastructures.

By automating the build and release process, we improve efficiency while ensuring that releases are reproducible and comply with security and compliance standards. We’ve also added steps such as container signing with Cosign and SBOM generation to enhance transparency and strengthen our security practices.

Setting Up GoReleaser in GitLab

The first step is configuring GoReleaser in the GitLab CI pipeline. GoReleaser automates the process of building, packaging, and releasing your Go projects, but with Docker’s Buildx, it also enables multi-architecture builds.

We start by creating a basic .goreleaser.yml configuration file, which defines how GoReleaser interacts with GitLab and handles the build process.

Below is a basic GoReleaser configuration for GitLab CI:

version: 2

gitlab_urls:
  api: https://gitlab.com/api/v4/
  download: https://gitlab.com

This configuration sets up the API and download endpoints for GitLab, allowing GoReleaser to communicate with your GitLab repository.

Building Multi-Architecture Docker Images

With GoReleaser, you can build Docker images for multiple architectures, such as amd64 and arm64, using Docker’s buildx. Here’s how we configure multi-arch builds:

builds:
  - env:
      - CGO_ENABLED=0
    goos:
      - linux
      - darwin
    goarch:
      - amd64
      - arm64

In this configuration, GoReleaser builds binaries for both amd64 and arm64 architectures across linux and darwin platforms. This is important for projects that need to run across different environments, such as cloud infrastructure and Apple’s M1 architecture.

For Docker images, we define templates for each architecture:

dockers:
  - image_templates: ["registry.gitlab.com/yourgroup/{{ .ProjectName }}:{{ .Version }}-amd64"]
    use: buildx
    dockerfile: Dockerfile
    goos: linux
    goarch: amd64
    build_flag_templates:
      - "--platform=linux/amd64"
  - image_templates: ["registry.gitlab.com/yourgroup/{{ .ProjectName }}:{{ .Version }}-arm64v8"]
    use: buildx
    dockerfile: Dockerfile
    goos: linux
    goarch: arm64
    build_flag_templates:
      - "--platform=linux/arm64/v8"

These image_templates generate Docker images for both architectures. The use: buildx directive instructs Docker to use the buildx builder, which allows cross-compilation and multi-platform builds. The images are pushed to GitLab’s registry, tagged by architecture and version.

Once the images for both architectures are built, we use Docker manifests to create a single tag that references both images. This ensures that when users or services pull the image, Docker will automatically serve the correct architecture-specific image based on the pulling platform.

docker_manifests:
  - name_template: "registry.gitlab.com/yourgroup/{{ .ProjectName }}:{{ .Version }}"
    image_templates:
      - "registry.gitlab.com/yourgroup/{{ .ProjectName }}:{{ .Version }}-amd64"
      - "registry.gitlab.com/yourgroup/{{ .ProjectName }}:{{ .Version }}-arm64v8"

This makes it easier for developers and deployment tools to pull the right image without needing to manually specify the architecture.

Signing Docker Images with Cosign

Security is a key aspect of any release pipeline, and signing Docker images helps ensure their integrity. We use Cosign (part of the Sigstore project) to sign our Docker images as part of the GoReleaser workflow.

docker_signs:
  - artifacts: all
    args: ["sign", "--key=${COSIGN_KEY}", "--tlog-upload=false", "-a", "builder=gitlab-promote", "${artifact}@${digest}"]

This configuration signs all the Docker images that are built, ensuring that every image deployed can be verified for authenticity. We pass the signing key via an environment variable (COSIGN_KEY), which allows the signing process to be integrated seamlessly into the CI pipeline.

Generating SBOMs for Compliance and Security

In addition to signing images, creating a Software Bill of Materials (SBOM) is important for compliance and security auditing. SBOMs provide a detailed list of all components and dependencies included in the software, which is essential for understanding potential vulnerabilities. It can also be used for searching across a large amount of software components to identify what software dependencies and which versions or licenses are used.

We generate SBOMs using Syft as part of the GoReleaser build process:

builds:
  - id: "platform"
    env:
      - CGO_ENABLED=0
    goos:
      - linux
    goarch:
      - amd64
    hooks:
      before:
        - syft registry.gitlab.com/yourproject/{{ .ProjectName }}:{{ .Version }} -o spdx-json > ./sbom.json

This setup calls syft to generate an SBOM in the SPDX format for the Docker image. The resulting sbom.json file can be stored alongside the image artifacts, providing a detailed breakdown of dependencies and their licenses.

Automating the Process with GitLab CI

Now that we’ve configured GoReleaser, Docker Buildx, Cosign, and SBOM generation, it’s time to automate the entire process with GitLab CI. Here’s a simplified GitLab CI configuration that ties everything together:

stages:
  - build
  - release

build:
  script:
    - goreleaser release --rm-dist
  only:
    - tags

This pipeline automatically triggers the GoReleaser process when there’s a commit to the main branch. The --rm-dist flag ensures that previous builds are cleaned up, preventing contamination between releases.

The entire process—from building the binaries, creating multi-architecture Docker images, signing those images, and generating SBOMs—is fully automated, saving both time and reducing human error.

The GitLab CI Pipeline

Our GitLab CI setup revolves around two core templates:

  1. Golang CI Template (golang.yaml): This handles general Go builds using Docker and supports the necessary authentication for accessing private Git repositories and Docker registries.
  2. GoReleaser Configuration file: This template is responsible for managing multi-architecture Docker builds using GoReleaser and Docker Buildx.

Here’s a quick look at the configuration:

GitLab CI Variables

Our CI variables set-up support for GitLab’s container registry and secure access to private repositories for go dependencies:

variables:
  GOPATH: $CI_PROJECT_DIR/.go
  REGISTRY_USERNAME:  gitlab-ci-token
  REGISTRY_PASSWORD: $CI_JOB_TOKEN
  REGISTRY_NAME: $CI_REGISTRY
  GITLAB_TOKEN: $YOUR_GITLAB_ENV_TOKEN
  NETRC: machine gitlab.com login gitlab-ci-token password $GITLAB_TOKEN
  GOPRIVATE: gitlab.com

These variables allow the GitLab pipeline to authenticate with both the registry and private Go modules during the build process.

GoReleaser CI Template with Docker

In our GitLab CI pipeline, the .go:goreleaser template automates the process of building and releasing Go projects with GoReleaser. This template handles multi-architecture Docker builds, ensuring that everything from cross-compilation to image pushing happens seamlessly. Let’s break down each component of the template:

.go:goreleaser:
  extends:
    - .go:release
    - .docker
  image:
    name: goreleaser/goreleaser:v2.3.2
    entrypoint: ['']
  services:
    - name: docker:26-dind
  only:
    - tags
  script:
    - echo $NETRC > ~/.netrc
    - docker run --privileged --rm tonistiigi/binfmt:qemu-v6.2.0 --install all
    - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
    - goreleaser release --clean --skip-validate

The template extends two core configurations: .go:release and .docker.

  1. .go – manages Go-specific tasks like compiling binaries and managing Go modules.
  2. .docker – includes Docker-related setup, such as starting the Docker daemon, which is crucial for building and pushing Docker images.

For building Docker images within the pipeline, we rely on Docker-in-Docker (DinD). Additionally, in the script section, there are a few important steps.

We configure .netrc to allow access to private Go repositories during the build process. We also install QEMU to enable cross-compilation, allowing us to build Docker images for multiple architectures, like AMD64 and ARM64, using Docker Buildx.

Conclusion

Integrating GoReleaser with GitLab CI has allowed us to standardize our build and release process across different architectures, ensuring consistency and reliability in every release.

As a cloud-native MSP, this integration has not only streamlined our internal workflows but also enhanced our ability to support customers in optimizing their CI/CD pipelines. The standardized approach enables us to deliver secure, multi-architecture builds that are both reproducible, while implementing SBOMs and signatures to improve security.


8 min read
Share this post:

Related posts