Skip to navigation

Running Isaac Lab in Docker

View as Markdown

Docker packages Isaac Lab and its dependencies into a reusable environment. docker/container.py is the main entry point for running Isaac Lab in a container. It wraps docker compose so that you do not have to remember which Dockerfile, environment files, and volume mounts belong together — you name a profile, and the script assembles the rest.

The image it builds is also the basis for the other workflows: Running on HPC clusters converts it to an Apptainer image, and Cloud Deployment provisions a machine that runs it.

The standard Isaac Lab container depends on the Isaac Sim Docker image. By running that container, you are implicitly agreeing to the NVIDIA Software License Agreement. If you do not agree to the EULA, do not run that container. The kitless image contains neither Isaac Sim nor Kit.

Prerequisites

Install Docker Engine, Docker Compose, and the NVIDIA Container Toolkit. The container is tested with Docker Engine 26.0.0 and Docker Compose 2.25.0; use these versions or newer. Follow the post-installation steps so that Docker runs without sudo.

The Isaac Sim documentation on container installation covers the same prerequisites in more detail, including how to obtain access to the Isaac Sim image.

Due to limitations with snap, please make sure the Isaac Lab directory is placed under the /home directory tree when using docker.

Container profiles

A profile is a Docker Compose profile: docker-compose.yaml tags each service with one, and Compose starts only the services whose profile is active. Naming a profile therefore selects a matched set — one service, one Dockerfile, one set of environment files, and one image name:

ProfileCompose serviceDockerfileEnvironment filesBuilt on top of
baseisaac-lab-baseDockerfile.base.env.basethe Isaac Sim image
ros2isaac-lab-ros2Dockerfile.ros2.env.base + .env.ros2the base image, built first
kitlessisaac-lab-kitlessDockerfile.kitless.env.kitlessnothing — standalone, no Isaac Sim

Every command below takes the profile as its first positional argument and defaults to base. Only one profile applies at a time, and the resulting image and container are both named isaac-lab-<profile>. Pass --suffix to append a name suffix when you want several variants of the same profile side by side. Do not use --suffix with cluster deployments, whose export commands expect the unsuffixed image name.

For what each image actually contains and how to choose between them, see Container images.

Start, run, and retrieve results

From the repository root on the host, build and start the default base container, then open a shell in it. Append a profile to target a different image:

./docker/container.py start
./docker/container.py enter

Inside the container, run the logging example without a visualizer:

uv run isaaclab -p scripts/tutorials/00_sim/log_time.py --viz none

The script writes simulation time at each step to /workspace/isaaclab/logs/docker_tutorial/log.txt. Stop the script with Ctrl+C and type exit to return to the host shell. Retrieve the results before stopping the container:

./docker/container.py copy
./docker/container.py stop

The log is now at docker/artifacts/logs/docker_tutorial/log.txt on the host. The stop command removes the container and its Compose-managed volumes, including logs and caches; copy out anything you need first. The image remains. To remove it after stopping, run docker image rm isaac-lab-base; the next start rebuilds it. See Docker pruning for other cleanup options.

# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""
This script demonstrates how to generate log outputs while the simulation plays.
It accompanies the tutorial on docker usage.
~~~bash
# Usage
uv run python scripts/tutorials/00_sim/log_time.py
~~~
"""
"""Parse the command-line arguments first."""
import argparse
import os
from isaaclab.app import add_launcher_args, launch_simulation
# create argparser
parser = argparse.ArgumentParser(description="Tutorial on creating logs from within the docker container.")
# append simulation launcher cli args
add_launcher_args(parser)
# parse the arguments
args_cli = parser.parse_args()
"""Rest everything follows."""
from isaaclab.sim import SimulationCfg, SimulationContext
def main():
"""Main function."""
# Specify that the logs must be in logs/docker_tutorial
log_dir_path = os.path.join("logs")
if not os.path.isdir(log_dir_path):
os.mkdir(log_dir_path)
# In the container, the absolute path will be
# /workspace/isaaclab/logs/docker_tutorial, because
# all Python execution is done through the uv-managed workspace
# and the calling process' path will be /workspace/isaaclab
log_dir_path = os.path.abspath(os.path.join(log_dir_path, "docker_tutorial"))
if not os.path.isdir(log_dir_path):
os.mkdir(log_dir_path)
print(f"[INFO] Logging experiment to directory: {log_dir_path}")
# Configure the simulation
sim_cfg = SimulationCfg(dt=0.01, device=args_cli.device)
# Launch the simulator runtime that the configuration needs
with launch_simulation(sim_cfg, args_cli):
# Initialize the simulation context
sim = SimulationContext(sim_cfg)
# Set main camera
sim.set_camera_view([2.5, 2.5, 2.5], [0.0, 0.0, 0.0])
# Play the simulator
sim.reset()
# Now we are ready!
print("[INFO]: Setup complete...")
# Prepare to count sim_time
sim_dt = sim.get_physics_dt()
sim_time = 0.0
# Open logging file
with open(os.path.join(log_dir_path, "log.txt"), "w") as log_file:
# Simulate physics
while sim.is_running():
log_file.write(f"{sim_time}" + "\n")
# perform step
sim.step()
sim_time += sim_dt
if __name__ == "__main__":
# run the main function
main()

The image copies the repository to /workspace/isaaclab at build time, so edits made after the build are not picked up automatically. To keep the development loop fast, the compose file bind-mounts source, scripts, docs, and tools from the host, so changes to those directories appear inside the container immediately. Everything else requires a rebuild.

container.py command reference

container.py command reference

Run ./docker/container.py --help to list commands and ./docker/container.py <command> --help for that command’s arguments.

CommandDescription
buildBuild the image without creating a container.
startBuild the image and start the container in the background.
enterOpen a Bash shell in the running container.
configPrint the merged Compose configuration. Use --output-yaml <path> to write it to a file instead.
copyCopy logs, data, and built documentation to docker/artifacts on the host.
stopStop and remove the container and its Compose-managed volumes.

Every command accepts the following arguments:

ArgumentDescription
profileOptional profile name; defaults to base. Other profiles are ros2 and kitless.
--files [FILE ...]Merge additional Compose YAML files after docker-compose.yaml, in the supplied order.
--env-files [FILE ...]Merge additional environment files after the profile defaults, in the supplied order.
--suffix [SUFFIX]Append -SUFFIX to the image and container names. Omit it for unsuffixed names.
--infoPrint the resolved container interface configuration instead of running the command.
-h, --helpShow help and exit.

Extending the Compose configuration

--files and --env-files merge extra Compose and environment files into the generated configuration, which is how optional components are layered on without editing the checked-in files. Streaming to XR devices is the worked example — it adds the CloudXR Runtime service alongside base:

./docker/container.py start --files docker-compose.cloudxr-runtime.patch.yaml --env-files .env.cloudxr-runtime

Stop it with the same arguments. The teleoperation setup, firewall rules, and client connection steps are covered in Setting up Isaac Capture with CloudXR. Use ./docker/container.py config to print the merged result when a combination does not behave as expected.

What persists between runs

The compose file declares named volumes for Isaac Sim caches, logs, and your own data. They remain available while reusing a container, but container.py stop runs docker compose down --volumes` and removes them. Run `container.py copy` before `stop to preserve your results on the host.

container.py copy extracts the three volumes you are most likely to want on the host — logs, data_storage, and docs/_build — into docker/artifacts. For anything else, use docker cp, for example docker cp isaac-lab-base:/workspace/isaaclab/logs ..

If you are upgrading from an Isaac Lab image that ran as root, the existing volumes still hold root-owned files that the current uid/gid 1000 runtime user cannot write. Copy out anything worth keeping, then recreate them from the docker directory:

docker compose --file docker-compose.yaml --profile base --env-file .env.base down --volumes
Volume NameDescriptionContainer Path
isaac-cache-kitStores cached Kit resources/isaac-sim/kit/cache
isaac-data-kitStores Kit data/isaac-sim/kit/data
isaac-cache-ovStores cached OV resources/root/.cache/ov
isaac-cache-pipStores cached pip resources/root/.cache/pip
isaac-cache-glStores cached GLCache resources/root/.cache/nvidia/GLCache
isaac-cache-computeStores cached compute resources/root/.nv/ComputeCache
isaac-cache-uvStores uv downloads for the kit-less profile/home/isaaclab/.cache/uv
isaac-cache-warpStores Warp kernels for the kit-less profile/home/isaaclab/.cache/warp
isaac-logsStores logs generated by Omniverse/root/.nvidia-omniverse/logs
isaac-carb-logsStores logs generated by carb/isaac-sim/kit/logs/Kit/Isaac-Sim
isaac-dataStores data generated by Omniverse/root/.local/share/ov/data
isaac-docsStores documents generated by Omniverse/root/Documents
isaac-lab-docsStores documentation of Isaac Lab when built inside the container/workspace/isaaclab/docs/_build
isaac-lab-logsStores logs generated by Isaac Lab workflows when run inside the container/workspace/isaaclab/logs
isaac-lab-dataStores whatever data users may want to preserve between container runs/workspace/isaaclab/data_storage

Run docker volume ls on the host to find the actual names, which include the Compose project prefix, then use docker volume inspect <volume-name>. The kitless profile uses the uv and Warp caches and shares the documentation, logs, and data volumes with the others.

Display forwarding with X11

X11 forwarding lets GUI applications started inside the container display on the host. The first start asks whether to enable it and records the answer in docker/.container.cfg. To change it later, set X11_FORWARDING_ENABLED to 0 or 1 in that file and run start again — the rebuild is what applies the change.