User Plane

Headless Isaac Sim on GPU Nodes (WebRTC)

Run NVIDIA Isaac Sim 6.0.1 headless in a Docker container on a shared GPU node and drive it from a laptop over the Isaac Sim WebRTC Streaming Client, reaching the node by its Tailscale address. The container has no display; all rendering happens on the node's GPU and the H.264 stream is sent to the client.

This runbook is grounded on gpu-node-3 but the pattern applies to any GPU node in the tailnet. It is written so that any user in the node's docker group can launch the simulator — shared state lives under /opt/isaac-sim, never in a personal home directory.

Target node facts (gpu-node-3)

PropertyValue
OSUbuntu 24.04.4 LTS
GPU1× NVIDIA RTX PRO 6000 Blackwell (97,887 MiB)
NVIDIA driver580.159.03 (satisfies Isaac Sim 6.0.1 / Blackwell)
Docker29.4.3
NVIDIA Container Toolkit1.19.0 (CDI configured: nvidia.com/gpu)
Tailscale IP100.85.176.38 (MagicDNS: gpu-node-3)
docker group memberspantelis.monogioudis, andrewaq03, ayushyadav1783-cmd

How it connects

Prerequisites

On the node (already satisfied on gpu-node-3):

  • NVIDIA driver + NVIDIA Container Toolkit. Validate GPU passthrough:
    docker run --rm --gpus all nvcr.io/nvidia/cuda:12.8.0-base-ubuntu24.04 nvidia-smi
  • Your account must be in the docker group (id -nG | tr ' ' '\n' | grep -x docker).

On your client machine:

  • Isaac Sim WebRTC Streaming Client (native app) installed — download it from the Isaac Sim release/downloads page.
  • The client must be on the same tailnet (tailscale status shows gpu-node-3). Tailnet peers can reach all ports by default, so no host firewall rule is normally required; if ufw is enabled on the node, allow the ports below on the tailscale0 interface.

NGC access (if the pull is denied): the Isaac Sim image lives on NGC. If docker pull returns unauthorized, log in once with an NGC API key:

docker login nvcr.io   # username: $oauthtoken   password: <your NGC API key>

One-time shared setup (run once, by a sudo-capable admin)

This creates an isaac group, adds the node's Docker users, and provisions a shared directory tree owned by the container's uid (1234). Because every Isaac Sim container runs as uid 1234 regardless of who launches it, all users' containers can read/write these paths cleanly; the isaac group + setgid bit is what lets human users inspect and clean the files.

# 1. group + membership
sudo groupadd -f isaac
for u in pantelis.monogioudis andrewaq03 ayushyadav1783-cmd; do
  sudo usermod -aG isaac "$u"
done
ISAAC_GID=$(getent group isaac | cut -d: -f3)

# 2. shared root, shared caches (warmed once, reused by everyone), per-user parent
sudo mkdir -p /opt/isaac-sim/cache/{kit,compute,hub}
sudo mkdir -p /opt/isaac-sim/users
sudo chown -R 1234:"$ISAAC_GID" /opt/isaac-sim
sudo chmod -R 2775 /opt/isaac-sim          # setgid: new files inherit the isaac group

Provision a per-user instance dir (config/data/logs/pkg are kept per user so two people can run at once without clobbering each other). Run once per user:

NEWUSER=ayushyadav1783-cmd
ISAAC_GID=$(getent group isaac | cut -d: -f3)
sudo mkdir -p /opt/isaac-sim/users/"$NEWUSER"/{config,data,logs,pkg}
sudo chown -R 1234:"$ISAAC_GID" /opt/isaac-sim/users/"$NEWUSER"
sudo chmod -R 2775 /opt/isaac-sim/users/"$NEWUSER"

Why /opt/isaac-sim and not ~/docker/isaac-sim

/opt/isaac-sim and not ~/docker/isaac-sim The upstream guide puts everything under the launching user's home directory. On a shared node that makes the simulator usable by exactly one person. Placing the tree under /opt/isaac-sim, group-owned by isaac with the setgid bit, lets any member of the group launch it while sharing the expensive caches.

Pull the image (shared automatically)

The image layer store is shared across all users on the host, so pull once:

docker pull nvcr.io/nvidia/isaac-sim:6.0.1

Launch (any user in the docker + isaac groups)

ISAAC_GID=$(getent group isaac | cut -d: -f3)

# REQUIRED: this node's IP as the client reaches it (its Tailscale IP).
# Without it the media stream never arrives — black screen (see note below).
HOST_IP=100.85.176.38

# Pick a UNIQUE port pair if someone else is already streaming (see table below).
SIGNAL_PORT=49100     # TCP, WebRTC signaling
STREAM_PORT=47998     # UDP, WebRTC media

