У цій статті ми разом налаштуємо мультиплатформений CI для Go-проєктів на Linux, який компілює під amd64 та arm64 за допомогою Docker Buildx і QEMU, автоматично ганяє тести та публікує multi-arch образи. Це означає, що ваш бінарник або контейнер працюватимуть і на сервері x86_64, і на ARM-пристроях на кшталт Raspberry Pi 🚀

Що саме ми побудуємо

Пайплайн включатиме:

  • автотести для Go в Linux;
  • емуляцію інших архітектур через QEMU під час збірки й тестування;
  • multi-arch Docker-образ через Buildx та маніфест-список;
  • зручну автоматизацію задач через GitHub Actions.

Ми працюємо з інструментами Docker на Linux, QEMU та BuildKit. Для коду — Go 1.22+. Такий підхід органічно вписується у сучасну розробку на Linux і масштабування CI.

Передумови

  • Оновлений Linux-дистрибутив із правами sudo;
  • Встановлені Docker і Git;
  • Обліковий запис у Docker Hub або GHCR для публікації образів;
  • Репозиторій з Go-кодом (модуль). Це — класична Go в Linux історія.
# Ubuntu/Debian: базові речі
sudo apt update -y
sudo apt install -y git curl ca-certificates qemu-user-static

# Встановіть Docker, якщо ще не встановлено (офіційний скрипт)
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker "$USER"  # перелогіньтеся після цього

docker --version
qemu-aarch64 --version

Основний How-to

1) Багатостадійний Dockerfile з ціллю для тестів

Створимо Dockerfile, де є окрема стадія test: при її збірці тести запускаються усередині контейнера для вказаної архітектури. Якщо впадуть — збірка зупиниться.

cat > Dockerfile <<'EOF'
# syntax=docker/dockerfile:1.6

ARG GO_VERSION=1.22

FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-bookworm AS base
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .

# Тестова стадія: запускає `go test` під цілевою архітектурою
FROM base AS test
ARG TARGETOS TARGETARCH
ENV GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    go test ./... -count=1

# Прод-стадія: будуємо статичний бінарник і пакуємо в мінімальний образ
FROM base AS build
ARG TARGETOS TARGETARCH
ENV GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    go build -trimpath -ldflags="-s -w" -o /out/app ./cmd/app

FROM gcr.io/distroless/base-debian12 AS runtime
COPY --from=build /out/app /usr/local/bin/app
ENTRYPOINT ["/usr/local/bin/app"]
EOF

2) Увімкніть QEMU та Buildx локально

QEMU дає можливість виконувати ARM-бінарники на x86_64 і навпаки. Buildx — інструмент Docker для мультиплатформених збірок.

# Установити binfmt з підтримкою кількох архітектур
sudo docker run --privileged --rm tonistiigi/binfmt --install all

# Створити та активувати builder з підтримкою BuildKit
docker buildx create --name multi --use

docker buildx inspect --bootstrap

3) Прогін тестів і multi-arch збірка локально

Спочатку ганяємо тести в емульованому середовищі, далі — збірка і публікація.

# Тести для amd64
docker buildx build --platform linux/amd64 --target test --progress=plain .

# Тести для arm64 (через QEMU)
docker buildx build --platform linux/arm64 --target test --progress=plain .

# Якщо все зелене — збираємо multi-arch образ і пушимо
export IMAGE=youruser/yourapp:latest

docker login -u "$DOCKERHUB_USER" -p "$DOCKERHUB_TOKEN"

docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t "$IMAGE" \
  --push \
  .

docker buildx imagetools inspect "$IMAGE"

GitHub Actions: мультиплатформений CI

Далі — повноцінний CI. Будуть два завдання: тест-матриця (amd64/arm64) і релізна збірка з публікацією у реєстр. У Secrets додайте DOCKERHUB_USERNAME і DOCKERHUB_TOKEN. Це — чиста автоматизація задач без ручних дій 🐳

mkdir -p .github/workflows
cat > .github/workflows/ci.yml <<'EOF'
name: go-multiarch-ci

on:
  push:
    branches: [ main ]
  pull_request:

jobs:
  test:
    name: Test (${{ matrix.platform }})
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        platform: [linux/amd64, linux/arm64]
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.22'

      - name: Setup QEMU
        uses: docker/setup-qemu-action@v3

      - name: Setup Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Go cache
        uses: actions/cache@v4
        with:
          path: |
            ~/.cache/go-build
            ~/go/pkg/mod
          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
          restore-keys: ${{ runner.os }}-go-

      - name: Run tests in container (${ { matrix.platform } })
        run: |
          docker buildx build \
            --platform ${{ matrix.platform }} \
            --target test \
            --progress=plain \
            .

  build-and-push:
    name: Build and Push (multi-arch)
    needs: [test]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup QEMU
        uses: docker/setup-qemu-action@v3

      - name: Setup Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push multi-arch image
        uses: docker/build-push-action@v6
        with:
            context: .
            platforms: linux/amd64,linux/arm64
            push: true
            tags: youruser/yourapp:latest
            cache-from: type=registry,ref=youruser/yourapp:cache
            cache-to: type=registry,ref=youruser/yourapp:cache,mode=max
EOF

Альтернативні способи

  • Goreleaser: автоматизує збірку бінарників для різних GOOS/GOARCH, пакує архіви та релізи. Можна поєднати з Docker Buildx.
  • GitLab CI/CircleCI: аналогічні пайплайни з runner'ами та QEMU.
  • Native ARM runner: якщо є arm64 runner, тести й збірка будуть значно швидші, ніж під QEMU.

GUI-спосіб (де доречно)

  • GitHub: відкрийте вкладку Actions у вашому репозиторії, оберіть Go Workflow, збережіть. Потім відредагуйте YAML, додавши кроки Buildx/QEMU (як вище).
  • Docker Desktop for Linux: у налаштуваннях увімкніть BuildKit і перевірте, що підтримується buildx. Далі використовуйте інтегрований термінал для команд.

FAQ

Чому тести на arm64 повільні?

QEMU — це емуляція, вона повільніша. Варіанти: скоротити набір тестів на неосновних архітектурах, кешувати залежності, або додати реальний arm64 runner.

Отримую "exec format error"

Зазвичай це означає, що бінарник/образ не під ту архітектуру. Перевіряйте параметр --platform у buildx і manifest образу через docker buildx imagetools inspect.

CGO і нативні C-бібліотеки

Для cross-compilation з CGO потрібні крос-компілятори й заголовки. Налаштуйте CC і CXX (наприклад, aarch64-linux-gnu-gcc) у стадії збірки. Інакше встановіть CGO_ENABLED=0.

Приватні Go-модулі

Додайте GOPRIVATE і налаштуйте git credentials у CI. Наприклад, через git config url..insteadof або ssh-ключ у Secrets.

Як прискорити збірку?

Увімкніть BuildKit-кеш (cache-from/cache-to), кешуйте go build і модулі. Розбийте довгі RUN-команди та фіксуйте версії залежностей.

Проблеми з правами Docker у CI

На GitHub Actions використовуйте офіційні дії docker/*, вони вже працюють із правами. Локально — додайте себе в групу docker та перелогіньтесь.

Як тегувати релізи?

Використовуйте Git-теги і змінну GITHUB_REF для формування тегів образів: latest, vX.Y.Z. Додайте кроки у build-and-push.

Порада від Kernelka

Не запускайте інтеграційні тести всюди: робіть швидкі юніт-тести на всіх платформах, а важкі — лише на основній. Так ваш CI буде прудким і недорогим 😉

Підсумок

  • QEMU + Docker Buildx забезпечують мультиплатформені тести та збірку.
  • Окрема тест-стадія в Dockerfile зупиняє збірку при падінні тестів.
  • GitHub Actions легко автоматизує тести й публікацію multi-arch образів.
  • Кеш і мінімальні базові образи прискорюють CI і зменшують витрати.
  • Плануйте матрицю платформ з урахуванням продуктивності QEMU.