Skip to content

Commit a5f3d63

Browse files
committed
ci: speed up Docker builds with native arm64 runners and cargo-chef caching
- Build each platform on its native runner (arm64 on ubuntu-24.04-arm) instead of under QEMU emulation, then merge into a manifest list. This removes the ~1h arm64 emulation cost from every build. - Split dependency compilation from source compilation via cargo-chef so source-only edits reuse the cached dependency layer, and cache layers to a platform-scoped GHCR registry ref instead of the fragile type=gha cache. - Drop redundant RUSTFLAGS/CARGO_PROFILE_RELEASE_LTO overrides; fat LTO and codegen-units=1 are already defined in Cargo.toml's [profile.release]. - Only build on main, tags, and manual dispatch (not every branch push), and cancel superseded runs for the same ref via a concurrency group. - Keep non-build assets out of the build context via .dockerignore.
1 parent b1d466b commit a5f3d63

3 files changed

Lines changed: 113 additions & 23 deletions

File tree

.dockerignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,10 @@ output
44
.git
55
*.md
66
!README.md
7+
8+
# Non-build assets kept out of the build context so doc/example changes don't
9+
# invalidate the cached compile layer.
10+
examples
11+
scripts
12+
.config
13+
preview.png

.github/workflows/docker.yml

Lines changed: 90 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,104 @@ on:
55
tags:
66
- '*'
77
branches:
8-
- '**'
8+
- main
99
workflow_dispatch:
1010

11+
# Cancel superseded runs for the same ref so overlapping pushes don't queue up
12+
# hour-long builds against each other.
13+
concurrency:
14+
group: docker-${{ github.ref }}
15+
cancel-in-progress: true
16+
1117
env:
1218
REGISTRY: ghcr.io
1319
IMAGE_NAME: ${{ github.repository }}
1420

1521
jobs:
16-
docker:
17-
name: Build and push Docker image
18-
runs-on: ubuntu-latest
22+
build:
23+
name: Build (${{ matrix.platform }})
24+
runs-on: ${{ matrix.runner }}
1925
permissions:
2026
contents: read
2127
packages: write
28+
strategy:
29+
fail-fast: false
30+
matrix:
31+
include:
32+
- platform: linux/amd64
33+
runner: ubuntu-latest
34+
- platform: linux/arm64
35+
runner: ubuntu-24.04-arm
2236

2337
steps:
38+
- name: Prepare platform pair
39+
run: |
40+
platform=${{ matrix.platform }}
41+
echo "PLATFORM_PAIR=${platform//\//-}" >> "$GITHUB_ENV"
42+
2443
- uses: actions/checkout@v4
2544

45+
- name: Docker labels
46+
id: meta
47+
uses: docker/metadata-action@v5
48+
with:
49+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
50+
2651
- name: Set up Docker Buildx
2752
uses: docker/setup-buildx-action@v3
2853

29-
- name: Set up QEMU
30-
uses: docker/setup-qemu-action@v3
54+
- name: Log in to GHCR
55+
uses: docker/login-action@v3
56+
with:
57+
registry: ${{ env.REGISTRY }}
58+
username: ${{ github.actor }}
59+
password: ${{ secrets.GITHUB_TOKEN }}
60+
61+
# Each platform builds natively (arm64 on an arm64 runner instead of under
62+
# QEMU emulation) and caches to a platform-scoped registry ref.
63+
- name: Build and push by digest
64+
id: build
65+
uses: docker/build-push-action@v6
66+
with:
67+
context: .
68+
platforms: ${{ matrix.platform }}
69+
labels: ${{ steps.meta.outputs.labels }}
70+
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-${{ env.PLATFORM_PAIR }}
71+
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-${{ env.PLATFORM_PAIR }},mode=max
72+
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
73+
74+
- name: Export digest
75+
run: |
76+
mkdir -p "${{ runner.temp }}/digests"
77+
digest="${{ steps.build.outputs.digest }}"
78+
touch "${{ runner.temp }}/digests/${digest#sha256:}"
79+
80+
- name: Upload digest
81+
uses: actions/upload-artifact@v4
82+
with:
83+
name: digests-${{ env.PLATFORM_PAIR }}
84+
path: ${{ runner.temp }}/digests/*
85+
if-no-files-found: error
86+
retention-days: 1
87+
88+
merge:
89+
name: Merge and push manifest
90+
runs-on: ubuntu-latest
91+
needs: build
92+
permissions:
93+
contents: read
94+
packages: write
95+
96+
steps:
97+
- name: Download digests
98+
uses: actions/download-artifact@v4
99+
with:
100+
path: ${{ runner.temp }}/digests
101+
pattern: digests-*
102+
merge-multiple: true
103+
104+
- name: Set up Docker Buildx
105+
uses: docker/setup-buildx-action@v3
31106

32107
- name: Log in to GHCR
33108
uses: docker/login-action@v3
@@ -48,13 +123,12 @@ jobs:
48123
type=raw,value=nightly,enable=${{ github.ref == 'refs/heads/main' }}
49124
type=sha,prefix=sha-
50125
51-
- name: Build and push
52-
uses: docker/build-push-action@v6
53-
with:
54-
context: .
55-
platforms: linux/amd64,linux/arm64
56-
push: true
57-
tags: ${{ steps.meta.outputs.tags }}
58-
labels: ${{ steps.meta.outputs.labels }}
59-
cache-from: type=gha
60-
cache-to: type=gha,mode=max
126+
- name: Create manifest list and push
127+
working-directory: ${{ runner.temp }}/digests
128+
run: |
129+
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
130+
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
131+
132+
- name: Inspect image
133+
run: |
134+
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}

Dockerfile

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
1-
FROM rust:1.91-slim-bookworm AS builder
1+
# syntax=docker/dockerfile:1
22

3+
FROM rust:1.91-slim-bookworm AS chef
4+
RUN cargo install cargo-chef --locked
35
WORKDIR /app
46

5-
RUN apt-get update && apt-get install -y pkg-config libssl-dev && rm -rf /var/lib/apt/lists/*
6-
7-
COPY Cargo.toml Cargo.lock rust-toolchain.toml ./
8-
COPY crates crates
9-
COPY bifrost bifrost
7+
# Compute a dependency recipe that only changes when the dependency graph does,
8+
# so source-only edits reuse the cached dependency build below.
9+
FROM chef AS planner
10+
COPY . .
11+
RUN cargo chef prepare --recipe-path recipe.json
1012

11-
RUN RUSTFLAGS="-C codegen-units=1" CARGO_PROFILE_RELEASE_LTO=true cargo build --release --bin heimdall
13+
FROM chef AS builder
14+
RUN apt-get update && apt-get install -y pkg-config libssl-dev && rm -rf /var/lib/apt/lists/*
15+
# Build and cache dependencies first. Release optimization flags (fat LTO,
16+
# codegen-units = 1) are defined once in Cargo.toml's [profile.release].
17+
COPY --from=planner /app/recipe.json recipe.json
18+
RUN cargo chef cook --release --recipe-path recipe.json
19+
COPY . .
20+
RUN cargo build --release --bin heimdall
1221

1322
FROM debian:bookworm-slim
1423

0 commit comments

Comments
 (0)