Files
sglang/docker/sgl-router.Dockerfile
2026-09-14 19:08:16 -07:00

98 lines
4.4 KiB
Docker

# 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 <host-path>:/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"]