Skip to content

fix(build): cross-compile Docker images instead of emulating with QEMU - #2339

Merged
adityathebe merged 1 commit into
mainfrom
claude/docker-build-performance-tbjjx3
Jul 31, 2026
Merged

fix(build): cross-compile Docker images instead of emulating with QEMU#2339
adityathebe merged 1 commit into
mainfrom
claude/docker-build-performance-tbjjx3

Conversation

@adityathebe

@adityathebe adityathebe commented Jul 31, 2026

Copy link
Copy Markdown
Member

Problem

Since a33bb6c added platforms: linux/amd64,linux/arm64, the release docker publish job builds the arm64 image under QEMU emulation on a single amd64 ubuntu-latest runner. The Dockerfile is close to a worst case for emulation — a full Rust release build (cargo build --release for diffgen), a cgo-linked Go build (-tags rustdiffgen), and go mod download all run emulated, which is typically 5–20× slower than native. The amd64-only baseline for "Build and push image" was ~20 minutes (run 1476); the first multi-arch run (30620320760) burned ~70 minutes on attempt 1 and was still building deep into attempt 2.

Fix

Cross-compile instead of emulating. All builder stages are pinned to --platform=$BUILDPLATFORM so they always run natively on the build host, and target $TARGETARCH:

  • Rust: rustup target add aarch64-unknown-linux-gnu + gcc-aarch64-linux-gnu as the linker; the staticlib is copied to target/release/libdiffgen.a, the fixed path rustdiffgen.go's cgo directive links against.
  • Go: CGO_ENABLED=1 GOARCH=$TARGETARCH CC=aarch64-linux-gnu-gcc make build-prod.
  • Native builds (BUILDARCH == TARGETARCH, e.g. local make docker) skip the cross toolchain entirely and behave as before.

Only the small final runtime stage still runs under emulation (useradd + config-db go-offline, which is network-bound).

Also included:

  • cargo fetch now depends only on Cargo.toml/Cargo.lock (with a stub lib.rs), so the dependency layer survives diffgen source changes.
  • The go builder copies only target/release from the rust stage instead of the entire multi-GB target directory.
  • The PR Build workflow now builds both release platforms with buildx (timeout 20 → 30 min), so cross-compilation breakage is caught on PRs instead of on release. It reuses the same pinned docker/setup-buildx-action SHA as flanksource/action-workflows.

Validation

  • Cross-compiled diffgen to aarch64-unknown-linux-gnu with the exact toolchain packages from the Dockerfile; verified the produced staticlib/cdylib are ELF 64-bit ARM aarch64.
  • Cross-compiled the full config-db binary with CGO_ENABLED=1 GOARCH=arm64 CC=aarch64-linux-gnu-gcc make build-prod linking the cross-built libdiffgen.a — build succeeded and the output is ELF 64-bit LSB executable, ARM aarch64, dynamically linked, interpreter /lib/ld-linux-aarch64.so.1.
  • Verified the pinned flanksource/base-image:0.7.1 digest is a multi-arch index containing amd64 and arm64.
  • Exercised the Dockerfile's case/if shell logic for amd64-native, arm64-cross, and unsupported-arch paths.
  • This PR's updated Build workflow additionally runs the full multi-arch image build end-to-end as CI.

🤖 Generated with Claude Code

https://claude.ai/code/session_016NaE7LcgYZXbx5igVaN2Rg

The multi-arch release build ran the full Rust release build, the
cgo-linked Go build, and go mod download under QEMU for linux/arm64,
making the docker publish job take hours on an amd64 runner.

Builder stages are now pinned to the build platform and cross-compile
to TARGETARCH: diffgen via rustup target aarch64-unknown-linux-gnu and
the Go binary via GOARCH/CC with the aarch64 cross toolchain. Only the
small final runtime stage runs under emulation.

Also fetch cargo dependencies from the manifests alone so the layer
survives diffgen source changes, and build both release platforms in
the PR workflow so cross-compilation breakage surfaces before release.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016NaE7LcgYZXbx5igVaN2Rg
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The container build now uses Docker Buildx for Linux amd64 and arm64. The Dockerfile cross-compiles the Rust library and Go binary with target-specific toolchains, compilers, and architecture settings.

Changes

Multi-platform container build

Layer / File(s) Summary
Buildx workflow orchestration
.github/workflows/build.yml
The workflow timeout increases to 30 minutes. Docker Buildx builds Linux amd64 and arm64 images.
Target-specific Rust compilation
build/Dockerfile
Rust stages validate targets, configure cross-compilation toolchains, build diffgen, and copy its static library into the Go build path.
Target-specific Go integration
build/Dockerfile
Go stages install target cross-compilers, set CC and GOARCH, copy the Rust artifact, and build the production binary.

Sequence Diagram(s)

sequenceDiagram
  participant BuildWorkflow
  participant DockerBuildx
  participant RustBuild
  participant GoBuild
  participant ContainerImage
  BuildWorkflow->>DockerBuildx: build linux/amd64 and linux/arm64
  DockerBuildx->>RustBuild: compile target-specific diffgen library
  RustBuild->>GoBuild: copy Rust static library
  GoBuild->>ContainerImage: build target-specific production binary
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: cross-compiling multi-architecture Docker images instead of using QEMU emulation.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/docker-build-performance-tbjjx3
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/docker-build-performance-tbjjx3

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

Benchstat

