99# Base image
1010ARG ISAACSIM_BASE_IMAGE_ARG=nvcr.io/nvidia/isaac-sim
1111ARG ISAACSIM_VERSION_ARG=6.0.0
12+
13+ # uv, pinned by digest. Declared after the ARGs so those stay global for the base FROM.
14+ FROM ghcr.io/astral-sh/uv:0.12.9@sha256:8b940d3a9d65bed080436972241af2e21c84b5e8c9193f7014ed71479ee795ff AS uv
15+
1216FROM ${ISAACSIM_BASE_IMAGE_ARG}:${ISAACSIM_VERSION_ARG} AS base
1317ARG ISAACSIM_VERSION_ARG
1418ENV ISAACSIM_VERSION=${ISAACSIM_VERSION_ARG}
@@ -32,10 +36,19 @@ ARG DOCKER_USER_HOME_ARG
3236ENV DOCKER_USER_HOME=${DOCKER_USER_HOME_ARG}
3337ENV HOME=${DOCKER_USER_HOME}
3438
35- # Set environment variables
39+ # Locale and apt behaviour.
3640ENV LANG=C.UTF-8
3741ENV DEBIAN_FRONTEND=noninteractive
38- ENV PIP_RETRIES=12
42+
43+ # uv. Dependencies resolve from uv.lock into this venv instead of Isaac Sim's own
44+ # site-packages; isaaclab.sh sources setup_python_env.sh so ``isaacsim`` stays importable.
45+ # uv is pinned to Kit's interpreter: ``python-preference = "only-managed"`` would otherwise
46+ # download a CPython that cannot load Kit's extension modules.
47+ ARG VENV_PATH_ARG=/opt/isaaclab-venv
48+ ENV VIRTUAL_ENV=${VENV_PATH_ARG}
49+ ENV UV_PROJECT_ENVIRONMENT=${VENV_PATH_ARG}
50+ ENV UV_PYTHON=${ISAACSIM_ROOT_PATH_ARG}/kit/python/bin/python3
51+ ENV UV_PYTHON_PREFERENCE=only-system
3952ENV UV_HTTP_RETRIES=12
4053
4154# Base image may end with a non-root user; switch to root for system-level
@@ -71,27 +84,24 @@ RUN if [ "$(dpkg --print-architecture)" = "arm64" ]; then \
7184
7285# copy files necessary for installing Isaac Lab and its dependencies
7386# this way we minimize the chance of cache invalidation on file edits
74- COPY ../isaaclab.* ../environment.yml ../pyproject.toml ${ISAACLAB_PATH}/
87+ COPY --from=uv /uv /uvx /usr/local/bin/
88+ COPY ../isaaclab.* ../environment.yml ../pyproject.toml ../uv.lock ${ISAACLAB_PATH}/
7589COPY ../tools/ ${ISAACLAB_PATH}/tools/
76- COPY ../source/ ${ISAACLAB_PATH}/source/
77-
78- # Fix the line endings for the shell scripts (Windows git may add \r)
79- RUN find ${ISAACLAB_PATH} -type f -name "*.sh" -exec sed -i 's/\r$//' {} +
80-
81- # Ensure isaaclab.sh has execute permissions
82- RUN chmod +x ${ISAACLAB_PATH}/isaaclab.sh
8390
8491# Set up a symbolic link between the installed Isaac Sim root folder and _isaac_sim in the Isaac Lab directory
8592RUN ln -sf ${ISAACSIM_ROOT_PATH} ${ISAACLAB_PATH}/_isaac_sim
8693
87- # Install toml (used by tools/install_deps.py) and pytest (needed to run
88- # in-container tests; moved to optional [test] extra in pyproject.toml)
89- RUN ${ISAACLAB_PATH}/isaaclab.sh -p -m pip install toml pytest
94+ # Created before the venv so every install layer can set ownership as it writes, instead of
95+ # a recursive chown afterwards: on overlayfs a chown copies up every file it touches, which
96+ # costs both build time and a duplicate of the venv in the image.
97+ # The uid/gid 1000 match GitHub runner bind mounts used by Docker tests.
98+ # --non-unique is required because some base image revisions already carry
99+ # another user or group at uid/gid 1000.
100+ RUN groupadd --non-unique --gid 1000 isaaclab \
101+ && useradd --non-unique --uid 1000 --gid 1000 -M -l -s /bin/bash -d ${DOCKER_USER_HOME} isaaclab
90102
91- # Install apt dependencies for extensions that declare them in their extension.toml,
92- RUN ${ISAACLAB_PATH}/isaaclab.sh -p ${ISAACLAB_PATH}/tools/install_deps.py apt ${ISAACLAB_PATH}/source && \
93- apt-get -y autoremove && apt-get clean && \
94- rm -rf /var/lib/apt/lists/*
103+ # Created up front so the install steps below resolve into it.
104+ RUN uv venv --seed "${VIRTUAL_ENV}"
95105
96106# for singularity usage, have to create the directories that will binded
97107RUN mkdir -p ${ISAACSIM_ROOT_PATH}/kit/cache && \
@@ -113,41 +123,79 @@ RUN touch /bin/nvidia-smi && \
113123 mkdir -p /var/run/nvidia-persistenced && \
114124 touch /var/run/nvidia-persistenced/socket
115125
116- # robomimic declares imageio-ffmpeg for optional video helpers. Keep its
117- # bundled FFmpeg binary out of the distributed image and let users who
118- # enable video recording install MoviePy and its video backend explicitly.
119- RUN --mount=type=cache,target=${DOCKER_USER_HOME}/.cache/pip \
120- ${ISAACLAB_PATH}/isaaclab.sh --install && \
121- ${ISAACLAB_PATH}/isaaclab.sh -p -m pip uninstall -y imageio-ffmpeg
126+ # Workspace manifests only: ``uv sync --no-install-workspace`` needs every member's
127+ # pyproject.toml to build an installation plan, but not their sources. Resolving the ~240
128+ # third-party packages here means a source edit re-runs only the workspace install below.
129+ COPY --parents ../source/*/pyproject.toml ${ISAACLAB_PATH}/
130+
131+ # The image's package set. Kit ships in the base image, so the teleop stack comes without the
132+ # ``isaacsim`` wheel; ``teleop`` itself is left exactly as tuned.
133+ ARG IMAGE_EXTRAS="--extra sb3 --extra skrl --extra rl-games --extra rsl-rl --extra viser --extra rerun --extra mimic --extra teleop --extra test"
122134
123- # HACK: Remove install of quadprog dependency
124- RUN ${ISAACLAB_PATH}/isaaclab.sh -p -m pip uninstall -y quadprog
135+
136+ RUN --mount=type=cache,target=/root/.cache/uv \
137+ cd ${ISAACLAB_PATH} && \
138+ uv sync --frozen --inexact ${IMAGE_EXTRAS} \
139+ --no-install-workspace --no-install-package imageio-ffmpeg && \
140+ chown -R isaaclab:isaaclab ${VIRTUAL_ENV} && \
141+ chmod -R a+rwX ${VIRTUAL_ENV}/lib ${VIRTUAL_ENV}/bin
142+
143+ COPY ../source/ ${ISAACLAB_PATH}/source/
144+
145+ # Fix the line endings for the shell scripts (Windows git may add \r), then restore the
146+ # launcher's mode; one layer because both walk the tree the COPY above just wrote. ``sed -i``
147+ # rewrites the file under the build umask, so an explicit 755 keeps it runnable by a
148+ # container started with a uid other than the image's own.
149+ RUN find ${ISAACLAB_PATH} -type f -name "*.sh" -exec sed -i 's/\r$//' {} + \
150+ && chmod 755 ${ISAACLAB_PATH}/isaaclab.sh
151+
152+ # Install apt dependencies for extensions that declare them in their extension.toml,
153+ RUN ${ISAACLAB_PATH}/isaaclab.sh -p ${ISAACLAB_PATH}/tools/install_deps.py apt ${ISAACLAB_PATH}/source && \
154+ apt-get -y autoremove && apt-get clean && \
155+ rm -rf /var/lib/apt/lists/*
156+
157+ # robomimic and moviepy declare imageio-ffmpeg for optional video helpers. Its bundled
158+ # FFmpeg binary stays out of the distributed image; users who enable video recording install
159+ # MoviePy and its video backend explicitly.
160+ RUN --mount=type=cache,target=/root/.cache/uv \
161+ cd ${ISAACLAB_PATH} && \
162+ uv sync --frozen --inexact ${IMAGE_EXTRAS} \
163+ --no-install-package imageio-ffmpeg && \
164+ find ${VIRTUAL_ENV} \! -user isaaclab \
165+ -exec chown isaaclab:isaaclab {} + -exec chmod a+rwX {} +
166+
167+ # Isaac Sim's prebundled packages sit on PYTHONPATH ahead of the venv for launch paths that
168+ # never import isaaclab (``runheadless.sh``, ``isaac-sim.streaming.sh``), so its torch would
169+ # shadow the venv's and load a libtorch_cuda.so needing an NCCL symbol the bundled NCCL does
170+ # not export (nvbugs 6343978). Repoint them at the venv; the pip install path did this itself.
171+ RUN ${ISAACLAB_PATH}/isaaclab.sh -p -c \
172+ "from isaaclab.cli.commands.install import _repoint_prebundle_packages; _repoint_prebundle_packages()"
173+
174+ # uv venv/uv sync above needed Kit's interpreter as the venv base; from here on ``uv pip``
175+ # should target the venv, including the interactive ``pip`` alias below.
176+ ENV UV_PYTHON=${VENV_PATH_ARG}/bin/python
125177
126178# aliasing isaaclab.sh and python for convenience
127179RUN echo "export ISAACLAB_PATH=${ISAACLAB_PATH}" >> ${DOCKER_USER_HOME}/.bashrc && \
128180 echo "alias isaaclab=${ISAACLAB_PATH}/isaaclab.sh" >> ${DOCKER_USER_HOME}/.bashrc && \
129- echo "alias python=${ISAACLAB_PATH}/_isaac_sim/python .sh" >> ${DOCKER_USER_HOME}/.bashrc && \
130- echo "alias python3=${ISAACLAB_PATH}/_isaac_sim/python .sh" >> ${DOCKER_USER_HOME}/.bashrc && \
131- echo "alias pip='${ISAACLAB_PATH}/_isaac_sim/python.sh -m pip'" >> ${DOCKER_USER_HOME}/.bashrc && \
132- echo "alias pip3='${ISAACLAB_PATH}/_isaac_sim/python.sh -m pip'" >> ${DOCKER_USER_HOME}/.bashrc && \
133- echo "alias tensorboard='${ISAACLAB_PATH}/_isaac_sim/python.sh ${ISAACLAB_PATH}/_isaac_sim /tensorboard'" >> ${DOCKER_USER_HOME}/.bashrc && \
181+ echo "alias python=' ${ISAACLAB_PATH}/isaaclab .sh -p' " >> ${DOCKER_USER_HOME}/.bashrc && \
182+ echo "alias python3=' ${ISAACLAB_PATH}/isaaclab .sh -p' " >> ${DOCKER_USER_HOME}/.bashrc && \
183+ echo "alias pip='uv pip'" >> ${DOCKER_USER_HOME}/.bashrc && \
184+ echo "alias pip3='uv pip'" >> ${DOCKER_USER_HOME}/.bashrc && \
185+ echo "alias tensorboard='${VIRTUAL_ENV}/bin /tensorboard'" >> ${DOCKER_USER_HOME}/.bashrc && \
134186 echo "export TZ=$(date +%Z)" >> ${DOCKER_USER_HOME}/.bashrc && \
135187 echo "shopt -s histappend" >> ${DOCKER_USER_HOME}/.bashrc && \
136188 echo "PROMPT_COMMAND='history -a'" >> ${DOCKER_USER_HOME}/.bashrc
137189
138- # Create the non-root runtime user after root-only image setup is complete.
139- # The uid/gid 1000 match GitHub runner bind mounts used by Docker tests.
140- # --non-unique is required because some base image revisions already carry
141- # another user or group at uid/gid 1000.
142- RUN groupadd --non-unique --gid 1000 isaaclab \
143- && useradd --non-unique --uid 1000 --gid 1000 -M -l -s /bin/bash -d ${DOCKER_USER_HOME} isaaclab
144-
145190# The Isaac Sim image sets HUB__ARGS__DETECT_ONLY=true, which forbids OmniHub from starting.
146191# omni.client still asks it to launch, so every Kit startup retries ~39 times before giving
147192# up. Letting OmniHub start also needs its cache at /var/cache/hub, owned by the isaac-sim
148193# user (uid 1234) upstream, to be writable by the uid 1000 runtime user.
149194ENV HUB__ARGS__DETECT_ONLY=false
150195
196+ # CI installs optional extras into the venv at test time (e.g. ``pytetwild`` for the
197+ # tetrahedralization feature) and a volume-mounted run executes as the runner's uid rather
198+ # than the image's, so the paths pip writes must be writable by a uid the image cannot know.
151199RUN chown -R isaaclab:isaaclab \
152200 ${ISAACLAB_PATH} \
153201 ${DOCKER_USER_HOME} \
0 commit comments