Create and use Docker images for ZED and ROS 2

Open in ClaudeOpen in ChatGPT

This guide is based on the docker folder of the ZED ROS 2 Wrapper repository, which builds Docker images that run the ZED ROS 2 Wrapper.

The images are built on top of the official StereoLabs ZED images, which already ship CUDA and the ZED SDK. On top of that base, the build installs ROS 2 and compiles the wrapper from the sources of the current checkout.

A single, parameterized Dockerfile is used for every target. Two entry-point scripts select the right StereoLabs base image and pass the build arguments:

  • build_desktop.sh — desktop / dGPU images (CUDA + Ubuntu base, using the gl-devel variant).
  • build_jetson.sh — NVIDIA® Jetson™ images (L4T base, using the devel variant).

Everything else these scripts rely on lives in the scripts/ folder and is not meant to be run directly:

FileRole
DockerfileSingle image definition, parameterized by BASE_IMAGE, ROS2_DISTRO, and (optionally) CUSTOM_ZED_SDK_URL.
scripts/install_ros2.shInstalls ROS 2 (from APT, or from source if no binaries exist for the base OS).
scripts/install_zed_sdk.shInstalls the ZED SDK from a custom .run URL (only when --sdk-url is used).
scripts/build_wrapper.shInstalls the wrapper dependencies and builds the wrapper with colcon.
scripts/zed-wrapper-deps.reposSource-mode wrapper dependencies (used only when ROS 2 is built from source).
scripts/ros_entrypoint.shContainer entry point (sets up the ROS 2 environment).
scripts/_common.shShared helpers for the build scripts.

The supported ROS 2 distributions, ZED SDK versions, and the CUDA/Ubuntu and JetPack/L4T combinations that are known to build and run change with every release. For the complete, up-to-date list of available and validated configurations — as well as the default values for each build option — always refer to the docker folder of the ZED ROS 2 Wrapper repository.

The entry point sets ROS_DOMAIN_ID to 0, the default value in ROS 2. If your setup requires a different value, change it in scripts/ros_entrypoint.sh before building your image, or run export ROS_DOMAIN_ID=<new_value> at the start of each interactive session. See the official ROS 2 documentation for details.

ROS 2 installation: APT or from source

The image installs ROS 2 from APT when binary packages exist for the selected distribution on the base image’s Ubuntu release, and builds ROS 2 from source otherwise. The decision is made automatically during the build by reading the Ubuntu codename of the base image.

From-source builds are heavy and best-effort. Building ROS 2 from source compiles the whole ROS 2 core and can take a long time. Each ROS 2 distribution also officially targets a specific Ubuntu release, so a from-source build on a different release is not guaranteed to compile.

Build the Docker images

First, check out the tag or commit of the wrapper you want to build:

$git checkout <branch_or_tag>

Desktop

$./build_desktop.sh [--ros-distro <ros2_distro>] [--os ubuntu-<XX.XX>] \
> [--sdk <X.Y.Z|latest>] [--cuda <XX.X>] [--sdk-url <URL>]

Run it without arguments to build with the default configuration. Desktop images always use the StereoLabs gl-devel base variant.

Jetson

$./build_jetson.sh [--ros-distro <ros2_distro>] [--os <jpX.Y.Z|l4t-rXX.X>] \
> [--sdk <X.Y.Z|latest>] [--sdk-url <URL>]

Run it without arguments to build with the default configuration. Jetson images always use the StereoLabs devel base variant.

Notes on inputs

  • --ros-distro selects the ROS 2 distribution to install. It must be supported on the base image’s Ubuntu release, otherwise the script fails fast before building.
  • --sdk latest resolves to the newest ZED SDK that has an image for the chosen platform on Docker Hub. Some platforms may not yet have an image for the very latest SDK, in which case latest resolves to the most recent SDK published for that platform.
  • --sdk-url <URL> builds against a custom or unreleased ZED SDK .run installer instead of a published image — see Build against a custom or unreleased ZED SDK.
  • The base image tag is validated against Docker Hub before the build. If a combination does not exist, the script stops with a clear message. Check the available tags on the StereoLabs Docker Hub page.

Build against a custom or unreleased ZED SDK

By default, the image is built on a stereolabs/zed base that already ships a published ZED SDK version. To build against a ZED SDK that is not published as a Docker image — a local build or a pre-release .run installer — pass it to --sdk-url, either as a URL or as a path to a local .run file:

$# Desktop: plain CUDA base + custom SDK installer.
$# --cuda must be a FULL version (X.Y.Z) to select an nvcr.io/nvidia/cuda tag.
$./build_desktop.sh --ros-distro <ros2_distro> --os ubuntu-<XX.XX> --cuda <X.Y.Z> \
> --sdk-url <URL_or_local_path_to_ZED_SDK.run>
$
$# Jetson: plain L4T JetPack base + custom SDK installer.
$# --os must be a FULL L4T version (l4t-rX.Y.Z) to select an nvcr.io/nvidia/l4t-jetpack tag.
$./build_jetson.sh --ros-distro <ros2_distro> --os l4t-r<X.Y.Z> \
> --sdk-url <URL_or_local_path_to_ZED_SDK.run>