Base: a33bb6cc4bf7020e1c36d82343a414e517df85f2
Head: 5ecf18791d6de81d5147d8b207b9946add6bf487

📊 2 minor regression(s) (all within 5% threshold)

Benchmark Base Head Change p-value
RunTemplateBool/largeEnv-4 19.93µ 20.06µ +0.66% 0.002
LocationFilter/largeEnv-4 46.63µ 46.83µ +0.44% 0.041
Full benchstat output
goos: linux
goarch: amd64
pkg: github.com/flanksource/config-db/bench
cpu: AMD EPYC 7763 64-Core Processor                
                                         │ bench-base.txt │           bench-head.txt           │
                                         │     sec/op     │    sec/op     vs base              │
LocationFilter/smallEnv-4                    22.19µ ± 15%   21.26µ ± 13%       ~ (p=0.589 n=6)
LocationFilter/largeEnv-4                    46.63µ ±  1%   46.83µ ± 14%  +0.44% (p=0.041 n=6)
RunTemplateBool/smallEnv-4                   10.73µ ±  2%   10.67µ ±  1%       ~ (p=0.180 n=6)
RunTemplateBool/largeEnv-4                   19.93µ ±  0%   20.06µ ±  1%  +0.66% (p=0.002 n=6)
BenchSaveResultsSeed/N=1000-4                 4.118 ± 11%    4.155 ± 11%       ~ (p=0.818 n=6)
BenchSaveResultsUpdateUnchanged/N=1000-4      3.451 ±  2%    3.473 ±  1%       ~ (p=0.937 n=6)
BenchSaveResultsUpdateChanged/N=1000-4        7.653 ±  4%    7.579 ±  3%       ~ (p=0.699 n=6)
geomean                                      4.226m         4.207m        -0.45%

                                         │ bench-base.txt │            bench-head.txt            │
                                         │      B/op      │     B/op      vs base                │
LocationFilter/smallEnv-4                    14.97Ki ± 0%   14.97Ki ± 0%       ~ (p=1.000 n=6) ¹
LocationFilter/largeEnv-4                    20.38Ki ± 0%   20.38Ki ± 0%  +0.00% (p=0.015 n=6)
RunTemplateBool/smallEnv-4                   8.359Ki ± 0%   8.359Ki ± 0%       ~ (p=1.000 n=6) ¹
RunTemplateBool/largeEnv-4                   10.16Ki ± 0%   10.16Ki ± 0%       ~ (p=0.061 n=6)
BenchSaveResultsSeed/N=1000-4                1.277Gi ± 0%   1.277Gi ± 0%       ~ (p=0.180 n=6)
BenchSaveResultsUpdateUnchanged/N=1000-4     32.20Mi ± 0%   32.20Mi ± 0%       ~ (p=0.485 n=6)
BenchSaveResultsUpdateChanged/N=1000-4       796.5Mi ± 1%   797.1Mi ± 1%       ~ (p=0.180 n=6)
geomean                                      990.2Ki        990.3Ki       +0.01%
¹ all samples are equal

                                         │ bench-base.txt │            bench-head.txt            │
                                         │   allocs/op    │  allocs/op    vs base                │
LocationFilter/smallEnv-4                     278.0 ±  0%    278.0 ±  0%       ~ (p=1.000 n=6) ¹
LocationFilter/largeEnv-4                     522.0 ±  0%    522.0 ±  0%       ~ (p=1.000 n=6) ¹
RunTemplateBool/smallEnv-4                    216.0 ±  0%    216.0 ±  0%       ~ (p=1.000 n=6) ¹
RunTemplateBool/largeEnv-4                    297.0 ±  0%    297.0 ±  0%       ~ (p=1.000 n=6) ¹
BenchSaveResultsSeed/N=1000-4                442.6k ±  0%   442.7k ±  0%       ~ (p=0.723 n=6)
BenchSaveResultsUpdateUnchanged/N=1000-4     396.9k ±  0%   396.9k ±  0%       ~ (p=0.310 n=6)
BenchSaveResultsUpdateChanged/N=1000-4       984.1k ± 13%   984.1k ± 13%       ~ (p=0.937 n=6)
geomean                                      7.703k         7.703k        +0.00%
¹ all samples are equal

                                         │ bench-base.txt │           bench-head.txt           │
                                         │      MB/s      │    MB/s     vs base                │
BenchSaveResultsSeed/N=1000-4                0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
BenchSaveResultsUpdateUnchanged/N=1000-4     0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
BenchSaveResultsUpdateChanged/N=1000-4       0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                 ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

Copy link
Copy Markdown
Member Author

CI note: test-clickhouse is red on this PR, but the failure is pre-existing — the identical failure occurred on #2338 (job) before this branch existed. The job fails during helm install in the kind cluster: no matches for kind "ScrapePlugin" in version "configs.flanksource.com/v1" ... ensure CRDs are installed first — the chart's ScrapePlugin resources are applied before that CRD is registered. This PR only touches build/Dockerfile and the Build workflow, so I'm not fixing it here; it needs a chart/CRD-ordering fix (e.g. moving the ScrapePlugin CRD into the chart's crds/ directory or gating the plugin resources).


Generated by Claude Code

@github-actions

Copy link
Copy Markdown

Gavel results

Gavel exited with code .

View full results

@adityathebe
adityathebe merged commit eb696cb into main Jul 31, 2026
15 of 18 checks passed
@adityathebe
adityathebe deleted the claude/docker-build-performance-tbjjx3 branch July 31, 2026 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants