diff --git a/.github/workflows/nightly-experimental-sgl-router-docker.yml b/.github/workflows/nightly-experimental-sgl-router-docker.yml index e070382af..63141d182 100644 --- a/.github/workflows/nightly-experimental-sgl-router-docker.yml +++ b/.github/workflows/nightly-experimental-sgl-router-docker.yml @@ -9,10 +9,27 @@ concurrency: group: ${{ github.workflow }} cancel-in-progress: true +env: + IMAGE_REPO: lmsysorg/sglang-staging + IMAGE_TAG: experimental-router-dev + jobs: - publish: + # Build each arch natively (no QEMU emulation) and push the image by digest. + # The manifest job then stitches the per-arch digests into one multi-arch + # tag, so `docker pull` resolves the right arch automatically. + build: if: github.repository == 'sgl-project/sglang' - runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-24.04 + arch: amd64 + - platform: linux/arm64 + runner: ubuntu-24.04-arm + arch: arm64 + runs-on: ${{ matrix.runner }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -26,9 +43,53 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and Push + - name: Build and push by digest run: | docker buildx build . -f docker/sgl-router.Dockerfile \ - --platform linux/amd64 \ - -t lmsysorg/sglang-staging:experimental-router-dev \ - --push + --platform ${{ matrix.platform }} \ + --output "type=image,name=${IMAGE_REPO},push-by-digest=true,name-canonical=true,push=true" \ + --metadata-file /tmp/metadata.json + DIGEST=$(python3 -c "import json; print(json.load(open('/tmp/metadata.json'))['containerimage.digest'])") + echo "Pushed ${{ matrix.platform }} digest: ${DIGEST}" + mkdir -p /tmp/digests + echo "${DIGEST}" > "/tmp/digests/${{ matrix.arch }}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digest-${{ matrix.arch }} + path: /tmp/digests/${{ matrix.arch }} + retention-days: 1 + + create-manifest: + if: github.repository == 'sgl-project/sglang' + needs: build + runs-on: ubuntu-24.04 + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: digest-* + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Create and push multi-arch manifest + run: | + AMD64_DIGEST=$(cat /tmp/digests/amd64) + ARM64_DIGEST=$(cat /tmp/digests/arm64) + docker buildx imagetools create \ + -t "${IMAGE_REPO}:${IMAGE_TAG}" \ + "${IMAGE_REPO}@${AMD64_DIGEST}" \ + "${IMAGE_REPO}@${ARM64_DIGEST}" + + - name: Inspect manifest + run: docker buildx imagetools inspect "${IMAGE_REPO}:${IMAGE_TAG}" diff --git a/docker/sgl-router.Dockerfile b/docker/sgl-router.Dockerfile index 40de83aee..c8b3c0d26 100644 --- a/docker/sgl-router.Dockerfile +++ b/docker/sgl-router.Dockerfile @@ -49,6 +49,15 @@ RUN mkdir -p src && echo "fn main() {}" > src/main.rs \ FROM rust:${RUST_VERSION}-${DEBIAN_VERSION} AS builder RUN cargo install cargo-chef --locked --version ^0.1 WORKDIR /work + +# `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/recipe.json ./recipe.json COPY --from=chef /work/Cargo.lock ./Cargo.lock COPY experimental/sgl-router/rust-toolchain.toml ./