[CI] Preserve Rust Docker cache across commits (#53290)

Signed-off-by: mgoin <mgoin64@gmail.com> Co-authored-by: OpenAI Codex <codex@openai.com>

Misha Goin committed Aug 25, 2026 at 14:13 UTC c9331d8064808e3402ec79ae8631f48cebdc827a
5 files changed +50 -25
docker/Dockerfile
+12 -10
@@ -253,7 +253,7 @@ ENV TORCH_CUDA_ARCH_LIST=${torch_cuda_arch_list}
253 # build stage doesn't need the rust toolchain or the rust source.
254 # This stage reuses the Python environment from base and runs in parallel with
255 # csrc-build/extensions-build.
256 -FROM base AS rust-build
256 +FROM base AS rust-build-cache
257 ARG USE_SCCACHE
258 ARG SCCACHE_ENDPOINT
259
@@ -283,26 +283,28 @@ ENV CARGO_BUILD_JOBS=4
283 # separate local sccache daemon while sharing the same remote cache backend.
284 ENV SCCACHE_SERVER_PORT=4227
285
286 -# Only Rust inputs are present, so suppress the artificial dirty state caused
287 -# by other tracked files being absent from this build stage.
288 -ENV SETUPTOOLS_SCM_PRETEND_METADATA={dirty=false}
289 -
290 -# Build the release artifacts. Cache cargo registry/git, but not target/,
291 -# because stale target metadata can outlive source updates across BuildKit
292 -# cache reuse.
286 +# Build with a stable version so this target snapshot is keyed only by Rust
287 +# inputs. The child stage below relinks the crates that embed the exact version.
288 RUN --mount=type=cache,target=/root/.cargo/registry,sharing=locked \
289 --mount=type=cache,target=/root/.cargo/git,sharing=locked \
295 - --mount=type=bind,source=.git,target=.git \
290 --mount=type=secret,id=aws-credentials,target=/root/.aws/credentials,required=false \
291 if [ "$USE_SCCACHE" = "1" ]; then \
292 if [ -n "${SCCACHE_ENDPOINT}" ]; then export SCCACHE_ENDPOINT="${SCCACHE_ENDPOINT}"; fi; \
293 export RUSTC_WRAPPER=sccache; \
294 sccache --show-stats; \
295 fi \
302 - && bash build_rust.sh \
296 + && SETUPTOOLS_SCM_PRETEND_VERSION="0.0.0+docker.cache" bash build_rust.sh \
297 && if [ "$USE_SCCACHE" = "1" ]; then \
298 sccache --show-stats; \
299 fi
300 +
301 +# Relink with the exact Git-derived package version.
302 +FROM rust-build-cache AS rust-build
303 +
304 +RUN --mount=type=cache,target=/root/.cargo/registry,sharing=locked \
305 + --mount=type=cache,target=/root/.cargo/git,sharing=locked \
306 + --mount=type=bind,source=.git,target=.git \
307 + SETUPTOOLS_SCM_PRETEND_METADATA="{dirty=false}" bash build_rust.sh
308 #################### RUST BUILD IMAGE ####################
309
310 #################### CSRC BUILD IMAGE ####################
docker/Dockerfile.cpu
+12 -10
@@ -129,7 +129,7 @@ RUN echo 'ulimit -c 0' >> ~/.bashrc
129 # Build the Rust frontend (`vllm-rs`) in a dedicated stage so the wheel build
130 # stage doesn't need the rust toolchain. This stage runs in parallel
131 # with the main vllm-build stage.
132 -FROM base AS rust-build
132 +FROM base AS rust-build-cache
133 ENV DEBIAN_FRONTEND=noninteractive
134
135 RUN apt-get update -y \
@@ -163,25 +163,27 @@ COPY build_rust.sh build_rust.sh
163 # (rustc spawns enough concurrent processes to hit RLIMIT_NOFILE otherwise).
164 ENV CARGO_BUILD_JOBS=4
165
166 -# Only Rust inputs are present, so suppress the artificial dirty state caused
167 -# by other tracked files being absent from this build stage.
168 -ENV SETUPTOOLS_SCM_PRETEND_METADATA={dirty=false}
169 -
170 -# Build the release artifacts. Cache cargo registry/git, but not target/,
171 -# because stale target metadata can outlive source updates across BuildKit
172 -# cache reuse.
166 +# Build with a stable version so this target snapshot is keyed only by Rust
167 +# inputs. The child stage below relinks the crates that embed the exact version.
168 RUN --mount=type=cache,target=/root/.cargo/registry,sharing=locked \
169 --mount=type=cache,target=/root/.cargo/git,sharing=locked \
175 - --mount=type=bind,source=.git,target=.git \
170 --mount=type=secret,id=aws-credentials,target=/root/.aws/credentials,required=false \
171 if [ "$USE_SCCACHE" = "1" ]; then \
172 export RUSTC_WRAPPER=sccache; \
173 if [ -n "${SCCACHE_ENDPOINT}" ]; then export SCCACHE_ENDPOINT="${SCCACHE_ENDPOINT}"; fi; \
174 sccache --show-stats; \
175 fi && \
182 - bash build_rust.sh && \
176 + SETUPTOOLS_SCM_PRETEND_VERSION="0.0.0+docker.cache" bash build_rust.sh && \
177 if [ "$USE_SCCACHE" = "1" ]; then sccache --show-stats; fi
178
179 +# Relink with the exact Git-derived package version.
180 +FROM rust-build-cache AS rust-build
181 +
182 +RUN --mount=type=cache,target=/root/.cargo/registry,sharing=locked \
183 + --mount=type=cache,target=/root/.cargo/git,sharing=locked \
184 + --mount=type=bind,source=.git,target=.git \
185 + SETUPTOOLS_SCM_PRETEND_METADATA="{dirty=false}" bash build_rust.sh
186 +
187 ######################### SOURCE PREP IMAGE #########################
188 # Shared prep (source, rust artifacts, build deps) for both vllm-build and
189 # vllm-dev, so the two independent compiles (bdist_wheel vs. setup.py
docker/Dockerfile.xpu
+10 -5
@@ -1,7 +1,7 @@
1 ######################### RUST BUILD IMAGE #########################
2 # Build the Rust frontend (`vllm-rs`) in a dedicated stage so the main image
3 # doesn't need the rust toolchain. Runs in parallel with vllm-base.
4 -FROM ubuntu:22.04 AS rust-build
4 +FROM ubuntu:22.04 AS rust-build-cache
5 ENV DEBIAN_FRONTEND=noninteractive
6
7 RUN apt-get update -y \
@@ -25,14 +25,19 @@ COPY build_rust.sh build_rust.sh
25 # (rustc spawns enough concurrent processes to hit RLIMIT_NOFILE otherwise).
26 ENV CARGO_BUILD_JOBS=4
27
28 -# Only Rust inputs are present, so suppress the artificial dirty state caused
29 -# by other tracked files being absent from this build stage.
30 -ENV SETUPTOOLS_SCM_PRETEND_METADATA={dirty=false}
28 +# Build with a stable version so this target snapshot is keyed only by Rust
29 +# inputs. The child stage below relinks the crates that embed the exact version.
30 +RUN --mount=type=cache,target=/root/.cargo/registry,sharing=locked \
31 + --mount=type=cache,target=/root/.cargo/git,sharing=locked \
32 + SETUPTOOLS_SCM_PRETEND_VERSION="0.0.0+docker.cache" bash build_rust.sh
33 +
34 +# Relink with the exact Git-derived package version.
35 +FROM rust-build-cache AS rust-build
36
37 RUN --mount=type=cache,target=/root/.cargo/registry,sharing=locked \
38 --mount=type=cache,target=/root/.cargo/git,sharing=locked \
39 --mount=type=bind,source=.git,target=.git \
35 - bash build_rust.sh
40 + SETUPTOOLS_SCM_PRETEND_METADATA="{dirty=false}" bash build_rust.sh
41
42 FROM ubuntu:24.04 AS vllm-base
43
docs/assets/contributing/dockerfile-stages-dependency.png
Binary files a/docs/assets/contributing/dockerfile-stages-dependency.png and b/docs/assets/contributing/dockerfile-stages-dependency.png differ
tests/tools/test_docker_build_metadata_args.py
+16
@@ -153,6 +153,22 @@ def test_vllm_openai_image_embeds_metadata_contract() -> None:
153 assert expected in dockerfile
154
155
156 +def test_rust_build_cache_excludes_git_metadata() -> None:
157 + for name in ("Dockerfile", "Dockerfile.cpu", "Dockerfile.xpu"):
158 + dockerfile = (REPO_ROOT / "docker" / name).read_text()
159 + cached_stage, exact_version_stage = dockerfile.split(
160 + "FROM rust-build-cache AS rust-build", maxsplit=1
161 + )
162 + exact_version_stage = exact_version_stage.split("\nFROM ", maxsplit=1)[0]
163 + cached_run = cached_stage.rsplit("RUN ", maxsplit=1)[1]
164 +
165 + assert 'SETUPTOOLS_SCM_PRETEND_VERSION="0.0.0+docker.cache"' in cached_run
166 + assert "source=.git,target=.git" not in cached_run
167 + assert "source=.git,target=.git" in exact_version_stage
168 + assert 'SETUPTOOLS_SCM_PRETEND_METADATA="{dirty=false}"' in exact_version_stage
169 + assert "bash build_rust.sh" in exact_version_stage
170 +
171 +
172 def test_rocm_ci_base_bake_embeds_content_hash_label() -> None:
173 bake_file = (REPO_ROOT / "docker" / "docker-bake-rocm.hcl").read_text()
174