Let's say that you found a container image from the Package List or DockerHub, or built your own container - the normal way to run an interactive Docker container on your Jetson would be using docker run like this:
$ sudo docker run --runtime nvidia -it --rm --network=host CONTAINER:TAGThat's actually a rather minimal command, and doesn't have support for displays or other devices, and it doesn't mount the model/data cache (/data). Once you add everything in, it can get to be a lot to specify by hand. Hence, we have some helpers that provide shortcuts.
The jetson-containers run launcher can be run from any directory and forwards its command-line to docker run, with some added defaults - including the above flags, mounting the /data cache, and mounting various devices for display, audio, and video (like V4L2 and CSI cameras)
$ jetson-containers run CONTAINER:TAG # run with --runtime=nvidia, default mounts, ect
$ jetson-containers run CONTAINER:TAG my_app --abc xyz # run a command (instead of interactive mode)
$ jetson-containers run --volume /path/on/host:/path/in/container CONTAINER:TAG # mount a directoryThe flags and arguments to jetson-containers run are the same as they are to docker run - anything you specify will be passed along.
To solve the issue of finding a container with package(s) that you want and that's compatible with your version of JetPack/L4T, there's the autotag tool. It locates a container image for you - either locally, pulled from a registry, or built from source:
$ jetson-containers run $(autotag pytorch) # find pytorch container to run for your version of JetPack/L4TWhat's happening here with the $(autotag xyz) syntax, is that Bash command substitution expands the full container image name and forwards it to the docker run command. For example, if you do echo $(autotag pytorch) it would print out something like dustynv/pytorch:r35.2.1 (assuming that you don't already have the pytorch image locally).
You can of course use autotag interspersed along with other command-line arguments to launch the container:
$ jetson-containers run $(autotag pytorch) my_app --abc xyz # run a command (instead of interactive mode)
$ jetson-containers run --volume /path/on/host:/path/in/container $(autotag pytorch) # mount a directoryOr with using docker run directly:
$ sudo docker run --runtime nvidia -it --rm --network=host $(./autotag pytorch)This is the order in which autotag searches for container images:
- Local images (found under
docker images) - Pulled from registry (by default
hub.docker.com/u/dustynv) - Build it from source (it'll ask for confirmation first)
When searching for images, it knows to find containers that are compatible with your version of JetPack-L4T. For example, if you're on JetPack 4.6.x (L4T R32.7.x), you can run images that were built for other versions of JetPack 4.6. Or if you're on JetPack 5.1 (L4T R35), you can run images built for other versions of JetPack 5.1 (and likewise for JetPack 6.0 and newer)
If you have installed jetson-stats (or jtop) on your host, now a container with jetson-stats (jtop) installed can work inside the container by communicating with host server through a socket /run/jtop.sock (with -v /run/jtop.sock:/run/jtop.sock argument for docker run).
Check the official documentation for the detail.
Make sure you install the same version of jetson-stats (jtop) both on your host and in the container.
By default all containers run as root. Set DOCKER_USER to run as a different identity:
DOCKER_USER=1000:1000 jetson-containers run $(autotag pytorch)
# or by username (the user must exist inside the image):
DOCKER_USER=jetson jetson-containers run $(autotag pytorch)The launcher automatically adds --group-add video --group-add audio --group-add i2c --group-add dialout --group-add plugdev so that common hardware devices remain accessible. X11 forwarding is also updated to grant the declared user display access.
On Jetson, the GPU/Tegra device nodes are group-owned, not world-accessible:
$ ls -l /dev/nvhost-gpu /dev/nvmap /dev/dri/renderD128
crw-rw---- root video /dev/nvhost-gpu
crw-rw---- root video /dev/nvmap
crw-rw---- root render /dev/dri/renderD128
A non-root process that is not a member of these groups cannot open the nodes, and CUDA fails with:
error 801: operation not supported
unexpected error from cudaGetDeviceCount()
jetson-containers run fixes this automatically: when DOCKER_USER is set it discovers the numeric GIDs that own the NVIDIA/Tegra device nodes (/dev/nvhost*, /dev/nvmap, /dev/nvgpu/*, /dev/dri/render*) and adds each via --group-add <gid>.
Why numeric GIDs? The host's
rendergroup GID (e.g.104) usually does not match the container'srenderGID. Adding by name (--group-add render) would grant the wrong GID and silently fail. The numeric GID matches the bind-mounted device node exactly.
If you run the container with your own docker run (not jetson-containers run), add the GPU device groups yourself:
# look up the GIDs that own the GPU nodes on your host:
$ ls -ln /dev/nvhost-gpu /dev/dri/renderD128
crw-rw---- 1 0 44 ... /dev/nvhost-gpu # video = 44
crw-rw---- 1 0 104 ... /dev/dri/renderD128 # render = 104
# then pass them when running as non-root:
sudo docker run --runtime nvidia --user 1000:1000 \
--group-add 44 --group-add 104 \
-it --rm pytorch:latest python3 -c "import torch; print(torch.cuda.is_available())"| Feature | Root required? | Notes |
|---|---|---|
| GPU / CUDA / TensorRT | No | Non-root needs the GPU device-node groups; jetson-containers run adds them automatically (see above). With your own docker run, add --group-add <video-gid> --group-add <render-gid> |
| LLM / VLM inference | No | No hardware devices needed beyond the GPU |
| V4L2 cameras | No | Covered by --group-add video |
| USB devices | No | Covered by --group-add plugdev |
| Audio (PulseAudio) | No | Covered by --group-add audio |
| I2C / serial sensors | No | Covered by --group-add i2c / dialout |
| CSI cameras (Argus) | Yes | nvargus-daemon owns /tmp/argus_socket as root; incompatible with DOCKER_USER |
--csi2webcam pipeline |
No (host-side) | modprobe runs on host; GStreamer inside container needs video group |
/data volume writes |
Depends | Host data/ dir must be writable by the container UID |
Docker socket (docker.sock) |
No | Requires docker group inside image |
Package tests (test.py / test.sh) run during docker build, which always executes as root — so the build is unaffected by non-root runtime. No package test requires actual root privileges (none use sudo, apt, modprobe, privileged ports, or writes to system paths).
The only caveat when re-running a test at runtime under DOCKER_USER is that a number of tests save output artifacts under /data (e.g. /data/audio/tts/, /data/images/). Because /data is a host-mounted volume, a non-root user can only write there if the host data/ directory is writable by the container UID:
# make the data cache writable by your non-root UID (one-time, on the host):
sudo chown -R 1000:1000 jetson-containers/data
DOCKER_USER=1000:1000 jetson-containers run $(autotag piper-tts)This is a volume-ownership requirement, not a privilege requirement — CUDA and the rest of each test run fine as non-root.
Pass identity at build time via env vars and consume them in the Dockerfile:
export CONTAINER_USER=jetson CONTAINER_UID=1000 CONTAINER_GID=1000
jetson-containers build mypackageThese are forwarded as --build-arg CONTAINER_USER=jetson --build-arg CONTAINER_UID=1000 --build-arg CONTAINER_GID=1000. In the Dockerfile:
ARG CONTAINER_USER=root
ARG CONTAINER_UID=0
ARG CONTAINER_GID=0
RUN if [ "$CONTAINER_UID" != "0" ]; then \
groupadd -g $CONTAINER_GID $CONTAINER_USER && \
useradd -u $CONTAINER_UID -g $CONTAINER_GID -m $CONTAINER_USER; \
fi
USER $CONTAINER_USERDeclare the supported user in package metadata so tooling can discover it:
run_user: jetsonSecrets passed as plain --env flags are visible in docker inspect output and the process list. Prefer file-backed secrets instead.
# Preferred: token stays out of docker inspect / shell history
HUGGINGFACE_TOKEN_FILE=~/.hf_token jetson-containers run $(autotag llama3)
# Fallback (backwards-compatible, token visible in docker inspect):
HUGGINGFACE_TOKEN=hf_xxx jetson-containers run $(autotag llama3)HUGGINGFACE_TOKEN_FILE mounts the file read-only at /run/secrets/huggingface_token inside the container and sets HF_TOKEN_FILE / HUGGINGFACE_TOKEN_FILE env vars pointing to it.
Packages can declare secrets they need at build time. The value is never baked into an image layer:
# in Dockerfile header or config.yaml
secrets: [huggingface_token]export JETSON_SECRET_HUGGINGFACE_TOKEN=~/.hf_token
jetson-containers build mypackageInside the Dockerfile, consume via --mount=type=secret:
RUN --mount=type=secret,id=huggingface_token \
HF_TOKEN=$(cat /run/secrets/huggingface_token) \
python3 download_model.pyIf JETSON_SECRET_<NAME> is not set, a warning is printed and the build continues without that secret.