Skip to content

Commit caa2ce5

Browse files
authored
ci: publish arm64 alongside amd64 (#32)
* ci: publish arm64 alongside amd64 The Dockerfile already handles both architectures -- its TARGETARCH case maps arm64 to aarch64-unknown-linux-musl, and that path was the one verified locally during the 0.4.0 work. What was missing was a workflow that asked for it, so every release so far has been amd64 only. Build each architecture on a runner of that architecture rather than cross-building under QEMU. Emulating an aarch64 Rust compile, ring's C and assembly included, would turn a ~2 minute release into something far longer; ubuntu-24.04-arm is generally available and free for public repositories, so there is no reason to pay that cost. Both jobs run in parallel, so wall-clock time stays roughly where it was. Per-architecture builds push by digest only. A merge job then combines those digests into one manifest list, so `docker pull` resolves the right image per host from a single tag. Two details worth recording: Annotations are requested at index level via DOCKER_METADATA_ANNOTATIONS_LEVELS. The default is `manifest:`, which is the wrong level for a manifest list and would have produced malformed `index:manifest:...` arguments had they been re-prefixed by hand. The merge job ends by inspecting the pushed tag and failing if either architecture is absent. These jobs only run on release, so they cannot be exercised by pull request CI; that check means a silently single-arch manifest fails the release instead of shipping. * Set the crate version to 0.4.1 and document arm64 support The crate version had sat at 0.1.0 across all six releases, so anything reading it -- cargo tooling, an SBOM, a future `--version` flag -- gave a number unrelated to the artifact. Point it at the tag this will ship as. Cargo.lock has to move with it: CI and the Docker build both pass --locked, which rejects a lockfile that disagrees with the manifest. That would have failed the release rather than the pull request, since the image is only built on release. README now describes the images as multi-arch and drops the note telling arm64 users to emulate or build their own, with the cutover version called out so it stays accurate for anyone still on 0.4.0. Only the root package moved. The two internal libraries stay at 0.1.0 -- they are path dependencies and are never published on their own.
1 parent 1e5542b commit caa2ce5

4 files changed

Lines changed: 112 additions & 15 deletions

File tree

.github/workflows/rust.yml

Lines changed: 107 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ on:
88

99
env:
1010
CARGO_TERM_COLOR: always
11+
REGISTRY_IMAGE: nihaopaul/forward-auth-rust
1112

1213
jobs:
1314
build:
@@ -25,34 +26,130 @@ jobs:
2526
- name: test
2627
run: cargo test --workspace
2728

29+
# One job per architecture, each on a runner of that architecture, so the
30+
# Rust tree is never cross-compiled under emulation. These push by digest
31+
# only; tags are applied by the merge job below.
2832
docker:
29-
name: push docker image to hub
33+
name: build image (${{ matrix.arch }})
3034
if: github.event_name == 'release'
3135
needs: build
3236
permissions:
3337
contents: read
34-
runs-on: ubuntu-latest
38+
runs-on: ${{ matrix.runner }}
39+
strategy:
40+
fail-fast: false
41+
matrix:
42+
include:
43+
- arch: amd64
44+
platform: linux/amd64
45+
runner: ubuntu-latest
46+
- arch: arm64
47+
platform: linux/arm64
48+
runner: ubuntu-24.04-arm
3549
steps:
3650
- name: check repository
3751
uses: actions/checkout@v7
3852

53+
- name: Docker meta
54+
id: meta
55+
uses: docker/metadata-action@v6
56+
with:
57+
images: ${{ env.REGISTRY_IMAGE }}
58+
59+
- name: set up buildx
60+
uses: docker/setup-buildx-action@v4
61+
3962
- name: login to docker registry
4063
uses: docker/login-action@v4
4164
with:
4265
username: ${{secrets.DOCKERHUB_USERNAME}}
4366
password: ${{secrets.DOCKERHUB_TOKEN}}
4467

68+
- name: build and push by digest
69+
id: build
70+
uses: docker/build-push-action@v7
71+
with:
72+
context: .
73+
file: ./Dockerfile
74+
platforms: ${{ matrix.platform }}
75+
labels: ${{ steps.meta.outputs.labels }}
76+
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
77+
78+
- name: export digest
79+
run: |
80+
mkdir -p /tmp/digests
81+
digest="${{ steps.build.outputs.digest }}"
82+
touch "/tmp/digests/${digest#sha256:}"
83+
84+
- name: upload digest
85+
uses: actions/upload-artifact@v7
86+
with:
87+
name: digests-${{ matrix.arch }}
88+
path: /tmp/digests/*
89+
if-no-files-found: error
90+
retention-days: 1
91+
92+
# Collects the per-architecture digests into one multi-arch tag, so that
93+
# `docker pull nihaopaul/forward-auth-rust:<tag>` resolves per host.
94+
docker-merge:
95+
name: push multi-arch manifest
96+
if: github.event_name == 'release'
97+
needs: docker
98+
permissions:
99+
contents: read
100+
runs-on: ubuntu-latest
101+
steps:
102+
- name: download digests
103+
uses: actions/download-artifact@v8
104+
with:
105+
path: /tmp/digests
106+
pattern: digests-*
107+
merge-multiple: true
108+
109+
- name: set up buildx
110+
uses: docker/setup-buildx-action@v4
111+
45112
- name: Docker meta
46113
id: meta
47114
uses: docker/metadata-action@v6
48115
with:
49-
images: nihaopaul/forward-auth-rust
116+
images: ${{ env.REGISTRY_IMAGE }}
117+
env:
118+
# Annotations default to the `manifest:` level, which is wrong for a
119+
# manifest list. Emit them pre-prefixed for the index instead.
120+
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
50121

51-
- name: build and push docker image to registry
52-
uses: docker/build-push-action@v7
122+
- name: login to docker registry
123+
uses: docker/login-action@v4
53124
with:
54-
context: .
55-
file: ./Dockerfile
56-
push: true
57-
tags: ${{ steps.meta.outputs.tags }}
58-
annotations: ${{ steps.meta.outputs.annotations }}
125+
username: ${{secrets.DOCKERHUB_USERNAME}}
126+
password: ${{secrets.DOCKERHUB_TOKEN}}
127+
128+
- name: create manifest list and push
129+
shell: bash
130+
working-directory: /tmp/digests
131+
run: |
132+
set -euo pipefail
133+
args=()
134+
while IFS= read -r a; do
135+
[ -n "$a" ] && args+=("--annotation" "$a")
136+
done <<< "${DOCKER_METADATA_OUTPUT_ANNOTATIONS:-}"
137+
while IFS= read -r t; do
138+
[ -n "$t" ] && args+=("-t" "$t")
139+
done < <(jq -r '.tags[]' <<< "$DOCKER_METADATA_OUTPUT_JSON")
140+
for d in *; do
141+
args+=("${REGISTRY_IMAGE}@sha256:${d}")
142+
done
143+
docker buildx imagetools create "${args[@]}"
144+
145+
- name: verify both architectures are published
146+
shell: bash
147+
run: |
148+
set -euo pipefail
149+
tag='${{ steps.meta.outputs.version }}'
150+
docker buildx imagetools inspect "${REGISTRY_IMAGE}:${tag}"
151+
for arch in amd64 arm64; do
152+
docker buildx imagetools inspect --raw "${REGISTRY_IMAGE}:${tag}" \
153+
| jq -e --arg a "$arch" '[.manifests[].platform.architecture] | index($a)' >/dev/null \
154+
|| { echo "::error::$arch is missing from the published manifest"; exit 1; }
155+
done

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ members = ["cloudflare-authenticator", "cloudflare-dynamic-config"]
33

44
[package]
55
name = "traefik-forward-auth-rust"
6-
version = "0.1.0"
6+
version = "0.4.1"
77
edition = "2021"
88
authors = ["Paul Adams <nihaopaul@gmail.com>"]
99

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Nothing is stored on disk and no state is kept between requests.
2525
```yaml
2626
services:
2727
forward-auth-rust:
28-
image: nihaopaul/forward-auth-rust:0.4.0
28+
image: nihaopaul/forward-auth-rust:0.4.1
2929
restart: unless-stopped
3030
environment:
3131
CF_DOMAIN: https://yourteam.cloudflareaccess.com
@@ -80,9 +80,9 @@ The service listens on `0.0.0.0` and exposes a single endpoint, `GET /auth`.
8080

8181
**Protect the right things.** This gates on "is this a valid login for one of my Cloudflare apps" — it does not evaluate per-application policies. Two apps behind the same instance can accept each other's tokens, so think carefully before putting it in front of a writable dashboard or API.
8282

83-
**Only `linux/amd64` images are published.** On arm64 hosts you'll need emulation, or build the image yourself.
83+
**Runs on x86 and ARM.** Images are published for `linux/amd64` and `linux/arm64` under a single tag, so `docker pull` fetches the right one for your host — no `platform:` override needed on a Raspberry Pi, an Ampere VPS, or Apple silicon. (Releases before 0.4.1 were amd64 only.)
8484

85-
**Pin a version.** The example uses `:0.4.0` rather than `:latest` so an unattended `docker compose pull` can't change what you're running.
85+
**Pin a version.** The example uses `:0.4.1` rather than `:latest` so an unattended `docker compose pull` can't change what you're running.
8686

8787
## Building from source
8888

0 commit comments

Comments
 (0)