Skip to content

ci(docker): pin the cross-compile multi-arch image build to the wg1 s… #50

ci(docker): pin the cross-compile multi-arch image build to the wg1 s…

ci(docker): pin the cross-compile multi-arch image build to the wg1 s… #50

Workflow file for this run

name: docker
# Build (and, except on pull requests, publish) the multi-architecture Graphus container image —
# linux/amd64 + linux/arm64 — to the GitHub Container Registry.
#
# CROSS-COMPILED on a native x86_64 self-hosted runner: the Dockerfile builder is pinned to
# `--platform=$BUILDPLATFORM`, so the heavy Rust compile always runs natively on the x86 runner and
# cross-compiles the arm64 (Raspberry Pi 5 / Apple Silicon) binary — no emulated compile on the slow
# Pi. QEMU is set up only for the tiny arm64 runtime layers (a handful of `apt-get`s). One `buildx`
# invocation yields the full multi-arch manifest.
#
# * Pull requests → amd64-only build (no push) on a GitHub-hosted runner (public-repo security:
# never run fork-PR code on a self-hosted machine).
# * Pushes to main → build + push the `:main` multi-arch manifest (dev image; the release image is
# handled by dockerhub.yml).
# * Tags `v*` / dispatch → build + push a full `:vX.Y.Z` / `:X.Y` / `:latest` manifest.
#
# NOTE: this workflow only BUILDS the image (the Dockerfile compiles the binary); it never runs the
# cargo test suite. The tests live in ci.yml and run on pull requests / manual dispatch only.
on:
push:
branches: [main]
tags: ["v*"]
pull_request:
paths:
- "Dockerfile"
- ".dockerignore"
- "docker/**"
- "docker-compose.yml"
- ".github/workflows/docker.yml"
- "crates/**"
- "Cargo.toml"
- "Cargo.lock"
workflow_dispatch:
concurrency:
group: docker-${{ github.ref }}
# Only cancel superseded pull-request builds (they never push). A run on main, a tag, or a dispatch
# publishes a multi-arch manifest, and a half-pushed manifest is worse than waiting.
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
IMAGE_NAME: ghcr.io/${{ github.repository }}
PLATFORMS: linux/amd64,linux/arm64
jobs:
build:
# Cross-compile on a native x86_64 self-hosted runner for trusted events; PRs stay on a
# GitHub-hosted runner so untrusted fork code never runs on the self-hosted machines.
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || fromJSON('["self-hosted", "Linux", "X64", "wg1"]') }}
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v7
# QEMU is needed only for the arm64 RUNTIME layers (the runtime stage's `apt-get`); the Rust
# compile is cross-compiled natively on x86 by the `--platform=$BUILDPLATFORM` builder stage.
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
# Only publish for pushes/tags/dispatch; PRs build without pushing.
- name: Decide whether to push
id: gate
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
echo "push=false" >> "$GITHUB_OUTPUT"
else
echo "push=true" >> "$GITHUB_OUTPUT"
fi
- name: Log in to GHCR
if: steps.gate.outputs.push == 'true'
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# metadata-action lowercases the image reference automatically (github.repository preserves the
# owner's case), so the derived tags are always registry-valid.
- name: Derive image tags and labels
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
- name: Build and push (multi-arch, cross-compiled)
uses: docker/build-push-action@v7
with:
context: .
file: ./Dockerfile
# PRs build the native amd64 image only (fast); pushes/tags cross-compile the full multi-arch
# manifest so arm64 is validated before it can ship.
platforms: ${{ github.event_name == 'pull_request' && 'linux/amd64' || env.PLATFORMS }}
push: ${{ steps.gate.outputs.push }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
provenance: true
sbom: true
cache-from: type=gha
cache-to: type=gha,mode=max