Skip to content

Make Isaac Lab Docker images run as non-root#5618

Merged
kellyguo11 merged 8 commits into
isaac-sim:developfrom
sheikh-nv:sheikh-nv/curobo-non-root
May 16, 2026
Merged

Make Isaac Lab Docker images run as non-root#5618
kellyguo11 merged 8 commits into
isaac-sim:developfrom
sheikh-nv:sheikh-nv/curobo-non-root

Conversation

@sheikh-nv

@sheikh-nv sheikh-nv commented May 14, 2026

Copy link
Copy Markdown
Contributor

Description

Makes the Isaac Lab base, ROS 2, and cuRobo Docker images run as a non-root runtime user by creating an isaaclab user after root-only setup and switching the final images to USER isaaclab.

The ROS 2 Dockerfile temporarily switches back to USER root for apt-based ROS setup, then restores USER isaaclab for runtime. The installci Dockerfile is intentionally unchanged.

This also updates deprecated Isaac Sim Dockerfile comments to point to the NGC Isaac Sim container page, removes the default root-allowance compose setting, updates Docker documentation, and adds CI coverage to verify the built base and cuRobo images do not run as root by default. ROS 2 is covered by the static Dockerfile regression test because this workflow does not build a ROS 2 image.

Fixes: N/A

Validation:

  • ./isaaclab.sh -f
  • git diff --check
  • docker run ... /isaac-sim/python.sh -m pytest docker/test/test_dockerfile_nonroot.py -q -> 7 passed, 1 skipped
  • Manual cuRobo runtime check confirmed non-root uid=1000

Type of change

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

Screenshots

N/A

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 (not applicable; no source/<pkg>/ package touched)
  • I have added my name to the CONTRIBUTORS.md or my name already exists there

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Isaac Lab Review Bot

Summary

This PR implements Docker security best practices by converting the cuRobo image to run as a non-root user (isaaclab with uid/gid 1000). The change includes proper CI verification and comprehensive regression tests to prevent future regressions.

Design Assessment

Architecture: ✅ Well-structured
The implementation follows the correct Docker pattern: perform all root-required setup first, create the non-root user, adjust ownership, then switch to USER isaaclab at the end. The use of --non-unique for uid/gid handles potential conflicts with base image users gracefully.

CI Integration: ✅ Solid
The new verify-curobo-non-root job provides defense-in-depth with both static Dockerfile analysis (pytest) and runtime identity verification.

Findings

🟡 Minor Suggestions

  1. docker/Dockerfile.curobo — Hardcoded uid/gid 1000
    The comment explains this matches GitHub runner bind mounts, which is appropriate for CI. However, users running locally may have different host uids. Consider documenting this in the container usage docs or providing a --user override example.

  2. .github/workflows/build.yaml:262-271 — First run step lacks explicit error handling
    While pytest exit codes propagate correctly, adding set -euo pipefail at the start of the shell block would match the defensive style used in the second step.

✅ Good Patterns Observed

  • --non-unique flag handles edge case where uid/gid 1000 already exists in base image
  • chmod 755 ${ISAACSIM_ROOT_PATH} opens traversal without recursively changing inner file permissions
  • DOCKERFILE_RUNTIME_USERS mapping forces explicit decisions for new Dockerfiles (regression protection)
  • Runtime verification (id -u != 0) as separate CI step provides independent validation

Test Coverage

Coverage: ✅ Good

  • Static analysis tests verify Dockerfile structure
  • Runtime CI job verifies actual container behavior
  • test_all_dockerfiles_have_runtime_user_expectations() prevents new Dockerfiles from escaping review

Minor gap: No direct test that the non-root user can actually execute Isaac Lab scripts (though the pytest run itself serves as an indirect functional test).

CI Status

  • ⚠️ pre-commit: failed (may be unrelated to this PR — please verify)
  • 🔄 Docker builds: pending
  • ✅ Changelog, links, labeler: passed

Verdict

