Skip to content

[Docker] Rebuild the kit-less image as a single stage - #7671

Draft
hujc7 wants to merge 9 commits into
isaac-sim:developfrom
hujc7:jichuanh/kitless-single-stage-refactor
Draft

[Docker] Rebuild the kit-less image as a single stage#7671
hujc7 wants to merge 9 commits into
isaac-sim:developfrom
hujc7:jichuanh/kitless-single-stage-refactor

Conversation

@hujc7

@hujc7 hujc7 commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

The kit-less image drops from 159 to 114 Dockerfile lines and stops installing a C toolchain
it never uses on x86_64, while a uv run invariant runs against the built image for the first
time. Cold build 109 s, image unchanged at 15.46 GB.

Description

Stacked on #7628 — that PR carries the four runtime uv settings for NVBug 6732972 and should
merge first. Until it does, this PR's diff shows its commits too.

1. Single stage

Dockerfile.kitless was the repo's only image whose final stage was not the stage that ran the
install, which is why ENV had to be declared twice and why nvbugs 6732972 was possible at all.
Merging the stages removes that class of bug rather than patching it.

The builder stage existed to keep a compiler out of the runtime. Measured: 0 of 222 installed
distributions are locally built on x86_64, so build-essential, cmake and python3-dev were
339 MB of unused toolchain. psutil==5.9.8 and pyopengl-accelerate==3.1.10 publish no aarch64
wheels, so those build deps are restored for arm64 only, as Dockerfile.base already does.

2. uv cache mount

Kit-less was the only image without one; base and cuRobo have used --mount=type=cache since
#7405. The mount targets ${HOME}/.cache/uv rather than root's, because this image sets HOME
for the whole build — an unmatched target silently bakes the cache into the layer.

3. Built-image invariant

kitless-docker.yml never passed verify-test-path, so docker/test/test_image_invariants.py
had never run against this image. It now does, with a new assertion that uv run resolves the
shipped environment. Verified to fail on an image with UV_PROJECT_ENVIRONMENT removed and pass
on the fixed one.

4. Validation

Measured on an RTX PRO 6000 (Blackwell), amd64:

Check Result
Cold --no-cache build 109 s, 15.46 GB
OVRTX renderer, --gpus all initialises
Warp / Newton on CUDA warp 1.17.0, newton 1.6.0rc1
uv run with source/ bind-mounted read-only resolves /opt/isaaclab-venv, no stray .venv
docker/test 46 passed, 2 skipped
uv run isaaclab -f clean

arm64 is not verified. PR CI builds amd64 only; the aarch64 path is exercised by
publish-images.yaml after merge. The conditional is confirmed inert on amd64.

A two-phase uv sync (deps, then workspace) was measured and rejected: it made a source-edit
rebuild 6.9× slower (468 s vs 68 s) and the image 14.9 GB larger, because the second sync
rewrites the venv in a later layer.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Release backport

  • Backport this pull request to the active release branch after it merges into develop

Checklist

  • I have read and understood the contribution guidelines
  • I have run the pre-commit checks with ./isaaclab.sh --format
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • I have added a changelog fragment under source/<pkg>/changelog.d/ for every touched package
  • I have added my name to the CONTRIBUTORS.md or my name already exists there

The kit-less image is the only multi-stage Dockerfile, and ENV does not cross
stages: the builder's uv configuration was never repeated in the runtime stage.
uv resolves the project environment from UV_PROJECT_ENVIRONMENT alone, so any
in-container `uv run` ignored VIRTUAL_ENV, targeted /workspace/isaaclab/.venv and
reinstalled the whole locked dependency set. Where Compose or CI bind-mounts the
host checkout over source/, the editable rebuild then wrote egg-info into files
the image's uid cannot touch and the command died with "Permission denied".

Repeat the four uv settings in the runtime stage. UV_NO_SYNC is what keeps
`uv run` on the environment the image shipped; the venv is already synced from
the lock, so re-deriving it can only diverge from it.

Every existing validation step calls `python` directly, which is why nothing
caught this. Add a `uv run` step to the kit-less validation action.
The stage split existed to keep a C toolchain out of the runtime, but nothing
in the lock builds a compiled source distribution: 0 of 222 installed
distributions are locally built on x86_64. Dropping build-essential, cmake and
python3-dev removes the toolchain outright, and with one stage the uv settings
are declared once instead of being repeated across a stage boundary the
runtime never inherited.

Install through a uv cache mount as Dockerfile.base and Dockerfile.curobo do.
The mount targets ${HOME}/.cache/uv, not root's, because this image sets HOME
for the whole build; UV_CACHE_DIR pins the two together so an unmatched mount
cannot bake the cache into the layer.

Assert the fix on the built image rather than on the Dockerfile text: the new
image invariant runs uv run inside the image and checks it resolves the shipped
environment. kitless-docker.yml never passed verify-test-path, so image
invariants had never run against this image at all.

Measured on desk, RTX PRO 6000: 15.46 GB image, 109 s cold build, OVRTX
renderer constructs on GPU, warp and newton import on CUDA, uv run resolves
/opt/isaaclab-venv with source mounted read-only.
The verify-test-path input added in the previous commit runs the image
invariants with ``uv run`` on the runner itself, and the GPU runners have no uv
on PATH, so the step failed with exit 127 before the tests ran. build.yaml
already carries the same setup-uv step ahead of its own verify-test-path for
this reason; kitless-docker.yml only had one in its ubuntu-latest changes job.
Restore the build toolchain for arm64 only. psutil 5.9.8 and pyopengl-accelerate
3.1.10 publish no aarch64 wheels, so uv builds them from sdist there; the earlier
claim that nothing in the lock compiles held only on x86_64, which is the one
architecture it was measured on. Guarded by dpkg --print-architecture as
Dockerfile.base does, so the x86_64 image keeps shipping no compiler.

Stop setting UV_NO_SYNC in the image. It suppressed the editable rebuild that
fails on a bind-mounted source tree, but it also stopped uv run --extra <name>
installing an extra the image does not carry, which breaks the documented
--extra leapp flow. The setting now travels with the caller that mounts the
source read-only, which is the validate action.

Retarget UV_PYTHON at the venv after the sync, as Dockerfile.base and
Dockerfile.curobo already do, so a runtime uv pip install does not resolve to
the externally-managed system interpreter.

Pre-create ${HOME}/.cache so BuildKit cannot leave the cache-mount parent
root-owned inside a 750 home, and select the image invariants in the workflow's
change patterns so an edit to them rebuilds the image they assert against.
@github-actions github-actions Bot added isaac-lab Related to Isaac Lab team infrastructure labels Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

infrastructure isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant