Configure NVIDIA Jetson
NVIDIA Jetson remains a supported MoveIt Pro platform. PickNik has not yet built and validated the ROS 2 Jazzy container image for JetPack/L4T, so current Jazzy packages do not launch on Jetson yet. Keep existing Jetson deployments on a MoveIt Pro release that publishes a Jetson image until the Jazzy port is announced.
The hardware preparation, storage, power, and performance guidance on this page remains applicable. PickNik supports NVIDIA AGX Orin with JetPack 6.2 or higher; other Jetson platforms are supported on a best-effort basis.
Configuring JetPack for GPU Acceleration
The following instructions assume that you already have Docker installed and that your system has internet access.
GPU acceleration for MoveIt Pro requires installing the nvidia-container-toolkit. On NVIDIA Jetson platforms with JetPack, this package is provided in an APT repository which is already configured on the system. To install and configure the package, open a terminal and run:
sudo apt update && sudo apt install nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
To verify that the toolkit is installed properly, run the command nvidia-smi directly on the AGX Orin (host) which should produce output similar to:
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 540.4.0 Driver Version: 540.4.0 CUDA Version: 12.6 |
|-----------------------------------------+----------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+======================+======================|
| 0 Orin (nvgpu) N/A | N/A N/A | N/A |
| N/A N/A N/A N/A / N/A | Not Supported | N/A N/A |
| | | N/A |
+-----------------------------------------+----------------------+----------------------+
+---------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=======================================================================================|
| No running processes found |
+---------------------------------------------------------------------------------------+
Then, run the following example container:
docker run --rm --runtime=nvidia --gpus all ubuntu nvidia-smi
And observe that the output matches the output of running the nvidia-smi command on the host.
MoveIt Pro GPU Inference Availability
On release lines that publish a validated JetPack-specific image, MoveIt Pro detects Jetson and selects the matching image automatically. The current Jazzy release does not publish that image, and there is no supported user-Dockerfile customization that enables it. Keep existing deployments pinned to their validated Jetson-compatible MoveIt Pro release until PickNik publishes the Jazzy port.
NVIDIA AGX Orin Onboard Storage Limitations
The NVIDIA AGX Orin is provided with 64GB of eMMC 5.1 onboard storage. This eMMC storage is capable of a theoretical maximum linear transfer speed of 100MB/s. The NVIDIA AGX Orin Developer Kit is also provided with a PCIe x4 Gen4 M.2 slot for expansion which is capable of transfer speeds many times that of the onboard eMMC. For this reason, PickNik recommends installing a compatible M.2 NVMe storage device and configuring Docker to use this device for image storage using the instructions below.
Choosing a Drive
For the NVIDIA AGX Orin Developer Kit, NVIDIA recommends an NVMe storage device which has the following specifications:
- M.2 key M
- 2280 form factor
- PCIe Gen4 (Gen3 supported with Gen3 speeds / Gen5 supported at Gen4 speeds)
- At least 1TB capacity
Installing the NVMe Drive
In addition to the instructions below, follow all best-practices for safe electronics handling such as anti-static procedures.
- Power off the NVIDIA AGX Orin Developer Kit and disconnect power and data cables.
- Flip the Developer Kit upside-down.
- Identify connector J1 using the Carrier Board Component Identification Diagram.
- Remove the mounting screw in the stand-off directly across from connector J1.
- Fully insert the M.2 NVMe drive edge connector into the carrier board connector J1 at a 45 degree angle.
- Gently push the drive down toward the carrier board until the semi-circular cutout in the end of the drive aligns with the stand-off from which you removed the screw in step 4.
- While holding the drive down, re-install the screw into the stand-off to affix the drive to the carrier board. Take care not to over-tighten this screw.
- Flip the Developer Kit right-side-up and reconnect power and data cables.
Formatting and Mounting
- Power on the NVIDIA AGX Orin Developer Kit.
- Log in using your username and password.
- Open a terminal window (keyboard shortcut: CTRL+ALT+T).
- In the terminal window type
lsblkand hit enter. If your new drive was detected correctly, there should be an entry at the bottom of the output similar tonvme0n1 259:0 0 931.5G 0 disk. If your device is named differently, write down the name and use it in the following steps. - Type
sudo fdisk /dev/nvme0n1, hit enter, enter your password, then hit enter again.- Type
gand hit enter to create a new GPT partition table. - Type
nand hit enter to create a new partition. - Hit enter to accept the default partition number.
- Hit enter to accept the default first sector.
- Hit enter to accept the default last sector and use the remaining drive capacity.
- Type
wand hit enter to write the changes and exitfdisk.
- Type
- Type
lsblkand hit enter. Confirm that the drivenvme0n1now has a new partition below it named similarly tonvme0n1p1. - Type
sudo mkfs.ext4 /dev/nvme0n1p1which will format the new partition asext4(common Linux extended filesystem format). - Type
sudo mkdir /mnt/nvmewhich will create a new folder that we will use as the mount point for the new partition. - Type
lsblk -fwhich will print the partition information but also include the UUID field which we will use in the following steps. Highlight the UUID of the new partition with your mouse and copy it to your clipboard (right-click -> Copy). - Edit the
/etc/fstabfile with a terminal editor likevimornano. Add a new line to the end of this file with the formatUUID=<partition_uuid> /mnt/nvme ext4 defaults 0 2, replacing<partition_uuid>with the UUID that you copied in step 9 (right-click -> Paste). - Save and exit the editor.
- Type
sudo rebootand hit enter to reboot the NVIDIA AGX Orin. - Once the Orin has rebooted and you have logged in again, you can verify that the new partition is mounted using the command
ls /mnt/nvmein a terminal to see the contents of the new partition.
Migrating Docker Storage Location
The following instructions assume that you already have Docker installed and that you carefully followed the instructions above to install and configure an NVMe drive.
-
Confirm that
/mnt/nvmeis the intended mounted filesystem and not a symlink:test -d /mnt/nvme && test ! -L /mnt/nvme &&mountpoint --quiet /mnt/nvme ||{ echo "NVMe target validation failed." >&2; exit 1; } -
Detect whether this Docker installation uses the containerd image store, then back up the current service configuration. Do not infer the backend from the Docker version: upgraded Docker 29 installations can still use the legacy image store.
set -euo pipefailDRIVER_STATUS="$(docker info --format '{{json .DriverStatus}}')"STORAGE_DRIVER="$(docker info --format '{{.Driver}}')"case "$DRIVER_STATUS" in*io.containerd.snapshotter.v1*) MIGRATE_CONTAINERD=true ;;*)test -n "$STORAGE_DRIVER" ||{ echo "Could not determine Docker's active image store." >&2; exit 1; }MIGRATE_CONTAINERD=false;;esacsudo install -d -m 0755 /var/backupsBACKUP_DIR="$(sudo mktemp -d -p /var/backups moveit-pro-docker-storage.XXXXXXXX)"sudo chmod 0700 "$BACKUP_DIR"if sudo test -e /etc/docker/daemon.json; thensudo cp -a /etc/docker/daemon.json "$BACKUP_DIR/daemon.json"elsesudo touch "$BACKUP_DIR/daemon.json.absent"fiif sudo test -e /etc/containerd/config.toml; thensudo cp -a /etc/containerd/config.toml "$BACKUP_DIR/containerd-config.toml"elsesudo touch "$BACKUP_DIR/containerd-config.toml.absent"fiif $MIGRATE_CONTAINERD; thensudo touch "$BACKUP_DIR/containerd-migration.enabled"fiprintf 'Configuration backup: %s\n' "$BACKUP_DIR"Record the printed backup path; the rollback commands require it. The marker records the backend reported by
docker info; do not create or remove it based only on the Docker version. -
Record the current images and named volumes, then stop Docker and containerd:
set -euo pipefaildocker image lsdocker volume lssudo systemctl stop docker.socket docker.service containerdfor unit in docker.socket docker.service containerd; doif sudo systemctl is-active --quiet "$unit"; thenecho "$unit is still active; refusing to touch live data." >&2exit 1fidone -
Copy the active data without deleting its source. Copy containerd only when step 2 created the migration marker:
set -euo pipefailsudo test ! -e /mnt/nvme/docker ||{ echo "/mnt/nvme/docker already exists; refusing to merge data." >&2; exit 1; }sudo install -d -m 0711 /mnt/nvme/dockersudo rsync -aHAXx --numeric-ids /var/lib/docker/ /mnt/nvme/docker/ ||{ echo "Docker data copy failed." >&2; exit 1; }if sudo test -e "$BACKUP_DIR/containerd-migration.enabled"; thensudo test ! -e /mnt/nvme/containerd ||{ echo "/mnt/nvme/containerd already exists; refusing to merge data." >&2; exit 1; }sudo install -d -m 0711 /mnt/nvme/containerdif sudo test -d /var/lib/containerd; thensudo rsync -aHAXx --numeric-ids /var/lib/containerd/ /mnt/nvme/containerd/ ||{ echo "containerd data copy failed." >&2; exit 1; }sudo touch "$BACKUP_DIR/containerd-data-source.present"fifi -
Set
"data-root": "/mnt/nvme/docker"in/etc/docker/daemon.json, preserving any existing settings. If$BACKUP_DIR/containerd-migration.enabledexists, ensure a version-appropriate containerd configuration exists, then setroot = "/mnt/nvme/containerd"in it. Do not change the containerd root otherwise.set -euo pipefailif sudo test -e "$BACKUP_DIR/containerd-migration.enabled" &&! sudo test -e /etc/containerd/config.toml; thensudo install -d -m 0755 /etc/containerdcontainerd config default | sudo tee /etc/containerd/config.toml >/dev/nullfi -
Start the services and verify the migration before changing the old directories:
set -euo pipefailsudo systemctl start containerd dockertest "$(docker info --format '{{ .DockerRootDir }}')" = /mnt/nvme/docker ||{ echo "Docker is not using the NVMe data root." >&2; exit 1; }if sudo test -e "$BACKUP_DIR/containerd-migration.enabled"; thensudo containerd config dump |grep -Eq '^[[:space:]]*root[[:space:]]*=[[:space:]]*"/mnt/nvme/containerd"' ||{ echo "containerd is not using the NVMe root." >&2; exit 1; }fidocker image lsdocker volume lssudo systemctl --no-pager --full status containerd dockermountpoint --quiet /mnt/nvmetest "$(findmnt -n -o TARGET -T /mnt/nvme/docker)" = /mnt/nvmeif sudo test -e "$BACKUP_DIR/containerd-migration.enabled"; thentest "$(findmnt -n -o TARGET -T /mnt/nvme/containerd)" = /mnt/nvmefiConfirm that the expected images, named volumes, and volume contents are present. If validation fails, the original data still exists under
/var/lib; restore the exact pre-migration configuration before restarting the services:set -euo pipefail# If this is a new shell, set BACKUP_DIR to the path printed in step 2.sudo test -d "$BACKUP_DIR" || { echo "Backup directory not found." >&2; exit 1; }sudo systemctl stop docker.socket docker.service containerdfor unit in docker.socket docker.service containerd; do! sudo systemctl is-active --quiet "$unit" ||{ echo "$unit is still active; aborting rollback." >&2; exit 1; }doneif sudo test -e "$BACKUP_DIR/daemon.json.absent"; thensudo rm -f /etc/docker/daemon.jsonelsesudo cp -a "$BACKUP_DIR/daemon.json" /etc/docker/daemon.jsonfiif sudo test -e "$BACKUP_DIR/containerd-config.toml.absent"; thensudo rm -f /etc/containerd/config.tomlelsesudo cp -a "$BACKUP_DIR/containerd-config.toml" /etc/containerd/config.tomlfisudo systemctl start containerd docker -
After the migrated system has been validated, stop the services and quarantine only the data roots that were migrated. Refuse to overwrite an earlier quarantine:
set -euo pipefailsudo test -d "$BACKUP_DIR" || { echo "Backup directory not found." >&2; exit 1; }mountpoint --quiet /mnt/nvme ||{ echo "NVMe target is not mounted; refusing to quarantine data." >&2; exit 1; }test "$(findmnt -n -o TARGET -T /mnt/nvme/docker)" = /mnt/nvme ||{ echo "Docker destination is not on the NVMe mount." >&2; exit 1; }if sudo test -e "$BACKUP_DIR/containerd-migration.enabled"; thentest "$(findmnt -n -o TARGET -T /mnt/nvme/containerd)" = /mnt/nvme ||{ echo "containerd destination is not on the NVMe mount." >&2; exit 1; }fiif sudo test -e "$BACKUP_DIR/daemon.json.absent"; then:elsesudo test -f "$BACKUP_DIR/daemon.json" ||{ echo "Docker configuration backup not found." >&2; exit 1; }fiif sudo test -e "$BACKUP_DIR/containerd-config.toml.absent"; then:elsesudo test -f "$BACKUP_DIR/containerd-config.toml" ||{ echo "containerd configuration backup not found." >&2; exit 1; }fisudo test -d /var/lib/docker || { echo "Docker data root not found." >&2; exit 1; }if sudo test -e /var/lib/docker.pre-nvme-migration; thenecho "Docker quarantine already exists." >&2exit 1fiif sudo test -e "$BACKUP_DIR/containerd-data-source.present"; thensudo test -d /var/lib/containerd ||{ echo "containerd data root not found." >&2; exit 1; }if sudo test -e /var/lib/containerd.pre-nvme-migration; thenecho "containerd quarantine already exists." >&2exit 1fifisudo systemctl stop docker.socket docker.service containerdfor unit in docker.socket docker.service containerd; do! sudo systemctl is-active --quiet "$unit" ||{ echo "$unit is still active; refusing to move live data." >&2; exit 1; }donesudo mv /var/lib/docker /var/lib/docker.pre-nvme-migrationif sudo test -e "$BACKUP_DIR/containerd-data-source.present"; thensudo mv /var/lib/containerd /var/lib/containerd.pre-nvme-migrationfisudo systemctl start containerd dockerVerify the Docker root, images, volumes, and application data again. Keep the quarantined directories until the deployment has operated successfully and you have a separate backup.
-
To roll back after quarantine, preflight every backup and destination before stopping services. Then preserve any directories recreated by the failed configuration, restore the old data to its exact path, restore or remove each configuration file according to its marker, and restart the services:
set -euo pipefail# Set BACKUP_DIR to the path printed in step 2.sudo test -d "$BACKUP_DIR" || { echo "Backup directory not found." >&2; exit 1; }if sudo test -e "$BACKUP_DIR/daemon.json.absent"; then:elsesudo test -f "$BACKUP_DIR/daemon.json" ||{ echo "Docker configuration backup not found." >&2; exit 1; }fiif sudo test -e "$BACKUP_DIR/containerd-config.toml.absent"; then:elsesudo test -f "$BACKUP_DIR/containerd-config.toml" ||{ echo "containerd configuration backup not found." >&2; exit 1; }fiROLLBACK_SUFFIX="failed-nvme-$(date +%Y%m%d-%H%M%S)"sudo test -d /var/lib/docker.pre-nvme-migration ||{ echo "Docker quarantine not found." >&2; exit 1; }sudo test ! -e "/var/lib/docker.$ROLLBACK_SUFFIX" ||{ echo "Docker failure quarantine already exists." >&2; exit 1; }if sudo test -e "$BACKUP_DIR/containerd-data-source.present"; thensudo test -d /var/lib/containerd.pre-nvme-migration ||{ echo "containerd quarantine not found." >&2; exit 1; }sudo test ! -e "/var/lib/containerd.$ROLLBACK_SUFFIX" ||{ echo "containerd failure quarantine already exists." >&2; exit 1; }fisudo systemctl stop docker.socket docker.service containerdfor unit in docker.socket docker.service containerd; do! sudo systemctl is-active --quiet "$unit" ||{ echo "$unit is still active; aborting rollback." >&2; exit 1; }doneif sudo test -e /var/lib/docker; thensudo mv /var/lib/docker "/var/lib/docker.$ROLLBACK_SUFFIX"fisudo mv /var/lib/docker.pre-nvme-migration /var/lib/dockerif sudo test -e "$BACKUP_DIR/containerd-data-source.present"; thenif sudo test -e /var/lib/containerd; thensudo mv /var/lib/containerd "/var/lib/containerd.$ROLLBACK_SUFFIX"fisudo mv /var/lib/containerd.pre-nvme-migration /var/lib/containerdfiif sudo test -e "$BACKUP_DIR/daemon.json.absent"; thensudo rm -f /etc/docker/daemon.jsonelsesudo cp -a "$BACKUP_DIR/daemon.json" /etc/docker/daemon.jsonfiif sudo test -e "$BACKUP_DIR/containerd-config.toml.absent"; thensudo rm -f /etc/containerd/config.tomlelsesudo cp -a "$BACKUP_DIR/containerd-config.toml" /etc/containerd/config.tomlfisudo systemctl start containerd docker
Browser Considerations for Existing Jetson Deployments
We recommend using Firefox to access the MoveIt Pro web UI on Jetson platforms. However, the default Firefox version shipped with Ubuntu 22.04 does not support GPU-accelerated rendering. To get a version with GPU acceleration support, install Firefox from the Mozilla Team PPA:
sudo add-apt-repository ppa:mozillateam/ppa
sudo apt update
# Remove the default Snap-packaged Firefox so apt can manage it instead.
sudo apt purge firefox
sudo snap remove firefox
sudo tee /etc/apt/preferences.d/firefoxPrio > /dev/null <<'EOF'
Package: firefox*
Pin: release o=LP-PPA-mozillateam
Pin-Priority: 1001
EOF
sudo apt install firefox
The APT pinning ensures the PPA version is always preferred over the Snap package, so future apt upgrade runs will continue to pull Firefox from the Mozilla Team PPA rather than reinstalling the Snap.
These browser recommendations apply only to existing deployments pinned to a validated Jetson-compatible MoveIt Pro release. They do not make the current Jazzy image runnable on Jetson. Launch that pinned release using its corresponding documentation, then open Firefox manually if its automatic browser uses the Xvfb virtual display instead of the physical monitor.
Other Performance Considerations
NVIDIA Jetson platforms are designed to be configurable for different power and performance needs.
By default, the NVIDIA AGX Orin comes configured with "medium" performance and power settings which can be changed with the nvpmodel utility.
For more details on this utility, see the NVIDIA Jetson Linux Developer Guide.
MAXN Power Mode
If your environment supports higher power draw and heat output from the NVIDIA AGX Orin, you can use the command below to set the power mode to "MAXN" (or "unlimited") for better compute performance:
sudo nvpmodel -m 0
For more information on available modes, power consumption, and clock frequencies, refer to the NVIDIA Jetson Linux Developer Guide.
Jetson Clocks
Even when using "MAXN" power mode, the clock frequencies configured for each chip or accelerator are defined as ranges.
When running real-time applications on these platforms, the delays introduced when scaling clock frequencies can cause "hitches" or momentary latencies.
To alleviate this, NVIDIA provides another utility called jetson_clocks which pins each chip's or accelerator's frequency to the max frequency in its range.
However, the configuration changes that jetson_clocks applies are not persistent across reboots.
On Jetson AGX Orin with JetPack 6.2 / L4T R36.4.7, combining MAXN and jetson_clocks can cause frequent OC3 over-current throttling during bursty GPU workloads such as MuJoCo camera rendering. Test the intended standalone workload while monitoring tegrastats and the platform over-current counters. If OC3 events occur, keep MAXN if needed but disable persistent jetson_clocks so NVIDIA's governor can smooth GPU and EMC current transients. Do not disable OC3 protection without hardware-owner approval.
Run and inspect jetson_clocks manually before making it persistent:
sudo jetson_clocks
sudo jetson_clocks --show
Only after the workload completes without OC3 throttling, create /etc/systemd/system/jetson-clocks.service with:
[Unit]
Description=Jetson Clocks Service
After=nvpmodel.service
[Service]
Type=oneshot
ExecStart=/usr/bin/jetson_clocks
RemainAfterExit=true
[Install]
WantedBy=multi-user.target
Reload systemd, enable the service for boot, and start it:
sudo systemctl daemon-reload
sudo systemctl enable jetson-clocks.service
sudo systemctl start jetson-clocks.service