Minor fixes needed — The implementation is sound and follows security best practices. Address the pre-commit failure and consider the minor suggestions above. Once CI passes, this is ready to ship. 🚀


Update (e9f0135): Reviewed incremental changes. The implementation is complete and well-structured. The .bashrc path fix (using ${DOCKER_USER_HOME} instead of hardcoded /root) is a good catch. Previous minor suggestions (uid/gid docs, set -euo pipefail) are non-blocking. No new issues found. ✅


Update (b772981): Both minor suggestions from my initial review have been addressed:

  • ✅ Added documentation in docs/source/deployment/docker.rst explaining uid/gid 1000 and the --user override option
  • ✅ Added set -euo pipefail to the CI shell blocks

No new issues found. LGTM! 🚀


Update (81b2b4e): PR scope expanded — base and ROS2 images now also run as non-root user. Changes:

  • Dockerfile.base — Creates isaaclab user (uid/gid 1000), sets correct ownership, switches to non-root at end
  • Dockerfile.ros2 — Properly handles non-root base: USER root for apt setup, restores USER isaaclab at end
  • docker-compose.yaml — Removed OMNI_KIT_ALLOW_ROOT=1 (no longer needed)
  • ✅ New verify-base-non-root CI job mirrors the cuRobo verification pattern
  • ✅ Test coverage updated: DOCKERFILE_RUNTIME_USERS map reflects new expectations, new test_ros2_dockerfile_restores_non_root_runtime_user() test added
  • ✅ Documentation updated consistently

Implementation is consistent with the previously approved cuRobo pattern. No new issues found. LGTM! 🚀


Update (c9b944a): CI test action updated to handle volume-mounted containers correctly:

  • ✅ When bind-mounting source code, container now runs as host uid/gid with --user ${host_uid}:${host_gid}
  • ✅ Proper environment variables set (HOME, XDG_CACHE_HOME, USER, LOGNAME) for the mounted user
  • This ensures correct file ownership and prevents permission issues with the mounted volume

Good fix for CI compatibility with the non-root images. No issues found. ✅


Update (d1acb0f): Enhanced CI runtime storage for volume-mounted containers:

  • ✅ Creates dedicated docker_runtime_dir with full Kit/Omniverse directory hierarchy
  • ✅ Mounts writable volumes for cache, config, data, and logs (mirrors compose/singularity setup)
  • ✅ Adds XDG_DATA_HOME environment variable for proper XDG compliance
  • ✅ Cleanup logic in both trap handler and normal exit path

This addresses Kit writing generated files outside the source tree — provides proper writable storage for the non-root user in CI. Well done! ✅

@sheikh-nv sheikh-nv force-pushed the sheikh-nv/curobo-non-root branch 2 times, most recently from e9f0135 to b772981 Compare May 14, 2026 16:42
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label May 14, 2026
@sheikh-nv

Copy link
Copy Markdown
Contributor Author

Addressed the review-bot suggestions in b772981:

  • added set -euo pipefail to the Dockerfile non-root regression step
  • documented the cuRobo non-root uid/gid behavior and docker run --user "$(id -u):$(id -g)" override for local bind mounts

Pre-commit is passing; Docker/docs/license checks are still running.

@sheikh-nv sheikh-nv changed the title Make cuRobo Docker image run as non-root Make Isaac Lab Docker images run as non-root May 14, 2026
@sheikh-nv sheikh-nv marked this pull request as ready for review May 15, 2026 03:31
@greptile-apps

greptile-apps Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR migrates the Isaac Lab base, ros2, and curobo Docker images from running as root to a dedicated isaaclab user at uid/gid 1000, with corresponding CI jobs and a static regression test to prevent future regressions. The installci image is intentionally left on root, and OMNI_KIT_ALLOW_ROOT=1 is removed from the compose environment since it is no longer needed.

  • Dockerfile changes (base, curobo): after all root-only installation steps, a new isaaclab user is created (--non-unique, uid/gid 1000, home at /root), ownership of ISAACLAB_PATH and DOCKER_USER_HOME is transferred with chown -R, and the final USER isaaclab instruction is added. Dockerfile.ros2 brackets the apt installation with USER root / USER isaaclab.
  • CI additions: two new verify-*-non-root workflow jobs assert the runtime uid is non-zero; action.yml in volume-mount mode now creates a writable docker_runtime_dir tree on the runner and runs the container as the host uid with overridden HOME/XDG variables.
  • Test: docker/test/test_dockerfile_nonroot.py statically verifies each Dockerfile's final USER directive and enforces that any newly added Dockerfile must declare an explicit runtime user.

Confidence Score: 3/5

Safe to merge on a clean install; breaks existing deployments with root-owned named volumes until they are manually pruned.

The code changes themselves are structurally correct and the new non-root images work as advertised on a fresh setup. The gap is operational: users who already have running deployments with persistent Docker named volumes will get permission errors on first launch because those volumes retain root ownership. The upgrade path is undocumented in the added docker.rst note.

docs/source/deployment/docker.rst needs a migration note for existing named volumes; docker/Dockerfile.base and docker/Dockerfile.curobo have the chmod 755 /root concern.

Security Review

  • /root world-traversal (docker/Dockerfile.base, docker/Dockerfile.curobo): chmod 755 ${DOCKER_USER_HOME} opens /root to read and traverse by any uid inside the container. While low-impact in a single-user container, any secrets or credentials written to /root by other image layers would become readable to processes running as a different uid. No other security issues identified.

Important Files Changed

Filename Overview
docker/Dockerfile.base Adds non-root runtime user isaaclab at uid/gid 1000 with home=/root; applies chmod 755 to /root (weakening its standard 700 perms) and chown -R to transfer ownership
docker/Dockerfile.curobo Mirror of Dockerfile.base non-root changes; adds isaaclab user, chown, chmod 755 on /root, and USER isaaclab at end
docker/Dockerfile.ros2 Adds USER root at start and USER isaaclab at end to bracket the ROS 2 apt installation; uses --chown=isaaclab:isaaclab for COPY
docker/docker-compose.yaml Removes OMNI_KIT_ALLOW_ROOT=1 from shared environment; existing named Docker volumes retain root ownership and will be unwritable by uid 1000 on upgrade
.github/actions/run-tests/action.yml Adds writable bind-mount tree under RUNNER_TEMP for volume-mount mode, runs container with host uid:gid, and overrides HOME/XDG env vars to avoid writes to /root
.github/workflows/build.yaml Adds verify-base-non-root and verify-curobo-non-root CI jobs that pull images and assert runtime uid != 0
docker/test/test_dockerfile_nonroot.py New static regression test: verifies final USER directive, user creation commands, and ROS 2 root→isaaclab sequence; enforces explicit runtime-user declaration for any new Dockerfiles
docs/source/deployment/docker.rst Adds note about uid/gid 1000 and --user flag; missing guidance on pruning existing root-owned named volumes before upgrading

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[FROM isaacsim base] --> B[USER root]
    B --> C[apt-get install deps]
    C --> D[COPY source files]
    D --> E[pip install Isaac Lab deps]
    E --> F[Write .bashrc aliases to DOCKER_USER_HOME]
    F --> G[groupadd/useradd isaaclab uid=1000 gid=1000 home=/root]
    G --> H[chown -R isaaclab:isaaclab ISAACLAB_PATH + DOCKER_USER_HOME]
    H --> I[chmod 755 ISAACSIM_ROOT_PATH + DOCKER_USER_HOME]
    I --> J[COPY --chown=isaaclab:isaaclab ../ to ISAACLAB_PATH]
    J --> K[USER isaaclab]
    K --> L[WORKDIR ISAACLAB_PATH]

    subgraph ROS2 Overlay
        M[FROM isaac-lab-base] --> N[USER root]
        N --> O[apt-get install ROS 2 Humble]
        O --> P[COPY --chown=isaaclab:isaaclab .ros/]
        P --> Q[USER isaaclab]
    end

    subgraph cuRobo Overlay
        R[FROM isaacsim base] --> S[same root setup as base]
        S --> T[pip install cuRobo extras]
        T --> U[groupadd/useradd isaaclab uid=1000]
        U --> V[USER isaaclab]
    end
Loading

Reviews (1): Last reviewed commit: "Fix Docker test runtime mounts for non-r..." | Re-trigger Greptile

Comment thread docs/source/deployment/docker.rst Outdated
Comment on lines +105 to +108
The Isaac Lab base, ROS 2, and cuRobo images run as a non-root user with uid/gid 1000 to keep
bind-mounted workspaces writable on GitHub runners. If you run one of these images directly with
``docker run`` and your host uid/gid differs, pass Docker's ``--user "$(id -u):$(id -g)"`` option so
new files on bind mounts are owned by your host user.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Upgrade path breaks existing named volumes

The new note covers the --user flag for docker run, but omits a more impactful case: users upgrading from the root-based images with existing Docker named volumes (isaac-cache-kit, isaac-cache-ov, isaac-cache-compute, etc.). Named volumes persist their data across image rebuilds and are not re-initialised when the image changes. Old volumes contain root-owned files; the isaaclab user at uid 1000 cannot write to them, so Isaac Sim will fail on first launch with permission errors until the volumes are recreated. The note should direct existing users to prune the affected volumes before running the new image (e.g., docker volume rm isaac-cache-ov isaac-cache-pip isaac-cache-compute … or docker volume prune --filter label=com.docker.compose.project=…).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — Documentation now includes upgrade instructions with docker compose ... down --volumes for users migrating from root-based images.

Comment thread docker/Dockerfile.base
Comment on lines +149 to +154
# Open up traversal of the Isaac Sim root and runtime home for non-root users.
# Inner Isaac Sim files keep their original permissions, so avoid chowning the
# full install.
RUN chmod 755 \
${ISAACSIM_ROOT_PATH} \
${DOCKER_USER_HOME}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 security chmod 755 on ${DOCKER_USER_HOME} (which resolves to /root) relaxes the standard 700 permissions on the root home directory. With 755, any process running inside the container as a different uid can traverse into /root and read its contents. In a single-user container this is low-risk, but it is a deliberate security relaxation worth making explicit. Consider 750 to allow group traversal (isaaclab group) while blocking world access, or document why 755 is required.

Suggested change
# Open up traversal of the Isaac Sim root and runtime home for non-root users.
# Inner Isaac Sim files keep their original permissions, so avoid chowning the
# full install.
RUN chmod 755 \
${ISAACSIM_ROOT_PATH} \
${DOCKER_USER_HOME}
# Open up traversal of the Isaac Sim root and runtime home for non-root users.
# Inner Isaac Sim files keep their original permissions, so avoid chowning the
# full install.
# 750: owner (isaaclab) rwx, group r-x, world ---
RUN chmod 755 \
${ISAACSIM_ROOT_PATH} \
&& chmod 750 \
${DOCKER_USER_HOME}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — Changed to chmod 750 for ${DOCKER_USER_HOME}, restricting world access while allowing group traversal.

Comment thread docker/Dockerfile.curobo
Comment on lines +204 to +209
# Open up traversal of the Isaac Sim root and runtime home for non-root users.
# Inner Isaac Sim files keep their original permissions, so avoid chowning the
# full install.
RUN chmod 755 \
${ISAACSIM_ROOT_PATH} \
${DOCKER_USER_HOME}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 security Same chmod 755 on ${DOCKER_USER_HOME} (/root) as in Dockerfile.base — makes /root world-traversable. Consider 750 for consistency with any hardening applied to the base Dockerfile.

Suggested change
# Open up traversal of the Isaac Sim root and runtime home for non-root users.
# Inner Isaac Sim files keep their original permissions, so avoid chowning the
# full install.
RUN chmod 755 \
${ISAACSIM_ROOT_PATH} \
${DOCKER_USER_HOME}
# Open up traversal of the Isaac Sim root and runtime home for non-root users.
# Inner Isaac Sim files keep their original permissions, so avoid chowning the
# full install.
RUN chmod 755 \
${ISAACSIM_ROOT_PATH} \
&& chmod 750 \
${DOCKER_USER_HOME}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — Changed to chmod 750 for ${DOCKER_USER_HOME}, consistent with the hardening applied to Dockerfile.base.

sheikh-nv added 5 commits May 15, 2026 03:42
Signed-off-by: sheikh-nv <7774242+sheikh-nv@users.noreply.github.com>
Extend the non-root runtime user setup to the base image, restore the runtime user after ROS2 apt setup, and update docs, compose defaults, and CI coverage for the expanded scope.
Mount writable runtime storage for volume-mounted Docker CI jobs so Kit cache, config, data, and log files do not write into the Isaac Sim install tree while tests run as the host uid/gid.
@sheikh-nv sheikh-nv force-pushed the sheikh-nv/curobo-non-root branch from d1acb0f to 88400eb Compare May 15, 2026 03:47
@isaaclab-review-bot

Copy link
Copy Markdown

Update (88400eb): All previous inline review comments have been addressed:

  • ✅ Documentation now includes upgrade path guidance for recreating named volumes (docker compose ... down --volumes)
  • chmod 755 on ${DOCKER_USER_HOME} changed to chmod 750 in both Dockerfile.base and Dockerfile.curobo

No new issues found. LGTM 👍

@isaaclab-review-bot

Copy link
Copy Markdown

🤖 Incremental Review Update (c34c247)

Reviewed the new commits since my last review (88400ebc34c247). Changes include:

  • CUDA startup crash fix (deferring torch.cuda.set_device until after SimulationApp)
  • Version bumps (isaaclab 5.2.1, isaaclab_newton 0.9.1, isaaclab_physx 0.7.1)
  • Changelog consolidation from .d/ files
  • LEAPP export script refactoring for better modularity
  • Test improvements (batched exports, removed unnecessary AppLauncher usage)
  • CI improvements (leapp diagnostics, HUB__ARGS__DETECT_ONLY handling)

No new issues in the incremental changes.

Previous concerns remain open (original inline comments still visible):

  • P1: Named volumes upgrade path documentation (docker.rst)
  • P2: chmod 755 on /root in both Dockerfiles

@kellyguo11 kellyguo11 moved this to Ready to merge in Isaac Lab May 15, 2026
@github-actions github-actions Bot added the isaac-lab Related to Isaac Lab team label May 15, 2026
@isaaclab-review-bot

Copy link
Copy Markdown

🤖 Incremental Review: f9e8322

Commit: Adjust CI Kit startup threshold

Changes:

  • Introduces separate timeout constants: _LOCAL_STARTUP_TIME_LIMIT = 15.0 (local) vs _CI_STARTUP_TIME_LIMIT = 20.0 (CI)
  • Uses GITHUB_ACTIONS environment variable to detect CI environment
  • Adds clearer assertion error message showing elapsed vs limit times

Assessment: ✅ Looks Good

This is a sensible adjustment. The non-root Docker images use isolated writable runtime/cache mounts in CI, which results in colder startup than reused local caches. The 5-second buffer (15s→20s) accounts for this appropriately.

Code quality:

  • ✅ Constants are clearly named and self-documenting
  • ✅ Comment explains the rationale (isolated writable mounts for non-root users)
  • ✅ Improved assertion message aids debugging

No new issues found. Previous inline review comments still apply. 🚀

