# Multi-stage build for sgl-router. # # Three stages, each scoped to its caching contract: # 1. chef — generate a `recipe.json` describing the dep graph. # 2. builder — compile deps from the recipe, then the workspace. # 3. runtime — distroless cc-debian12 with the stripped binary. # # The `cargo-chef` indirection is the canonical Rust multi-stage cache # pattern: the recipe step's inputs are JUST `Cargo.toml` + `Cargo.lock`, # so a source-only change produces a recipe-layer cache hit and the # heavy `cook --release` step is reused untouched. A naive "copy # manifests → cargo fetch → copy src" approach caches only the fetched # registry; every source change still recompiles every dep. # # `experimental/sgl-router/Cargo.lock` is committed, and every cargo step # below runs with `--locked`, so the image ships exactly the dependency # graph CI checked, tested, and license-audited. # # Build (from the repo root): # docker build -f docker/sgl-router.Dockerfile -t sgl-router:dev . # Run: # docker run --rm -p 8090:8090 \ # -v $(pwd)/docker/sgl-router.sample.yaml:/etc/sgl-router/sgl-router.yaml \ # sgl-router:dev --config /etc/sgl-router/sgl-router.yaml # # Image budget: < 100 MB stripped (M6 acceptance). Verify with # `docker image inspect sgl-router:dev --format '{{.Size}}'`. ARG RUST_VERSION=1.92 ARG DEBIAN_VERSION=bookworm ######################## STAGE 1 — chef recipe ########################## FROM rust:${RUST_VERSION}-${DEBIAN_VERSION} AS chef RUN cargo install cargo-chef --locked --version ^0.1 WORKDIR /work/sgl-router COPY experimental/sgl-router/Cargo.toml experimental/sgl-router/Cargo.lock ./ COPY experimental/sgl-router/sgl-kv-indexer/Cargo.toml sgl-kv-indexer/Cargo.toml # Stub a minimal src tree so cargo can see the workspace targets, then # prepare the chef recipe. RUN mkdir -p src sgl-kv-indexer/src/bin \ && echo "fn main() {}" > src/main.rs \ && echo "" > src/lib.rs \ && echo "" > sgl-kv-indexer/src/lib.rs \ && echo "fn main() {}" > sgl-kv-indexer/src/bin/kv-indexer-server.rs \ && echo "fn main() {}" > sgl-kv-indexer/src/bin/kv-indexer-bridge.rs \ && cargo chef prepare --recipe-path recipe.json \ && rm -rf src sgl-kv-indexer/src ######################## STAGE 2 — builder ############################## FROM rust:${RUST_VERSION}-${DEBIAN_VERSION} AS builder RUN apt-get update \ && apt-get install -y --no-install-recommends protobuf-compiler \ && rm -rf /var/lib/apt/lists/* \ && cargo install cargo-chef --locked --version ^0.1 WORKDIR /work/sgl-router # `dynamo-tokenizers` pulls in `pcre2-sys`, whose build.rs links the SYSTEM # libpcre2-8 whenever pkg-config finds it (it does here — the rust:bookworm # base ships libpcre2-dev). That dynamic dep is absent from the distroless # runtime, so the binary fails at startup with "libpcre2-8.so.0: cannot open # shared object file". Force pcre2-sys to compile its vendored PCRE2 and link # it statically, keeping the runtime self-contained. ENV PCRE2_SYS_STATIC=1 COPY --from=chef /work/sgl-router/recipe.json ./recipe.json COPY experimental/sgl-router/sgl-kv-indexer/Cargo.toml sgl-kv-indexer/Cargo.toml # Cook (compile + cache) the dep graph from the recipe. The recipe carries every # workspace member's manifest and the lockfile, so chef recreates the Indexer's # source stubs itself. RUN cargo chef cook --locked --release --recipe-path recipe.json # Now bring in the real sources and the manifests they need. COPY experimental/sgl-router/Cargo.toml experimental/sgl-router/Cargo.lock ./ COPY experimental/sgl-router/src ./src COPY experimental/sgl-router/sgl-kv-indexer/Cargo.toml sgl-kv-indexer/Cargo.toml COPY experimental/sgl-router/sgl-kv-indexer/build.rs sgl-kv-indexer/build.rs COPY experimental/sgl-router/sgl-kv-indexer/proto sgl-kv-indexer/proto COPY experimental/sgl-router/sgl-kv-indexer/src sgl-kv-indexer/src RUN touch sgl-kv-indexer/build.rs \ && cargo build --locked --release --bin sgl-router \ && strip target/release/sgl-router ######################## STAGE 3 — runtime ############################## FROM gcr.io/distroless/cc-debian12:nonroot AS runtime COPY --from=builder /work/sgl-router/target/release/sgl-router /usr/local/bin/sgl-router # Default config path; mount your own via `-v :/etc/sgl-router`. ENV SGL_ROUTER_CONFIG=/etc/sgl-router/sgl-router.yaml EXPOSE 8090 # distroless `nonroot` runs as uid 65532. The router doesn't need root. USER nonroot:nonroot ENTRYPOINT ["/usr/local/bin/sgl-router"]