From d984eafba94f0bdc3f025ef8dbb705b3ecf8367e Mon Sep 17 00:00:00 2001 From: Rolando Santamaria Maso Date: Wed, 22 Jul 2026 09:59:27 +0200 Subject: [PATCH] fix(docker): vendor arch-safe PIGuard daemon build The upstream go-prompt-injection-guard Dockerfile pins its base images by amd64-only sha256 digests, so building the remote git context on Apple Silicon produces x86-64 binaries (piguard-server, libonnxruntime.so) inside an arm64-labeled image and the container crash-loops with "rosetta error: failed to open elf at /lib64/ld-linux-x86-64.so.2". Vendor the daemon build in docker/piguard-daemon/Dockerfile: it compiles the pinned upstream v1.0.0 source (git clone --branch v1.0.0) with unpinned base image tags, which resolve to the host architecture. The upstream Makefile is arch-aware and selects the aarch64 ONNX Runtime when GOARCH=arm64, so the resulting image contains native binaries (verified: ELF e_machine b7 = EM_AARCH64 for both the server binary and libonnxruntime.so; container reaches healthy and correctly labels BENIGN/INJECTION through the gateway). This replaces the manual Apple Silicon workaround documented in PR #85. --- docker/docker-compose.yml | 16 ++++------ docker/piguard-daemon/Dockerfile | 55 ++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 docker/piguard-daemon/Dockerfile diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 838247f..ea58219 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -162,19 +162,17 @@ services: # gateway bridges that to plain HTTP on the compose network so odek can use # its existing HTTP-based piguard client without sharing a socket volume. # - # Apple Silicon note: the upstream daemon Dockerfile pins its base images by - # amd64 digest, which yields x86-64 binaries that crash under Rosetta on - # macOS. If `docker compose up` fails with "rosetta error", build the daemon - # locally from the guard repo with the digest pins removed and tag it - # piguard:local (compose reuses the existing image). + # The daemon image is vendored (./piguard-daemon) for arch-safe builds: the + # upstream Dockerfile pins amd64-only base digests, which crash under Rosetta + # on Apple Silicon. The vendored Dockerfile builds the pinned upstream v1.0.0 + # source with native (unpinned) base images instead. piguard: profiles: ["restricted", "godmode", "telegram-restricted", "telegram-godmode"] # GHCR packages for this repo are private, so we build the daemon image locally - # from the public source. The first build clones the repo and compiles the - # binary; subsequent starts reuse the cached image. + # from the public source. The first build clones the upstream repo and compiles + # the binary; subsequent starts reuse the cached image. build: - context: https://github.com/BackendStack21/go-prompt-injection-guard.git#v1.0.0 - dockerfile: Dockerfile + context: ./piguard-daemon image: piguard:local command: - "--socket=/run/piguard/piguard.sock" diff --git a/docker/piguard-daemon/Dockerfile b/docker/piguard-daemon/Dockerfile new file mode 100644 index 0000000..96cfb1f --- /dev/null +++ b/docker/piguard-daemon/Dockerfile @@ -0,0 +1,55 @@ +# syntax=docker/dockerfile:1 +# +# Vendored, architecture-safe build of the PIGuard prompt-injection daemon. +# +# Why vendored: the upstream Dockerfile +# (https://github.com/BackendStack21/go-prompt-injection-guard/blob/v1.0.0/Dockerfile) +# pins its base images by amd64-only sha256 digests, so on Apple Silicon Macs +# the build produces x86-64 binaries (piguard-server, libonnxruntime.so) inside +# an arm64-labeled image, and the container crash-loops with: +# rosetta error: failed to open elf at /lib64/ld-linux-x86-64.so.2 +# This Dockerfile builds the same pinned upstream source (v1.0.0) but uses +# unpinned base image tags, which resolve to the host architecture. The upstream +# Makefile is arch-aware (it selects the aarch64 ONNX Runtime when +# GOARCH=arm64), so the resulting image contains native binaries. Note: the +# upstream Makefile has no checksum entries for linux-arm64, so it prints +# WARNINGs during the build — that is expected and harmless. + +# ---- build stage ---- +# cgo is required: the tokenizer bindings link a static library and ONNX Runtime +# is loaded at runtime. A glibc base is required (the prebuilt libtokenizers.a is +# a GNU build), so this uses debian-based images rather than Alpine. +FROM golang:1.26-bookworm AS builder + +# Fetch the pinned upstream release. +RUN git clone --depth 1 --branch v1.0.0 \ + https://github.com/BackendStack21/go-prompt-injection-guard.git /src +WORKDIR /src + +# Fetch native libs (tokenizers static + ONNX Runtime shared) and build both +# binaries. +RUN make libtokenizers libonnxruntime build + +# ---- runtime stage ---- +FROM debian:bookworm-slim AS runtime +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates libstdc++6 \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /src/bin/piguard /usr/local/bin/piguard +COPY --from=builder /src/bin/piguard-server /usr/local/bin/piguard-server +COPY --from=builder /src/lib/libonnxruntime.so /usr/local/lib/libonnxruntime.so + +# The tokenizer code is linked statically; only ONNX Runtime is loaded at +# runtime, located via ORT_DYLIB_PATH. +ENV ORT_DYLIB_PATH=/usr/local/lib/libonnxruntime.so + +# Run as a non-root user. /run/piguard holds the Unix socket (share it with +# clients via a volume); /models holds the exported model (mount it read-only). +RUN useradd --system --uid 10001 piguard \ + && mkdir -p /run/piguard /models \ + && chown -R piguard /run/piguard +USER piguard + +ENTRYPOINT ["piguard-server"] +CMD ["--socket", "/run/piguard/piguard.sock", "--model-dir", "/models"]