Skip to content

Commit edd80c9

Browse files
authored
fix(docker): vendor arch-safe PIGuard daemon build (#86)
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.
1 parent 4a319d1 commit edd80c9

2 files changed

Lines changed: 62 additions & 9 deletions

File tree

docker/docker-compose.yml

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -162,19 +162,17 @@ services:
162162
# gateway bridges that to plain HTTP on the compose network so odek can use
163163
# its existing HTTP-based piguard client without sharing a socket volume.
164164
#
165-
# Apple Silicon note: the upstream daemon Dockerfile pins its base images by
166-
# amd64 digest, which yields x86-64 binaries that crash under Rosetta on
167-
# macOS. If `docker compose up` fails with "rosetta error", build the daemon
168-
# locally from the guard repo with the digest pins removed and tag it
169-
# piguard:local (compose reuses the existing image).
165+
# The daemon image is vendored (./piguard-daemon) for arch-safe builds: the
166+
# upstream Dockerfile pins amd64-only base digests, which crash under Rosetta
167+
# on Apple Silicon. The vendored Dockerfile builds the pinned upstream v1.0.0
168+
# source with native (unpinned) base images instead.
170169
piguard:
171170
profiles: ["restricted", "godmode", "telegram-restricted", "telegram-godmode"]
172171
# GHCR packages for this repo are private, so we build the daemon image locally
173-
# from the public source. The first build clones the repo and compiles the
174-
# binary; subsequent starts reuse the cached image.
172+
# from the public source. The first build clones the upstream repo and compiles
173+
# the binary; subsequent starts reuse the cached image.
175174
build:
176-
context: https://github.com/BackendStack21/go-prompt-injection-guard.git#v1.0.0
177-
dockerfile: Dockerfile
175+
context: ./piguard-daemon
178176
image: piguard:local
179177
command:
180178
- "--socket=/run/piguard/piguard.sock"

docker/piguard-daemon/Dockerfile

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# syntax=docker/dockerfile:1
2+
#
3+
# Vendored, architecture-safe build of the PIGuard prompt-injection daemon.
4+
#
5+
# Why vendored: the upstream Dockerfile
6+
# (https://github.com/BackendStack21/go-prompt-injection-guard/blob/v1.0.0/Dockerfile)
7+
# pins its base images by amd64-only sha256 digests, so on Apple Silicon Macs
8+
# the build produces x86-64 binaries (piguard-server, libonnxruntime.so) inside
9+
# an arm64-labeled image, and the container crash-loops with:
10+
# rosetta error: failed to open elf at /lib64/ld-linux-x86-64.so.2
11+
# This Dockerfile builds the same pinned upstream source (v1.0.0) but uses
12+
# unpinned base image tags, which resolve to the host architecture. The upstream
13+
# Makefile is arch-aware (it selects the aarch64 ONNX Runtime when
14+
# GOARCH=arm64), so the resulting image contains native binaries. Note: the
15+
# upstream Makefile has no checksum entries for linux-arm64, so it prints
16+
# WARNINGs during the build — that is expected and harmless.
17+
18+
# ---- build stage ----
19+
# cgo is required: the tokenizer bindings link a static library and ONNX Runtime
20+
# is loaded at runtime. A glibc base is required (the prebuilt libtokenizers.a is
21+
# a GNU build), so this uses debian-based images rather than Alpine.
22+
FROM golang:1.26-bookworm AS builder
23+
24+
# Fetch the pinned upstream release.
25+
RUN git clone --depth 1 --branch v1.0.0 \
26+
https://github.com/BackendStack21/go-prompt-injection-guard.git /src
27+
WORKDIR /src
28+
29+
# Fetch native libs (tokenizers static + ONNX Runtime shared) and build both
30+
# binaries.
31+
RUN make libtokenizers libonnxruntime build
32+
33+
# ---- runtime stage ----
34+
FROM debian:bookworm-slim AS runtime
35+
RUN apt-get update \
36+
&& apt-get install -y --no-install-recommends ca-certificates libstdc++6 \
37+
&& rm -rf /var/lib/apt/lists/*
38+
39+
COPY --from=builder /src/bin/piguard /usr/local/bin/piguard
40+
COPY --from=builder /src/bin/piguard-server /usr/local/bin/piguard-server
41+
COPY --from=builder /src/lib/libonnxruntime.so /usr/local/lib/libonnxruntime.so
42+
43+
# The tokenizer code is linked statically; only ONNX Runtime is loaded at
44+
# runtime, located via ORT_DYLIB_PATH.
45+
ENV ORT_DYLIB_PATH=/usr/local/lib/libonnxruntime.so
46+
47+
# Run as a non-root user. /run/piguard holds the Unix socket (share it with
48+
# clients via a volume); /models holds the exported model (mount it read-only).
49+
RUN useradd --system --uid 10001 piguard \
50+
&& mkdir -p /run/piguard /models \
51+
&& chown -R piguard /run/piguard
52+
USER piguard
53+
54+
ENTRYPOINT ["piguard-server"]
55+
CMD ["--socket", "/run/piguard/piguard.sock", "--model-dir", "/models"]

0 commit comments

Comments
 (0)