docker run --name "isaac-sim-$USER" --entrypoint bash -it --rm \
  --gpus all --network=host \
  -e ACCEPT_EULA=Y -e PRIVACY_CONSENT=Y \
  -e ISAACSIM_HOST=$HOST_IP \
  -e ISAACSIM_SIGNAL_PORT=$SIGNAL_PORT -e ISAACSIM_STREAM_PORT=$STREAM_PORT \
  -u 1234:"$ISAAC_GID" \
  -v /opt/isaac-sim/cache/kit:/isaac-sim/.cache:rw \
  -v /opt/isaac-sim/cache/compute:/isaac-sim/.nv/ComputeCache:rw \
  -v /opt/isaac-sim/cache/hub:/var/cache/hub:rw \
  -v /opt/isaac-sim/users/$USER/config:/isaac-sim/.nvidia-omniverse/config:rw \
  -v /opt/isaac-sim/users/$USER/data:/isaac-sim/.local/share/ov/data:rw \
  -v /opt/isaac-sim/users/$USER/logs:/isaac-sim/.nvidia-omniverse/logs:rw \
  -v /opt/isaac-sim/users/$USER/pkg:/isaac-sim/.local/share/ov/pkg:rw \
  nvcr.io/nvidia/isaac-sim:6.0.1

You are now at a shell inside the container. Start the streaming app:

./runheadless.sh -v

Wait for the log line Isaac Sim Full Streaming App is loaded. — first launch compiles shaders and can take several minutes (subsequent launches are fast because the shader cache is shared under /opt/isaac-sim/cache).

  • -e ISAACSIM_HOST=$HOST_IP is required. It is the address runheadless.sh advertises to the client as the WebRTC media (ICE) candidate. Without it the server advertises 127.0.0.1, so TCP signaling connects but no video ever arrives — a black screen. Set it to the node's Tailscale IP (100.85.176.38), i.e. the address the client uses to reach the node.
  • --network=host is required so the WebRTC ports bind directly on the node.
  • --gpus all exposes the single Blackwell GPU to the container (equivalently, with CDI: --device nvidia.com/gpu=all).
  • --rm discards the container on exit; your work persists via the mounted data/config volumes.

Connect the WebRTC client (over Tailscale)

  1. Confirm the node is reachable: tailscale ping gpu-node-3.
  2. Open the Isaac Sim WebRTC Streaming Client.
  3. Set Server = 100.85.176.38 and Signaling port = your SIGNAL_PORT (default 49100), then Connect.
  4. The live viewport streams to the client; input events are sent back over the same connection.

Running several users at once

The node has one GPU (96 GB VRAM — plenty for multiple sims; compute is time-shared). With --network=host, concurrent sessions must use distinct ports and container names. Suggested fixed assignment:

UserContainer nameSignal (TCP)Media (UDP)
pantelis.monogioudisisaac-sim-pantelis.monogioudis4910047998
andrewaq03isaac-sim-andrewaq034910147999
ayushyadav1783-cmdisaac-sim-ayushyadav1783-cmd4910248000

The container name defaults to isaac-sim-$USER above, so that is already unique; just export SIGNAL_PORT/STREAM_PORT from this table before launching. Point each client at 100.85.176.38 with the matching signal port.

Disk & housekeeping

df -h /                                   # watch headroom before pulling the image
docker image ls nvcr.io/nvidia/isaac-sim  # the big image
du -sh /opt/isaac-sim/cache/*             # shared caches — safe to clear to reclaim space
docker system df                          # reclaimable docker space
  • Clearing /opt/isaac-sim/cache/* is safe; it only forces a one-time shader recompile.
  • If / stays tight, relocate Docker storage: stop Docker, set "data-root" in /etc/docker/daemon.json to a path on a larger volume, rsync the existing store, restart.

Troubleshooting

SymptomFix
docker pullunauthorizeddocker login nvcr.io with an NGC API key (user $oauthtoken).
Client cannot connectConfirm tailscale ping gpu-node-3; verify the client's signal port matches ISAACSIM_SIGNAL_PORT; ensure the container reached Isaac Sim Full Streaming App is loaded.
Signaling connects but the viewport is black (no video)ISAACSIM_HOST was not set, so the server advertised 127.0.0.1 for the media stream. Relaunch with -e ISAACSIM_HOST=<node Tailscale IP> (e.g. 100.85.176.38).
no space left on device during pull/runFree space on / or move Docker data-root (see above).
Permission denied writing cache/dataThe host dirs must be owned 1234:isaac; re-run the setup chown/chmod block.
groups: cannot find name for group ID NNNN at container startHarmless — the image has no /etc/group entry for the host isaac GID. The container still runs as 1234:isaac and volume writes are group-owned correctly. Ignore it (or pass --group-add NNNN to silence).
Two sessions fight over the streamGive each user a distinct signal/media port pair (table above).
Black or frozen viewportGPU under contention or driver mismatch; check nvidia-smi on the node and the container logs under /opt/isaac-sim/users/$USER/logs.

References

  • Isaac Sim — Container Installation
  • Related work: AURA-756 (Gaussian-splatting digital twins + IsaacSim spike), AURA-747 (multi-simulator abstraction → Isaac Sim), under the Simulation & Real2Sim / Digital Twins epic.

On this page