@kellyguo11 kellyguo11 merged commit 32462c0 into isaac-sim:develop May 16, 2026
120 of 132 checks passed
@github-project-automation github-project-automation Bot moved this from Ready to merge to Done in Isaac Lab May 16, 2026
matthewtrepte pushed a commit to matthewtrepte/IsaacLab that referenced this pull request May 18, 2026
# Description

Makes the Isaac Lab base, ROS 2, and cuRobo Docker images run as a
non-root runtime user by creating an `isaaclab` user after root-only
setup and switching the final images to `USER isaaclab`.

The ROS 2 Dockerfile temporarily switches back to `USER root` for
apt-based ROS setup, then restores `USER isaaclab` for runtime. The
`installci` Dockerfile is intentionally unchanged.

This also updates deprecated Isaac Sim Dockerfile comments to point to
the NGC Isaac Sim container page, removes the default root-allowance
compose setting, updates Docker documentation, and adds CI coverage to
verify the built base and cuRobo images do not run as root by default.
ROS 2 is covered by the static Dockerfile regression test because this
workflow does not build a ROS 2 image.

Fixes: N/A

Validation:
- `./isaaclab.sh -f`
- `git diff --check`
- `docker run ... /isaac-sim/python.sh -m pytest
docker/test/test_dockerfile_nonroot.py -q` -> `7 passed, 1 skipped`
- Manual cuRobo runtime check confirmed non-root `uid=1000`

## Type of change

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

## Screenshots

N/A

## Checklist

- [x] I have read and understood the [contribution
guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html)
- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] I have added a changelog fragment under
`source/<pkg>/changelog.d/` for every touched package (not applicable;
no `source/<pkg>/` package touched)
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there

---------

Signed-off-by: sheikh-nv <7774242+sheikh-nv@users.noreply.github.com>
Co-authored-by: Kelly Guo <kellyg@nvidia.com>
AntoineRichard pushed a commit that referenced this pull request Jun 9, 2026
# Description

Since the non-root Docker migration
(#5618, first shipped in
3.0.0-beta2), the
container runs as user isaaclab (uid/gid 1000) with HOME=/root.
Persistent
mounts at /root/.local/share/ov/data that were created by an older
root-based
image (stale named volumes) or by Docker as auto-created bind-mount dirs
are
owned by root, so the runtime user cannot write the extension-registry
cache.
For XR teleop this aborts startup with a confusing, seemingly-unrelated
error:
PermissionError: [Errno 13] Permission denied:
'/root/.local/share/ov/data/exts'
...
No versions of omni.kit.xr.bundle.generic that satisfies:
isaaclab.python.xr.openxr-3.0.0 ...
Exiting app because of dependency solver failure...
The XR bundle isn't actually missing — the registry never synced because
its
cache dir couldn't be created. This PR documents the cause and fix:

docs/source/how-to/cloudxr_teleoperation.rst: adds an admonition to the
"Run with Docker" section explaining the failure and the fix
(recreate/chown
volumes for Compose, pre-create/chown host dirs for single-container).
docs/source/deployment/docker.rst: warns that non-root prebuilt images
need bind-mount host dirs pre-created and chowned to uid/gid 1000, with
a
copy-paste snippet.
Docs-only; no code changes and no changelog fragment (no source/<pkg>/
package
touched).

Fixes # (issue)

## Type of change

- Documentation update

## Checklist

- [x] I have read and understood the [contribution
guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html)
- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there
AntoineRichard pushed a commit that referenced this pull request Jun 9, 2026
# Description

Since the non-root Docker migration (#5618, first shipped in
3.0.0-beta2), the
container runs as user `isaaclab` (uid/gid 1000) with `HOME=/root`.
Persistent
mounts at `/root/.local/share/ov/data` that were created by an older
root-based
image (stale named volumes) or by Docker as auto-created bind-mount dirs
are
owned by `root`, so the runtime user cannot write the extension-registry
cache.
For XR teleop this aborts startup with a confusing, seemingly-unrelated
error:
PermissionError: [Errno 13] Permission denied:
'/root/.local/share/ov/data/exts'
    ...
