From 182a9df6fbcb5210bccbaa3c24d34a8f552d71aa Mon Sep 17 00:00:00 2001 From: Neel Jawale Date: Thu, 23 Apr 2026 10:17:03 -0700 Subject: [PATCH 1/7] Removes redundant USER root from Dockerfile.curobo The NVIDIA Isaac Sim base image declares no USER directive and defaults to root, so the explicit USER root in this Dockerfile is a no-op declaration. Removing it aligns Dockerfile.curobo with the upstream NVIDIA Isaac Sim Dockerfile and with the other Isaac Lab Dockerfiles (Dockerfile.ros2, Dockerfile.installci) which omit the directive. Build and runtime behavior are unchanged: the container still runs as root via the base image default. --- docker/Dockerfile.curobo | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker/Dockerfile.curobo b/docker/Dockerfile.curobo index 36072612f576..6e38e0f8f9fd 100644 --- a/docker/Dockerfile.curobo +++ b/docker/Dockerfile.curobo @@ -34,8 +34,6 @@ ENV DOCKER_USER_HOME=${DOCKER_USER_HOME_ARG} ENV LANG=C.UTF-8 ENV DEBIAN_FRONTEND=noninteractive -USER root - # Install dependencies RUN apt-get update && \ apt-get install -y --no-install-recommends \ From 3f9dfbec0fd31073d048e6cca122962b95a28114 Mon Sep 17 00:00:00 2001 From: Neel Jawale Date: Thu, 23 Apr 2026 10:25:15 -0700 Subject: [PATCH 2/7] Use ${HOME}/.bashrc consistently in Dockerfile.curobo --- docker/Dockerfile.curobo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile.curobo b/docker/Dockerfile.curobo index 6e38e0f8f9fd..65f4b13b7884 100644 --- a/docker/Dockerfile.curobo +++ b/docker/Dockerfile.curobo @@ -167,8 +167,8 @@ RUN echo "export ISAACLAB_PATH=${ISAACLAB_PATH}" >> ${HOME}/.bashrc && \ echo "alias pip3='${ISAACLAB_PATH}/_isaac_sim/python.sh -m pip'" >> ${HOME}/.bashrc && \ echo "alias tensorboard='${ISAACLAB_PATH}/_isaac_sim/python.sh ${ISAACLAB_PATH}/_isaac_sim/tensorboard'" >> ${HOME}/.bashrc && \ echo "export TZ=$(date +%Z)" >> ${HOME}/.bashrc && \ - echo "shopt -s histappend" >> /root/.bashrc && \ - echo "PROMPT_COMMAND='history -a'" >> /root/.bashrc + echo "shopt -s histappend" >> ${HOME}/.bashrc && \ + echo "PROMPT_COMMAND='history -a'" >> ${HOME}/.bashrc # copy the rest of the files COPY ../ ${ISAACLAB_PATH}/ From b0643e44a4564dadcca9147e77f073d814afb14d Mon Sep 17 00:00:00 2001 From: Neel Jawale Date: Thu, 23 Apr 2026 12:09:24 -0700 Subject: [PATCH 3/7] Drops privileges to isaac-sim for pip installs and runtime in Dockerfile.curobo The base image (nvcr.io/nvidian/isaac-sim:latest-develop) ends with USER isaac-sim (uid 1234). Previously the Dockerfile switched back to root for the entire build and left runtime as root via OMNI_KIT_ALLOW_ROOT. Removing the initial USER root broke CI because apt needs root; simply restoring it keeps us as root the whole way which is what we want to avoid. Correct pattern applied here: * Explicit USER root for the system-level setup block (apt, cuda repo, /bin/nvidia-* placeholders, /root cache dirs, get-pip bootstrap, COPYed source files). * After the last root-required step, chown only the paths we are about to write to from the non-root user (${ISAACLAB_PATH}, site-packages, ${DOCKER_USER_HOME}) to isaac-sim:isaac-sim. /isaac-sim itself is already isaac-sim-owned from the base image, so re-chowning it would needlessly bloat the layer. * USER isaac-sim before the editable pip installs (isaaclab.sh --install, cuRobo, isaaclab_teleop) and the bashrc writes, so those paths end up owned by the runtime user. * BuildKit cache mount at ${DOCKER_USER_HOME}/.cache/pip pinned to uid=1234,gid=1234, otherwise it would default to root-owned and isaac-sim could not write into it. * Final COPY annotated with --chown=isaac-sim:isaac-sim for consistency. This keeps CI build args (DOCKER_USER_HOME_ARG=/root) and docker-compose paths unchanged, so docker-compose usage continues to work with OMNI_KIT_ALLOW_ROOT. Dockerfile.base and Dockerfile.ros2 follow the same legacy pattern and are out of scope for this PR. --- docker/Dockerfile.curobo | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile.curobo b/docker/Dockerfile.curobo index 65f4b13b7884..92f018e2d6c5 100644 --- a/docker/Dockerfile.curobo +++ b/docker/Dockerfile.curobo @@ -34,6 +34,10 @@ ENV DOCKER_USER_HOME=${DOCKER_USER_HOME_ARG} ENV LANG=C.UTF-8 ENV DEBIAN_FRONTEND=noninteractive +# Base image ends with USER isaac-sim (uid 1234); switch to root for +# system-level setup (apt, cuda, /bin/nvidia-* placeholders, etc.). +USER root + # Install dependencies RUN apt-get update && \ apt-get install -y --no-install-recommends \ @@ -143,9 +147,20 @@ RUN rm -rf ${ISAACSIM_ROOT_PATH}/kit/python/lib/python3.12/site-packages/pip* && ${ISAACLAB_PATH}/_isaac_sim/kit/python/bin/python3 get-pip.py && \ rm get-pip.py +# Hand workspace ownership to the non-root runtime user (isaac-sim, uid 1234, +# provisioned by the base image) and drop privileges. Only the paths we still +# need to write to are chowned; /isaac-sim itself is already isaac-sim-owned +# from the base image, so re-chowning it would needlessly bloat the layer. +RUN chown -R isaac-sim:isaac-sim \ + ${ISAACLAB_PATH} \ + ${ISAACSIM_ROOT_PATH}/kit/python/lib/python3.12/site-packages \ + ${DOCKER_USER_HOME} + +USER isaac-sim + # installing Isaac Lab dependencies # use pip caching to avoid reinstalling large packages -RUN --mount=type=cache,target=${DOCKER_USER_HOME}/.cache/pip \ +RUN --mount=type=cache,target=${DOCKER_USER_HOME}/.cache/pip,uid=1234,gid=1234 \ ${ISAACLAB_PATH}/isaaclab.sh --install # HACK: Uninstall quadprog as it causes issues with some reinforcement learning frameworks @@ -171,7 +186,7 @@ RUN echo "export ISAACLAB_PATH=${ISAACLAB_PATH}" >> ${HOME}/.bashrc && \ echo "PROMPT_COMMAND='history -a'" >> ${HOME}/.bashrc # copy the rest of the files -COPY ../ ${ISAACLAB_PATH}/ +COPY --chown=isaac-sim:isaac-sim ../ ${ISAACLAB_PATH}/ # make working directory as the Isaac Lab directory # this is the default directory when the container is run From 61f1fd76f643a2ed46b4d58b2a4b2af622c75833 Mon Sep 17 00:00:00 2001 From: Neel Jawale Date: Fri, 24 Apr 2026 15:47:01 -0700 Subject: [PATCH 4/7] Runs Dockerfile.curobo as new isaaclab user (uid 1000) for bind-mount compat The previous attempt switched to the base image's isaac-sim user (uid 1234), which broke the test step in CI: the test runner bind-mounts the host workspace (/opt/github-runner/_work/.../IsaacLab, owned by uid 1000) over /workspace/isaaclab, overlaying our build-time chown. As uid 1234, the container could not write to the bind mount and `mkdir tests` failed before pytest even started. Fix: create a fresh isaaclab user with uid 1000 to match the runner host user, instead of remapping isaac-sim. Creating a new user (rather than remapping isaac-sim's UID) avoids triggering an OverlayFS copy-up of the multi-GB Isaac Sim install in /isaac-sim, which would otherwise become orphan-owned. /isaac-sim stays mode 755 and isaac-sim-owned, so isaaclab can still read and execute everything there. Only the paths the new user needs to write to (${ISAACLAB_PATH}, kit site-packages, ${DOCKER_USER_HOME}, /home/isaaclab) are chowned. Also bumps the BuildKit pip cache mount uid/gid from 1234 to 1000 (numeric form is required because BuildKit does not consult /etc/passwd at mount time) and switches the final COPY --chown to isaaclab. Caveat: hardcodes uid 1000, matching the typical EC2 self-hosted runner user. If a runner is reconfigured to a different uid this will break the same way; in that case the right next step is to parameterize via build arg, but until then 1000 is the simplest correct value. --- docker/Dockerfile.curobo | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/docker/Dockerfile.curobo b/docker/Dockerfile.curobo index 92f018e2d6c5..254a62f70799 100644 --- a/docker/Dockerfile.curobo +++ b/docker/Dockerfile.curobo @@ -147,20 +147,26 @@ RUN rm -rf ${ISAACSIM_ROOT_PATH}/kit/python/lib/python3.12/site-packages/pip* && ${ISAACLAB_PATH}/_isaac_sim/kit/python/bin/python3 get-pip.py && \ rm get-pip.py -# Hand workspace ownership to the non-root runtime user (isaac-sim, uid 1234, -# provisioned by the base image) and drop privileges. Only the paths we still -# need to write to are chowned; /isaac-sim itself is already isaac-sim-owned -# from the base image, so re-chowning it would needlessly bloat the layer. -RUN chown -R isaac-sim:isaac-sim \ +# Create a non-root runtime user with uid 1000 to match the CI runner host +# user that owns the bind-mounted /workspace/isaaclab. Without this match, +# runtime `mkdir tests` etc. fail with EACCES on the bind-mount overlay. +# The base image already provisions an isaac-sim user (uid 1234) which keeps +# ownership of /isaac-sim; we only chown the paths the new user needs to +# write to so we don't trigger an OverlayFS copy-up of the multi-GB Isaac +# Sim install. /isaac-sim stays mode 755 so isaaclab can still read+exec it. +RUN useradd -ms /bin/bash -l --uid 1000 isaaclab -d /home/isaaclab + +RUN chown -R isaaclab:isaaclab \ ${ISAACLAB_PATH} \ ${ISAACSIM_ROOT_PATH}/kit/python/lib/python3.12/site-packages \ - ${DOCKER_USER_HOME} + ${DOCKER_USER_HOME} \ + /home/isaaclab -USER isaac-sim +USER isaaclab # installing Isaac Lab dependencies # use pip caching to avoid reinstalling large packages -RUN --mount=type=cache,target=${DOCKER_USER_HOME}/.cache/pip,uid=1234,gid=1234 \ +RUN --mount=type=cache,target=${DOCKER_USER_HOME}/.cache/pip,uid=1000,gid=1000 \ ${ISAACLAB_PATH}/isaaclab.sh --install # HACK: Uninstall quadprog as it causes issues with some reinforcement learning frameworks @@ -186,7 +192,7 @@ RUN echo "export ISAACLAB_PATH=${ISAACLAB_PATH}" >> ${HOME}/.bashrc && \ echo "PROMPT_COMMAND='history -a'" >> ${HOME}/.bashrc # copy the rest of the files -COPY --chown=isaac-sim:isaac-sim ../ ${ISAACLAB_PATH}/ +COPY --chown=isaaclab:isaaclab ../ ${ISAACLAB_PATH}/ # make working directory as the Isaac Lab directory # this is the default directory when the container is run From fc7c60568c44d8650ce95c4f6d5560c77c157190 Mon Sep 17 00:00:00 2001 From: Neel Jawale Date: Sun, 26 Apr 2026 20:26:09 -0700 Subject: [PATCH 5/7] Allows non-unique uid when creating isaaclab user in Dockerfile.curobo The pinned cuRobo CI base image (nvcr.io/nvidian/isaac-sim:latest-develop) recently rolled forward to a digest that already provisions a user at uid 1000, causing the previous `useradd ... --uid 1000 isaaclab` to fail with `useradd: UID 1000 is not unique`. Adds --non-unique (-o) so the new isaaclab user can share uid 1000 with the existing one. File ownership only cares about the numeric uid, so chown of the writable paths to isaaclab:isaaclab still produces uid 1000 files. Build-time ${HOME}/.bashrc writes still resolve to /home/isaaclab because USER isaaclab triggers a getpwnam lookup by name, returning our entry rather than the colliding one. --- docker/Dockerfile.curobo | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile.curobo b/docker/Dockerfile.curobo index 254a62f70799..d54bcf7f9926 100644 --- a/docker/Dockerfile.curobo +++ b/docker/Dockerfile.curobo @@ -154,7 +154,12 @@ RUN rm -rf ${ISAACSIM_ROOT_PATH}/kit/python/lib/python3.12/site-packages/pip* && # ownership of /isaac-sim; we only chown the paths the new user needs to # write to so we don't trigger an OverlayFS copy-up of the multi-GB Isaac # Sim install. /isaac-sim stays mode 755 so isaaclab can still read+exec it. -RUN useradd -ms /bin/bash -l --uid 1000 isaaclab -d /home/isaaclab +# --non-unique (-o) is required because some base image revisions already +# carry a user at uid 1000 (e.g. an `ubuntu`/`kit`-style account); the file +# system only cares about the numeric uid, so two names mapping to it is +# fine. Resolving "isaaclab" via /etc/passwd still returns our /home/isaaclab +# entry, so build-time ${HOME}/.bashrc writes land where we own the path. +RUN useradd -m -l -s /bin/bash --non-unique --uid 1000 isaaclab -d /home/isaaclab RUN chown -R isaaclab:isaaclab \ ${ISAACLAB_PATH} \ From 02a97eb18cc917e857d1752aaab9b92dae1ac37a Mon Sep 17 00:00:00 2001 From: Neel Jawale Date: Sun, 26 Apr 2026 20:36:27 -0700 Subject: [PATCH 6/7] Pins isaaclab gid to 1000 with explicit groupadd in Dockerfile.curobo `useradd --non-unique --uid 1000` only relaxes the UID uniqueness check; it does not help with the implicit groupadd that useradd runs internally to create the user's primary group. On base image revisions that already carry a group at gid 1000 (likely whenever a uid-1000 user also exists), that internal groupadd either fails outright or silently picks a different gid like 1001, leaving the BuildKit pip cache mount's `gid=1000` mismatched against the user's primary group. Fix: explicitly groupadd the isaaclab group with --non-unique --gid 1000 before useradd, and pass --gid 1000 to useradd so it reuses the new group as the user's primary group instead of auto-creating a per-user group. This deterministically lands isaaclab at uid 1000 / gid 1000 regardless of collisions in the base image. --- docker/Dockerfile.curobo | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/docker/Dockerfile.curobo b/docker/Dockerfile.curobo index d54bcf7f9926..3d17e82365bf 100644 --- a/docker/Dockerfile.curobo +++ b/docker/Dockerfile.curobo @@ -147,19 +147,26 @@ RUN rm -rf ${ISAACSIM_ROOT_PATH}/kit/python/lib/python3.12/site-packages/pip* && ${ISAACLAB_PATH}/_isaac_sim/kit/python/bin/python3 get-pip.py && \ rm get-pip.py -# Create a non-root runtime user with uid 1000 to match the CI runner host -# user that owns the bind-mounted /workspace/isaaclab. Without this match, -# runtime `mkdir tests` etc. fail with EACCES on the bind-mount overlay. -# The base image already provisions an isaac-sim user (uid 1234) which keeps -# ownership of /isaac-sim; we only chown the paths the new user needs to -# write to so we don't trigger an OverlayFS copy-up of the multi-GB Isaac -# Sim install. /isaac-sim stays mode 755 so isaaclab can still read+exec it. -# --non-unique (-o) is required because some base image revisions already -# carry a user at uid 1000 (e.g. an `ubuntu`/`kit`-style account); the file -# system only cares about the numeric uid, so two names mapping to it is -# fine. Resolving "isaaclab" via /etc/passwd still returns our /home/isaaclab -# entry, so build-time ${HOME}/.bashrc writes land where we own the path. -RUN useradd -m -l -s /bin/bash --non-unique --uid 1000 isaaclab -d /home/isaaclab +# Create a non-root runtime user with uid/gid 1000 to match the CI runner +# host user that owns the bind-mounted /workspace/isaaclab. Without this +# match, runtime `mkdir tests` etc. fail with EACCES on the bind-mount +# overlay. The base image already provisions an isaac-sim user (uid 1234) +# which keeps ownership of /isaac-sim; we only chown the paths the new user +# needs to write to so we don't trigger an OverlayFS copy-up of the multi-GB +# Isaac Sim install. /isaac-sim stays mode 755 so isaaclab can still +# read+exec it. +# +# --non-unique (-o) on both groupadd and useradd is required because some +# base image revisions already carry a group/user at gid/uid 1000 (e.g. an +# `ubuntu`/`kit`-style account). The file system only cares about numeric +# IDs, so two names mapping to the same numeric ID is fine; resolving +# "isaaclab" via /etc/passwd still returns our /home/isaaclab entry, so +# build-time ${HOME}/.bashrc writes land where we own the path. We pin the +# group's GID explicitly to 1000 (instead of letting useradd auto-pick one +# when the default GID is taken) so that the BuildKit pip cache mount's +# `gid=1000` matches the user's primary group. +RUN groupadd --non-unique --gid 1000 isaaclab \ + && useradd --non-unique --uid 1000 --gid 1000 -m -l -s /bin/bash -d /home/isaaclab isaaclab RUN chown -R isaaclab:isaaclab \ ${ISAACLAB_PATH} \ From e0e7b861c50891cd603e2824cc2742727fc876dd Mon Sep 17 00:00:00 2001 From: Neel Jawale Date: Sun, 26 Apr 2026 21:08:49 -0700 Subject: [PATCH 7/7] Opens up /isaac-sim traversal for the isaaclab user in Dockerfile.curobo The base image creates /isaac-sim as isaac-sim's home directory; on recent Ubuntu (HOME_MODE 0700 in /etc/login.defs) that means mode 0700 isaac-sim:isaac-sim, which our new isaaclab user cannot enter. The `[ -f $ISAACLAB_PATH/_isaac_sim/python.sh ]` test in isaaclab.sh then fails, falls through to the system `python3` fallback, and the isaaclab.sh --install step dies with `exec: python3: not found`. Fix: chmod 755 /isaac-sim so non-owner users can traverse the directory to reach python.sh and the rest of the Kit runtime. Files inside retain their original permissions (NVIDIA ships them world-readable as part of their standard distribution layout), so only the top-level directory's mode needs to change. This is a single-inode metadata change; no OverlayFS copy-up of the multi-GB tree. If a future build surfaces a deeper permission issue inside /isaac-sim, escalate to `chmod -R o+rX ${ISAACSIM_ROOT_PATH}` -- accepts the layer cost in exchange for blanket access. --- docker/Dockerfile.curobo | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docker/Dockerfile.curobo b/docker/Dockerfile.curobo index 3d17e82365bf..c4bb7b9ae89c 100644 --- a/docker/Dockerfile.curobo +++ b/docker/Dockerfile.curobo @@ -174,6 +174,16 @@ RUN chown -R isaaclab:isaaclab \ ${DOCKER_USER_HOME} \ /home/isaaclab +# Open up traversal of ${ISAACSIM_ROOT_PATH} for non-owner users. The base +# image creates /isaac-sim as the isaac-sim user's home directory, which on +# recent Ubuntu defaults to mode 0700 (HOME_MODE in /etc/login.defs). Without +# at least exec for "other", isaaclab cannot enter the directory to find +# python.sh or the Kit runtime, and `isaaclab.sh -p` falls back to the +# nonexistent system python3. Inner files retain their original perms (NVIDIA +# ships them world-readable per standard distribution conventions), so we +# only need to relax the top-level directory. +RUN chmod 755 ${ISAACSIM_ROOT_PATH} + USER isaaclab # installing Isaac Lab dependencies