From 8b007ad1ab4d8b66bea1068892bd4b9ded5c1319 Mon Sep 17 00:00:00 2001 From: Serge Gatezh <2880401+gatezh@users.noreply.github.com> Date: Thu, 5 Mar 2026 13:52:34 -0700 Subject: [PATCH 1/7] feat: add RTK (Rust Token Killer) to ralphex-fe image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build RTK from source via multi-stage build (rust:alpine) to produce a native musl binary — the pre-built arm64 release is gnu-linked and incompatible with Alpine's musl libc. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/build-ralphex-fe.yml | 2 +- ralphex-fe/Dockerfile | 14 ++++++++++++++ ralphex-fe/README.md | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-ralphex-fe.yml b/.github/workflows/build-ralphex-fe.yml index e5f5902..a6c9b16 100644 --- a/.github/workflows/build-ralphex-fe.yml +++ b/.github/workflows/build-ralphex-fe.yml @@ -56,7 +56,7 @@ jobs: context: ralphex-fe dockerfile: ralphex-fe/Dockerfile version-tag: ${{ needs.prepare.outputs.version-tag }} - verify-command: 'bun --version && hugo version' + verify-command: 'bun --version && hugo version && rtk --version' extra-verify-script: | chromium-browser --no-sandbox --version && \ test "$PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH" = "/usr/bin/chromium-browser" && \ diff --git a/ralphex-fe/Dockerfile b/ralphex-fe/Dockerfile index b501f95..bdfee5e 100644 --- a/ralphex-fe/Dockerfile +++ b/ralphex-fe/Dockerfile @@ -1,6 +1,16 @@ ARG BUN_VERSION=1.3.9 ARG HUGO_VERSION=0.156.0 ARG RALPHEX_VERSION=0.20.0 +ARG RTK_VERSION=0.26.0 + +# Build RTK from source for Alpine/musl compatibility +# The pre-built arm64 binary is gnu-linked and incompatible with Alpine's musl libc. +# For amd64, a pre-built musl binary exists but building from source keeps both arches consistent. +FROM rust:alpine AS rtk-builder +ARG RTK_VERSION +RUN apk add --no-cache musl-dev +RUN cargo install --git https://github.com/rtk-ai/rtk.git --tag "v${RTK_VERSION}" --locked && \ + rtk --version # Extend the ralphex base image which provides the ralphex binary at /srv/ralphex # along with Claude Code, Codex, Node.js, git, ripgrep, and other development tools @@ -62,6 +72,10 @@ RUN set -eux; \ rm hugo.tar.gz hugo_checksums.txt; \ hugo version +# Copy RTK binary built from source in the builder stage +COPY --from=rtk-builder /usr/local/cargo/bin/rtk /usr/local/bin/rtk +RUN rtk --version + # OCI labels for container metadata and GitHub Package integration LABEL org.opencontainers.image.source="https://github.com/gatezh/devcontainer-images" LABEL org.opencontainers.image.description="Ralphex-fe: Frontend development image with Bun, Hugo Extended, Chromium, and Ralphex" diff --git a/ralphex-fe/README.md b/ralphex-fe/README.md index 9be6fb6..5f1d967 100644 --- a/ralphex-fe/README.md +++ b/ralphex-fe/README.md @@ -11,6 +11,7 @@ This is a standalone Docker image, not a devcontainer configuration. It can be u - **Bun 1.3.9** - Fast JavaScript runtime, bundler, and package manager - **Hugo Extended 0.155.3** - Full-featured static site generator with extended capabilities - **Chromium** - System browser for headless end-to-end testing +- **RTK 0.26.0** - [Rust Token Killer](https://www.rtk-ai.app/) - CLI proxy to minimize LLM token consumption - **Git** - Version control (included from base) - **Zsh** - Modern shell (included from base) From 1117e3484b1735079c0a319cf605fe4a71823363 Mon Sep 17 00:00:00 2001 From: Serge Gatezh <2880401+gatezh@users.noreply.github.com> Date: Thu, 5 Mar 2026 14:50:45 -0700 Subject: [PATCH 2/7] perf: cross-compile RTK to avoid QEMU-emulated Rust builds Replace QEMU-emulated arm64 Rust compilation (~23+ min) with native amd64 cross-compilation (~3-4 min) using aarch64-linux-gnu-gcc linker. Switch builder from rust:alpine to rust:slim-bookworm for cross-compilation toolchain availability. Co-Authored-By: Claude Opus 4.6 --- ralphex-fe/Dockerfile | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/ralphex-fe/Dockerfile b/ralphex-fe/Dockerfile index bdfee5e..a1efd04 100644 --- a/ralphex-fe/Dockerfile +++ b/ralphex-fe/Dockerfile @@ -3,14 +3,25 @@ ARG HUGO_VERSION=0.156.0 ARG RALPHEX_VERSION=0.20.0 ARG RTK_VERSION=0.26.0 -# Build RTK from source for Alpine/musl compatibility -# The pre-built arm64 binary is gnu-linked and incompatible with Alpine's musl libc. -# For amd64, a pre-built musl binary exists but building from source keeps both arches consistent. -FROM rust:alpine AS rtk-builder +# Cross-compile RTK on native amd64 to avoid slow QEMU-emulated Rust builds. +# --platform=linux/amd64 forces native execution; TARGETARCH selects the cross-compilation target. +# Using Debian for easy access to aarch64 cross-compilation toolchain. +FROM --platform=linux/amd64 rust:slim-bookworm AS rtk-builder ARG RTK_VERSION -RUN apk add --no-cache musl-dev -RUN cargo install --git https://github.com/rtk-ai/rtk.git --tag "v${RTK_VERSION}" --locked && \ - rtk --version +ARG TARGETARCH +RUN apt-get update && apt-get install -y --no-install-recommends \ + musl-tools gcc-aarch64-linux-gnu \ + && rm -rf /var/lib/apt/lists/* +RUN rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl +RUN if [ "$TARGETARCH" = "arm64" ]; then \ + export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-gnu-gcc && \ + export CC_aarch64_unknown_linux_musl=aarch64-linux-gnu-gcc && \ + cargo install --git https://github.com/rtk-ai/rtk.git --tag "v${RTK_VERSION}" --locked \ + --target aarch64-unknown-linux-musl; \ + else \ + cargo install --git https://github.com/rtk-ai/rtk.git --tag "v${RTK_VERSION}" --locked \ + --target x86_64-unknown-linux-musl; \ + fi # Extend the ralphex base image which provides the ralphex binary at /srv/ralphex # along with Claude Code, Codex, Node.js, git, ripgrep, and other development tools From 065e6a94c20cde9c355411dac9e176103426ddfc Mon Sep 17 00:00:00 2001 From: Serge Gatezh <2880401+gatezh@users.noreply.github.com> Date: Thu, 5 Mar 2026 15:21:49 -0700 Subject: [PATCH 3/7] fix(ci): ignore DL3029 for intentional --platform cross-compilation The --platform=linux/amd64 on FROM is used deliberately to cross-compile RTK natively instead of under QEMU emulation. Co-Authored-By: Claude Opus 4.6 --- .hadolint.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.hadolint.yaml b/.hadolint.yaml index c78cc9a..a17c0e6 100644 --- a/.hadolint.yaml +++ b/.hadolint.yaml @@ -11,6 +11,10 @@ ignored: - DL3047 # avoid using wget; use curl - DL4001 # either use wget or curl, not both + # --platform on FROM is intentional for cross-compilation (e.g. building + # arm64 musl binaries natively on amd64 to avoid slow QEMU emulation). + - DL3029 # do not use --platform with FROM + # pipefail SHELL not required — only one pipe exists (bun install) # and curl -f already fails on HTTP errors. - DL4006 # set SHELL option -o pipefail before RUN with pipe From 877ed7abf98d5751c83e8f4472faae8ae5b44f08 Mon Sep 17 00:00:00 2001 From: Serge Gatezh <2880401+gatezh@users.noreply.github.com> Date: Thu, 5 Mar 2026 15:35:24 -0700 Subject: [PATCH 4/7] fix: add cross-compilation headers for ring crate arm64 build Install libc6-dev-arm64-cross and set --sysroot so the aarch64 cross-compiler finds the correct C headers (fixes bits/libc-header-start.h not found). Co-Authored-By: Claude Opus 4.6 --- ralphex-fe/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ralphex-fe/Dockerfile b/ralphex-fe/Dockerfile index a1efd04..292b1cb 100644 --- a/ralphex-fe/Dockerfile +++ b/ralphex-fe/Dockerfile @@ -10,12 +10,13 @@ FROM --platform=linux/amd64 rust:slim-bookworm AS rtk-builder ARG RTK_VERSION ARG TARGETARCH RUN apt-get update && apt-get install -y --no-install-recommends \ - musl-tools gcc-aarch64-linux-gnu \ + musl-tools gcc-aarch64-linux-gnu libc6-dev-arm64-cross \ && rm -rf /var/lib/apt/lists/* RUN rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl RUN if [ "$TARGETARCH" = "arm64" ]; then \ export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-gnu-gcc && \ export CC_aarch64_unknown_linux_musl=aarch64-linux-gnu-gcc && \ + export CFLAGS_aarch64_unknown_linux_musl="--sysroot=/usr/aarch64-linux-gnu" && \ cargo install --git https://github.com/rtk-ai/rtk.git --tag "v${RTK_VERSION}" --locked \ --target aarch64-unknown-linux-musl; \ else \ From 7d3bc344a5ee877a2717b338c40c1c7284f58de1 Mon Sep 17 00:00:00 2001 From: Serge Gatezh <2880401+gatezh@users.noreply.github.com> Date: Thu, 5 Mar 2026 15:47:34 -0700 Subject: [PATCH 5/7] fix: use musl cross-compiler instead of glibc for arm64 RTK build The glibc cross-compiler (aarch64-linux-gnu-gcc) produces references to glibc-specific symbols (open64, stat64, etc.) that don't exist in musl. Switch to a proper musl cross-compiler from musl.cc that produces musl-compatible binaries for Alpine. Co-Authored-By: Claude Opus 4.6 --- ralphex-fe/Dockerfile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ralphex-fe/Dockerfile b/ralphex-fe/Dockerfile index 292b1cb..5a11b8b 100644 --- a/ralphex-fe/Dockerfile +++ b/ralphex-fe/Dockerfile @@ -10,13 +10,14 @@ FROM --platform=linux/amd64 rust:slim-bookworm AS rtk-builder ARG RTK_VERSION ARG TARGETARCH RUN apt-get update && apt-get install -y --no-install-recommends \ - musl-tools gcc-aarch64-linux-gnu libc6-dev-arm64-cross \ + musl-tools curl \ && rm -rf /var/lib/apt/lists/* +# Pre-built aarch64-linux-musl cross-compiler (musl headers + linker, no glibc) +RUN curl -sSfL https://musl.cc/aarch64-linux-musl-cross.tgz | tar xz -C /opt/ RUN rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl RUN if [ "$TARGETARCH" = "arm64" ]; then \ - export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-gnu-gcc && \ - export CC_aarch64_unknown_linux_musl=aarch64-linux-gnu-gcc && \ - export CFLAGS_aarch64_unknown_linux_musl="--sysroot=/usr/aarch64-linux-gnu" && \ + export CC_aarch64_unknown_linux_musl=/opt/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc && \ + export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=/opt/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc && \ cargo install --git https://github.com/rtk-ai/rtk.git --tag "v${RTK_VERSION}" --locked \ --target aarch64-unknown-linux-musl; \ else \ From a935804778f7667b54d893ae9dadab9050ec0b46 Mon Sep 17 00:00:00 2001 From: Serge Gatezh <2880401+gatezh@users.noreply.github.com> Date: Fri, 6 Mar 2026 09:59:16 -0700 Subject: [PATCH 6/7] fix: use zig as musl cross-compiler (musl.cc unreachable from CI) Replace musl.cc download (connection timeout in GitHub Actions) with zig, which natively targets musl and downloads from a reliable CDN (ziglang.org). A wrapper script invokes `zig cc -target aarch64-linux-musl` as the CC. Co-Authored-By: Claude Opus 4.6 --- ralphex-fe/Dockerfile | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/ralphex-fe/Dockerfile b/ralphex-fe/Dockerfile index 5a11b8b..e5006a1 100644 --- a/ralphex-fe/Dockerfile +++ b/ralphex-fe/Dockerfile @@ -5,19 +5,24 @@ ARG RTK_VERSION=0.26.0 # Cross-compile RTK on native amd64 to avoid slow QEMU-emulated Rust builds. # --platform=linux/amd64 forces native execution; TARGETARCH selects the cross-compilation target. -# Using Debian for easy access to aarch64 cross-compilation toolchain. +# Zig provides a C cross-compiler that natively targets musl (no glibc/musl mismatch). FROM --platform=linux/amd64 rust:slim-bookworm AS rtk-builder ARG RTK_VERSION ARG TARGETARCH +ARG ZIG_VERSION=0.14.0 RUN apt-get update && apt-get install -y --no-install-recommends \ - musl-tools curl \ + musl-tools xz-utils \ && rm -rf /var/lib/apt/lists/* -# Pre-built aarch64-linux-musl cross-compiler (musl headers + linker, no glibc) -RUN curl -sSfL https://musl.cc/aarch64-linux-musl-cross.tgz | tar xz -C /opt/ +RUN curl -sSfL "https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz" \ + | tar xJ -C /opt/ && \ + ln -s "/opt/zig-linux-x86_64-${ZIG_VERSION}/zig" /usr/local/bin/zig +# Wrapper script: invokes zig cc targeting aarch64-linux-musl +RUN printf '#!/bin/sh\nexec zig cc -target aarch64-linux-musl "$@"\n' \ + > /usr/local/bin/aarch64-linux-musl-cc && chmod +x /usr/local/bin/aarch64-linux-musl-cc RUN rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl RUN if [ "$TARGETARCH" = "arm64" ]; then \ - export CC_aarch64_unknown_linux_musl=/opt/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc && \ - export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=/opt/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc && \ + export CC_aarch64_unknown_linux_musl=aarch64-linux-musl-cc && \ + export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-musl-cc && \ cargo install --git https://github.com/rtk-ai/rtk.git --tag "v${RTK_VERSION}" --locked \ --target aarch64-unknown-linux-musl; \ else \ From 1f424ef52f90df1b93d3b7a4e98914d5f20bc497 Mon Sep 17 00:00:00 2001 From: Serge Gatezh <2880401+gatezh@users.noreply.github.com> Date: Fri, 6 Mar 2026 10:57:05 -0700 Subject: [PATCH 7/7] fix: use dedicated musl cross-compilation image for arm64 RTK build Replace broken zig/musl.cc/gcc cross-compilation attempts with a proven two-stage approach: - amd64: native musl build on rust:alpine - arm64: cross-compile using messense/rust-musl-cross:aarch64-musl (pre-built musl toolchain, runs natively on amd64, no QEMU) - Dynamic FROM rtk-${TARGETARCH} selects the correct builder per platform Both builds verified locally producing correct binaries. Co-Authored-By: Claude Opus 4.6 --- .hadolint.yaml | 8 +++++++ ralphex-fe/Dockerfile | 49 ++++++++++++++++++++----------------------- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/.hadolint.yaml b/.hadolint.yaml index a17c0e6..b691273 100644 --- a/.hadolint.yaml +++ b/.hadolint.yaml @@ -15,6 +15,14 @@ ignored: # arm64 musl binaries natively on amd64 to avoid slow QEMU emulation). - DL3029 # do not use --platform with FROM + # Dynamic FROM (e.g. FROM stage-${TARGETARCH}) references a build stage, + # not an untagged external image. Hadolint cannot resolve it statically. + - DL3006 # always tag image version explicitly + + # Separate RUN instructions are intentional for Docker layer caching + # (e.g. apk install vs cargo install in builder stages). + - DL3059 # multiple consecutive RUN instructions + # pipefail SHELL not required — only one pipe exists (bun install) # and curl -f already fails on HTTP errors. - DL4006 # set SHELL option -o pipefail before RUN with pipe diff --git a/ralphex-fe/Dockerfile b/ralphex-fe/Dockerfile index e5006a1..571487c 100644 --- a/ralphex-fe/Dockerfile +++ b/ralphex-fe/Dockerfile @@ -3,32 +3,29 @@ ARG HUGO_VERSION=0.156.0 ARG RALPHEX_VERSION=0.20.0 ARG RTK_VERSION=0.26.0 -# Cross-compile RTK on native amd64 to avoid slow QEMU-emulated Rust builds. -# --platform=linux/amd64 forces native execution; TARGETARCH selects the cross-compilation target. -# Zig provides a C cross-compiler that natively targets musl (no glibc/musl mismatch). -FROM --platform=linux/amd64 rust:slim-bookworm AS rtk-builder +# ── RTK builder: amd64 (native musl build on Alpine) ─────────────────── +# rust:alpine already targets musl; just need musl-dev for C dependencies. +FROM --platform=linux/amd64 rust:alpine AS rtk-amd64 ARG RTK_VERSION +RUN apk add --no-cache musl-dev +RUN cargo install --root /output \ + --git https://github.com/rtk-ai/rtk.git --tag "v${RTK_VERSION}" --locked \ + --target x86_64-unknown-linux-musl + +# ── RTK builder: arm64 (cross-compile with pre-built musl toolchain) ─── +# messense/rust-musl-cross:aarch64-musl provides a complete aarch64 musl +# cross-compiler (CC, linker, headers) pre-configured via .cargo/config.toml. +# Runs natively on amd64 — no QEMU emulation. +FROM --platform=linux/amd64 messense/rust-musl-cross:aarch64-musl AS rtk-arm64 +ARG RTK_VERSION +RUN cargo install --root /output \ + --git https://github.com/rtk-ai/rtk.git --tag "v${RTK_VERSION}" --locked \ + --target aarch64-unknown-linux-musl + +# ── Select the correct builder based on target architecture ───────────── +# BuildKit resolves TARGETARCH per platform; only the needed stage is built. ARG TARGETARCH -ARG ZIG_VERSION=0.14.0 -RUN apt-get update && apt-get install -y --no-install-recommends \ - musl-tools xz-utils \ - && rm -rf /var/lib/apt/lists/* -RUN curl -sSfL "https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz" \ - | tar xJ -C /opt/ && \ - ln -s "/opt/zig-linux-x86_64-${ZIG_VERSION}/zig" /usr/local/bin/zig -# Wrapper script: invokes zig cc targeting aarch64-linux-musl -RUN printf '#!/bin/sh\nexec zig cc -target aarch64-linux-musl "$@"\n' \ - > /usr/local/bin/aarch64-linux-musl-cc && chmod +x /usr/local/bin/aarch64-linux-musl-cc -RUN rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl -RUN if [ "$TARGETARCH" = "arm64" ]; then \ - export CC_aarch64_unknown_linux_musl=aarch64-linux-musl-cc && \ - export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-musl-cc && \ - cargo install --git https://github.com/rtk-ai/rtk.git --tag "v${RTK_VERSION}" --locked \ - --target aarch64-unknown-linux-musl; \ - else \ - cargo install --git https://github.com/rtk-ai/rtk.git --tag "v${RTK_VERSION}" --locked \ - --target x86_64-unknown-linux-musl; \ - fi +FROM rtk-${TARGETARCH} AS rtk-builder # Extend the ralphex base image which provides the ralphex binary at /srv/ralphex # along with Claude Code, Codex, Node.js, git, ripgrep, and other development tools @@ -90,8 +87,8 @@ RUN set -eux; \ rm hugo.tar.gz hugo_checksums.txt; \ hugo version -# Copy RTK binary built from source in the builder stage -COPY --from=rtk-builder /usr/local/cargo/bin/rtk /usr/local/bin/rtk +# Copy RTK binary from the architecture-matched builder stage +COPY --from=rtk-builder /output/bin/rtk /usr/local/bin/rtk RUN rtk --version # OCI labels for container metadata and GitHub Package integration