No versions of omni.kit.xr.bundle.generic that satisfies:
isaaclab.python.xr.openxr-3.0.0 ...
    Exiting app because of dependency solver failure...
The XR bundle isn't actually missing — the registry never synced because
its
cache dir couldn't be created. This PR documents the cause and fix:
- **`docs/source/how-to/cloudxr_teleoperation.rst`**: adds an admonition
to the
"Run with Docker" section explaining the failure and the fix
(recreate/chown
  volumes for Compose, pre-create/chown host dirs for single-container).
- **`docs/source/deployment/docker.rst`**: warns that non-root prebuilt
images
need bind-mount host dirs pre-created and chowned to uid/gid 1000, with
a
  copy-paste snippet.
Docs-only; no code changes and no changelog fragment (no `source/<pkg>/`
package
touched).

Fixes # (issue)

## Type of change

- Documentation update

## Checklist

- [x] I have read and understood the [contribution
guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html)
- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there
hujc7 added a commit that referenced this pull request Jun 10, 2026
…clab user (#6082)

## Summary

- Fixes training regressions in the non-root Docker images (base +
cuRobo): `skrl` training aborts with `PermissionError` creating `logs/`,
and Isaac Sim emits `omni.datastore` lock failures under `kit/cache`
(tripping the CI training-log blacklist).
- Root cause: a fresh Docker named volume inherits ownership from the
image directory at its mount path; the non-root `isaaclab` user (uid/gid
1000) cannot write mount points the image left missing or root-owned.
- Pre-creates and `chown`s every named-volume mount point before the
`USER` switch, driven by a single source of truth —
`docker-compose.yaml`, parsed by `docker/utils/volume_mounts.py` — so
the list is never duplicated in the Dockerfiles.

## 1. Background

#5618 switched the base / ROS 2 / cuRobo images to `USER isaaclab`
(uid/gid 1000) so bind-mounted workspaces stay writable for the
host/runner user. That change covered the `${DOCKER_USER_HOME}` mount
points (swept by the recursive `chown`), but not the named volumes that
mount outside the runtime home.

## 2. Mechanism

When an empty named volume is mounted for the first time, Docker (the
root daemon) seeds the volume's ownership from the image directory at
that path. If that directory is missing or root-owned in the image, the
volume comes up `root:root` and the uid-1000 runtime user is denied. The
home-based volumes already inherited the right owner; the ones under
`/isaac-sim` and `/workspace/isaaclab` did not (`kit/cache` was created
root-owned by a build `mkdir`; `logs` / `data_storage` / `docs/_build`
did not exist in the image at all).

## 3. Fix

3.1 `docker/utils/volume_mounts.py` — parses the `type: volume` targets
out of `docker-compose.yaml` (the single source of truth) and resolves
the `${VAR}` paths. Reuses PyYAML, already an IsaacLab dependency.

3.2 `Dockerfile.base` / `Dockerfile.curobo` — before `USER isaaclab`,
invoke the parser and `mkdir -p` + `chown -R isaaclab:isaaclab` every
mount point, so fresh volumes inherit `isaaclab` ownership. `set -o
pipefail` plus a non-empty guard abort the build if the parse yields
nothing, rather than silently shipping an unprepared mount point. The
ROS 2 image builds `FROM` base and inherits the fix.

3.3 Adding a volume to `docker-compose.yaml` is now prepared
automatically — no second list to keep in sync.

## 4. Test

`docker/test/test_dockerfile_nonroot.py` unit-tests the parser
(asserting it returns the mount points that triggered the regression)
and asserts each non-root Dockerfile wires the parser in with the guard.
Static, no image build required.

## 5. Validation

- Static tests pass (`13 passed`); the parser test fails if compose
drops a tracked volume.
- Verified on a freshly built base image: all named-volume mount points
come up `uid 1000`, and the exact QA training (`skrl` Anymal-C) runs to
completion with no `PermissionError` and zero `omni.datastore` lock
failures.

## 6. Notes

- No changelog fragment: `docker/` is not under `source/<pkg>` (matches
#5618).
- cuRobo has no compose file today, but it creates `kit/cache` as root
and runs Isaac Sim non-root, so its cache is unwritable even without a
volume mount; the same step fixes it.
hujc7 added a commit that referenced this pull request Jun 10, 2026
…clab user (cherry-pick #6082 → 3.0.0-beta2) (#6095)

## Summary

- Cherry-pick of #6082 onto `release/3.0.0-beta2`. Net diff is identical
to the develop-side PR; applies cleanly with no conflicts.
- Fixes training regressions in the non-root Docker images (base +
cuRobo): `skrl` training aborts with `PermissionError` creating `logs/`,
and Isaac Sim emits `omni.datastore` lock failures under `kit/cache`
(tripping the CI training-log blacklist).
- Root cause: a fresh Docker named volume inherits ownership from the
image directory at its mount path; the non-root `isaaclab` user (uid/gid
1000) cannot write mount points the image left missing or root-owned.
- Pre-creates and `chown`s every named-volume mount point before the
`USER` switch, driven by a single source of truth —
`docker-compose.yaml`, parsed by `docker/utils/volume_mounts.py` — so
the list is never duplicated in the Dockerfiles.

## 1. Background

#5618 switched the base / ROS 2 / cuRobo images to `USER isaaclab`
(uid/gid 1000) so bind-mounted workspaces stay writable for the
host/runner user. That change covered the `${DOCKER_USER_HOME}` mount
points (swept by the recursive `chown`), but not the named volumes that
mount outside the runtime home.

## 2. Mechanism

When an empty named volume is mounted for the first time, Docker (the
root daemon) seeds the volume's ownership from the image directory at
that path. If that directory is missing or root-owned in the image, the
volume comes up `root:root` and the uid-1000 runtime user is denied. The
home-based volumes already inherited the right owner; the ones under
`/isaac-sim` and `/workspace/isaaclab` did not (`kit/cache` was created
root-owned by a build `mkdir`; `logs` / `data_storage` / `docs/_build`
did not exist in the image at all).

## 3. Fix

3.1 `docker/utils/volume_mounts.py` — parses the `type: volume` targets
out of `docker-compose.yaml` (the single source of truth) and resolves
the `${VAR}` paths. Reuses PyYAML, already an IsaacLab dependency.

3.2 `Dockerfile.base` / `Dockerfile.curobo` — before `USER isaaclab`,
invoke the parser and `mkdir -p` + `chown -R isaaclab:isaaclab` every
mount point, so fresh volumes inherit `isaaclab` ownership. `set -o
pipefail` plus a non-empty guard abort the build if the parse yields
nothing, rather than silently shipping an unprepared mount point. The
ROS 2 image builds `FROM` base and inherits the fix.

3.3 Adding a volume to `docker-compose.yaml` is now prepared
automatically — no second list to keep in sync.

## 4. Test

`docker/test/test_dockerfile_nonroot.py` unit-tests the parser
(asserting it returns the mount points that triggered the regression)
and asserts each non-root Dockerfile wires the parser in with the guard.
Static, no image build required.

## 5. Validation

- Static tests pass (`13 passed`); the parser test fails if compose
drops a tracked volume.
- Verified on a freshly built base image: all named-volume mount points
come up `uid 1000`, and the exact QA training (`skrl` Anymal-C) runs to
completion with no `PermissionError` and zero `omni.datastore` lock
failures.

## 6. Notes

- No changelog fragment: `docker/` is not under `source/<pkg>` (matches
#5618).
- cuRobo has no compose file today, but it creates `kit/cache` as root
and runs Isaac Sim non-root, so its cache is unwritable even without a
volume mount; the same step fixes it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation infrastructure isaac-lab Related to Isaac Lab team

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants