Multi-platform Docker images with GoReleaser and GitHub Actions

GoReleaser v0.148.0 is out, and with it, the ability to release multi-platform Docker images, a.k.a. Docker Manifests.

In this guide we’ll explore how to use it with GitHub Actions, and how GoReleaser releases itself in this way.

PS: You will need GoReleaser version 0.152.0 or later for this guide to work properly.

An example project

I created an example project showing with all the code needed for everything to work. You can check it out here.

A simple main.go

For our example, we’ll have a very simple main.go file:

// main.go
package main

import (
	"fmt"
	"runtime"
)

var version = "dev"

func main() {
	fmt.Println("example", version, runtime.GOOS, runtime.GOARCH)
}

Our mighty Dockerfile

GoReleaser builds Docker images by copying the previously built binaries to the images (instead of building the binary inside Docker itself). This guarantees that the binary inside the image and the one you download from the releases page is the same.

Our very basic Dockerfile looks like this:

# Dockerfile
FROM alpine
COPY goreleaser-docker-manifest-actions-example \
	/usr/bin/goreleaser-docker-manifest-actions-example
ENTRYPOINT ["/usr/bin/goreleaser-docker-manifest-actions-example"]

To account for multiple platforms, we either create several dockerfiles, or use the --platform build flag. We’ll use the second approach in our example.

We can test it without GoReleaser by running:

GOOS=linux GOARCH=amd64 go build -o example .
docker buildx build -t testimage:amd64 . --platform=linux/amd64

GOOS=linux GOARCH=arm64 go build -o example .
docker buildx build -t testimage:arm64v8 . --platform=linux/arm64/v8

With that in place, let’s check our GoReleaser config file.

Our very own .goreleaser.yml

Our GoReleaser config file is very simple:

  • One item in the builds section which will build for multiple platforms;
  • Two items in the dockers section, building one image for amd64 and another for arm64;
  • One item in the new docker_manifests section, tying together the two images in a single manifest.

It looks like this:

# .goreleaser.yml
builds:
- env: [CGO_ENABLED=0]
  goos:
  - linux
  - windows
  - darwin
  goarch:
  - amd64
  - arm64
dockers:
- image_templates: ["ghcr.io/caarlos0/{{ .ProjectName }}:{{ .Version }}-amd64"]
  dockerfile: Dockerfile
  use: buildx
  build_flag_templates:
  - --platform=linux/amd64
  - --label=org.opencontainers.image.title={{ .ProjectName }}
  - --label=org.opencontainers.image.description={{ .ProjectName }}
  - --label=org.opencontainers.image.url=https://github.com/caarlos0/{{ .ProjectName }}
  - --label=org.opencontainers.image.source=https://github.com/caarlos0/{{ .ProjectName }}
  - --label=org.opencontainers.image.version={{ .Version }}
  - --label=org.opencontainers.image.created={{ time "2006-01-02T15:04:05Z07:00" }}
  - --label=org.opencontainers.image.revision={{ .FullCommit }}
  - --label=org.opencontainers.image.licenses=MIT
- image_templates: ["ghcr.io/caarlos0/{{ .ProjectName }}:{{ .Version }}-arm64v8"]
  goarch: arm64
  dockerfile: Dockerfile
  use: buildx
  build_flag_templates:
  - --platform=linux/arm64/v8
  - --label=org.opencontainers.image.title={{ .ProjectName }}
  - --label=org.opencontainers.image.description={{ .ProjectName }}
  - --label=org.opencontainers.image.url=https://github.com/caarlos0/{{ .ProjectName }}
  - --label=org.opencontainers.image.source=https://github.com/caarlos0/{{ .ProjectName }}
  - --label=org.opencontainers.image.version={{ .Version }}
  - --label=org.opencontainers.image.created={{ time "2006-01-02T15:04:05Z07:00" }}
  - --label=org.opencontainers.image.revision={{ .FullCommit }}
  - --label=org.opencontainers.image.licenses=MIT
docker_manifests:
- name_template: ghcr.io/caarlos0/{{ .ProjectName }}:{{ .Version }}
  image_templates:
  - ghcr.io/caarlos0/{{ .ProjectName }}:{{ .Version }}-amd64
  - ghcr.io/caarlos0/{{ .ProjectName }}:{{ .Version }}-arm64v8
- name_template: ghcr.io/caarlos0/{{ .ProjectName }}:latest
  image_templates:
  - ghcr.io/caarlos0/{{ .ProjectName }}:{{ .Version }}-amd64
  - ghcr.io/caarlos0/{{ .ProjectName }}:{{ .Version }}-arm64v8

You can check more options for buildsdocker and docker manifests on GoReleaser’s website.

The labels added to the images are optional, but in the specific case of ghcr.io, they allows GitHub to know which image is built from which repository and other metadata.

We can now verify this locally with:

goreleaser release --snapshot --clean

GoReleaser will use defaults for a lot of things, you can check the full config (with the defaults) in at dist/config.yaml.

GitHub Actions

Here we pretty much copy what’s already in GitHub Actions section in the GoReleaser’s website:

# .github/workflows/goreleaser.yml
name: goreleaser

on:
  push:
    tags:
      - '*'

permissions:
  contents: write

jobs:
  goreleaser:
    runs-on: ubuntu-latest
    env:
      DOCKER_CLI_EXPERIMENTAL: "enabled"
    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v1
      - name: Docker Login
        uses: docker/login-action@v1
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GH_PAT }}
      - name: Set up Go
        uses: actions/setup-go@v2
        with:
          go-version: 1.16
      - name: Run GoReleaser
        uses: goreleaser/goreleaser-action@v2
        with:
          version: latest
          args: release --clean
        env:
          GITHUB_TOKEN: ${{ secrets.GH_PAT }}

Important things to notice

  • We need to set DOCKER_CLI_EXPERIMENTAL=enabled for the docker manifest command to work;
  • We need to setup qemu and buildx in order to build Docker images in platforms other than linux/amd64 using docker buildx build;
  • We need to login into the GitHub Container Registry with a Personal Access Token (PAT), since the default GITHUB_TOKEN does not have enough permissions.

And that’s pretty much it!

Releasing

Now, we just need to push a tag, sit back, relax and watch the GoReleaser Action do everything.

In the end, you should have a release more or less like this:

GitHub Release - the changelog, assets and our multi-platform Docker image

GitHub Release - the changelog, assets and our multi-platform Docker image

You should also be able to see that the image is in fact multi-platform in the container registry:

GitHub Container Registry showing both OS/Arch combinations we provided.

GitHub Container Registry showing both OS/Arch combinations we provided.

We can now run our image:

$ docker run --rm --platform linux/amd64 \
	ghcr.io/caarlos0/goreleaser-docker-manifest-actions-example
example 1.0.2 linux amd64

We can also test the arm64 image:

$ docker run --rm --platform linux/arm64/v8 \
	ghcr.io/caarlos0/goreleaser-docker-manifest-actions-example
example 1.0.2 linux arm64

It works! 🎉

That’s it!

That’s it! I hope this is useful somehow.

Don’t forget to check out GoReleaser’s documentation for more details. Also make sure to take a look at Docker’s manifest documentation.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值