When --sdk-url is set:

  • The base image is a plain NVIDIA image (nvcr.io/nvidia/cuda for desktop, nvcr.io/nvidia/l4t-jetpack for Jetson) instead of a stereolabs/zed image, and the SDK is installed inside the image by scripts/install_zed_sdk.sh.
  • --sdk is ignored, and no Docker Hub tag lookup is done. The full version is required (--cuda X.Y.Z on desktop, --os l4t-rX.Y.Z on Jetson) because the NVIDIA base image tag is derived from it — make sure that tag exists on nvcr.io.
  • If --sdk-url is a path to an existing local file, it is staged into the build context and copied in directly (no network fetch). Otherwise it is treated as a URL: it must return HTTP 200 (checked during the build) and point to a ZED SDK Linux .run installer matching the base CUDA/L4T and Ubuntu version.

Cross compilation

You can build a Jetson (arm64) image from an x86_64 desktop using QEMU emulation.

  1. Register the QEMU binfmt handlers so the host can run arm64 containers (run once per host; repeat after a reboot):

    $docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
  2. (Optional) Check that arm64 emulation works — this should print aarch64:

    $docker run --rm --platform linux/arm64 arm64v8/ubuntu uname -m
  3. Build the Jetson image, forcing the arm64 platform. build_jetson.sh calls docker build without --platform, so on an x86_64 host export DOCKER_DEFAULT_PLATFORM to make the (arm64-only) base image and every RUN step run under emulation:

    $DOCKER_DEFAULT_PLATFORM=linux/arm64 ./build_jetson.sh [options]

From-source builds under emulation are slow. A distribution with no APT binaries for its base Ubuntu release builds the whole ROS 2 core from source, which can take a couple of hours under QEMU. For a fast, binary (APT) build under emulation, pick a distribution whose target Ubuntu matches the base. Building natively on the Jetson™ itself needs neither QEMU nor DOCKER_DEFAULT_PLATFORM and avoids this overhead entirely.

Run the Docker image

NVIDIA runtime

The NVIDIA® drivers must be accessible from the container to run the ZED SDK on the GPU. You will need:

  • The nvidia container runtime installed, following this guide.
  • A GPU-enabled runtime environment, set with --gpus all or -e NVIDIA_DRIVER_CAPABILITIES=all.
  • Docker privileged mode, set with --privileged.

Network

Set up the network configuration to enable communication between the container, other containers, and the host:

  • --network=host: remove network isolation between the container and the Docker host.
  • --ipc=host: use the host system’s Inter-Process Communication namespace.
  • --pid=host: use the host system’s process ID namespace.

Display context for CUDA-based applications

Share the host DISPLAY with the container using -e DISPLAY=$DISPLAY.

The shared volume /tmp/.X11-unix/:/tmp/.X11-unix is also required.

Shared volumes

A few volumes should also be shared with the host:

  • /tmp/.X11-unix/:/tmp/.X11-unix: required to enable X11 server communication for CUDA-based applications.
  • /usr/local/zed/settings:/usr/local/zed/settings: required if you plan to use the robot in an area without internet access and you previously downloaded the camera calibration files by following this guide.
  • /usr/local/zed/resources:/usr/local/zed/resources: recommended to persist the ZED SDK AI models (Object Detection, Body Tracking, NEURAL depth) across runs. Use a different host folder per SDK version.
  • /dev:/dev: shares the video and other required devices.
  • /dev/shm:/dev/shm: required to use ROS 2 with shared memory.
  • For GMSL2 cameras (ZED X, ZED X One), you will also need:
    • /tmp:/tmp
    • /var/nvidia/nvcam/settings/:/var/nvidia/nvcam/settings/
    • /etc/systemd/system/zed_x_daemon.service:/etc/systemd/system/zed_x_daemon.service

Start the container

First, allow the container to access EGL display resources (required only once):

$sudo xhost +si:localuser:root

Then start an interactive session:

USB3 cameras

$docker run --runtime nvidia -it --privileged --network=host --ipc=host --pid=host \
> -e NVIDIA_DRIVER_CAPABILITIES=all -e DISPLAY=$DISPLAY \
> -v /tmp/.X11-unix/:/tmp/.X11-unix \
> -v /dev:/dev \
> -v /dev/shm:/dev/shm \
> -v /usr/local/zed/resources/:/usr/local/zed/resources/ \
> -v /usr/local/zed/settings/:/usr/local/zed/settings/ \
> <docker_image_tag>

GMSL2 cameras

$docker run --runtime nvidia -it --privileged --network=host --ipc=host --pid=host \
> -e NVIDIA_DRIVER_CAPABILITIES=all -e DISPLAY=$DISPLAY \
> -v /tmp:/tmp \
> -v /dev:/dev \
> -v /var/nvidia/nvcam/settings/:/var/nvidia/nvcam/settings/ \
> -v /etc/systemd/system/zed_x_daemon.service:/etc/systemd/system/zed_x_daemon.service \
> -v /usr/local/zed/resources/:/usr/local/zed/resources/ \
> -v /usr/local/zed/settings/:/usr/local/zed/settings/ \
> <docker_image_tag>

Once the container is running, see Using ZED with ROS 2 from a Docker Container and the ROS 2 documentation to launch the ZED nodes.