A ready to use template repository for setting up ROS 2 with VS Code and Docker using Dev Containers, allowing for easy development without the need to install ROS 2 or any other tools on the host machine, but using all your favorite tools and extensions (and also GUI apps inside container!)!
version: 0.1.0
Sources:
- https://docs.ros.org/en/iron/How-To-Guides/Setup-ROS-2-with-VSCode-and-Docker-Container.html
- https://containers.dev/guides
- https://code.visualstudio.com/docs/devcontainers/containers
- Clone this repository to your local machine and change the name to your desired project name
git clone git@github.com:clausqr/ros2-vscode-container-dev.git my_project_name- Break the link to this repo and get rid of the images of
readme.MD
cd my_project_name
rm -rf .git && git init
rm readme.MD && touch readme.MD
rm img/*.pngAlternatively, if you are using github, you can select "Use this template"
and select "Create new repository" and fill in the details and start working on your new repo. Then after cloning your new repo you only need to get rid of this readme and its imgs:
git clone <your_new_repo_url> my_project_name
cd my_project_name
rm README.md && touch README.md
rm img/*.png- Open the project folder in VS Code.
- Install the Remote - Containers extension.
- Customize
Dockerfileaccording to your project needs - Run the
create_devcontainer.bashscript to tailor the template to your uid and gid, and also username and custom image name.
bash create_devcontainer.bash -h
Usage: create_devcontainer.bash [option...] {--username|-u} {--user-uid|-i} {--user-gid|-g} {--image-name|-n}
-u, --username Username to replace in the JSON template
-i, --user-uid User UID to replace in the JSON template
-g, --user-gid User GID to replace in the JSON template
-n, --image-name Image name to replace in the JSON template
If no command line arguments are provided, the values from setup.env will be used.- Build the container searching for "build container" in the command palette.
- Alternatively, reopen the project in a container.

- Inside the container, open a terminal and you will be in the
/ros2_wsfolder, which is mapped to the local./ros2_wsfolder. - Start developing. A default
.gitignorefile is in place to ignore build artifacts and logs, which stay inside the container. - From there on you can develop for ROS2 without installing ROS2 or any other tool on the host:
user@host:/ros2_ws/src$ ros2
usage: ros2 [-h] [--use-python-default-buffering] Call `ros2 <command> -h` for more detailed usage. ...
ros2 is an extensible command-line tool for ROS 2.
options:
-h, --help show this help message and exit
--use-python-default-buffering
Do not force line buffering in stdout and instead use the python default buffering, which might be affected by PYTHONUNBUFFERED/-u and depends on whatever stdout is
interactive or not
Commands:
action Various action related sub-commands
bag Various rosbag related sub-commands
component Various component related sub-commands
daemon Various daemon related sub-commands
doctor Check ROS setup and other potential issues
interface Show information about ROS interfaces
launch Run a launch file
lifecycle Various lifecycle related sub-commands
multicast Various multicast related sub-commands
node Various node related sub-commands
param Various param related sub-commands
pkg Various package related sub-commands
run Run a package specific executable
security Various security related sub-commands
service Various service related sub-commands
topic Various topic related sub-commands
wtf Use `wtf` as alias to `doctor`
Call `ros2 <command> -h` for more detailed usage.
user@host:/ros2_ws/src$For an enhanced development experience, consider installing the Remote Development extension pack for VS Code. This extension pack allows you to connect to the container and see all packages and libraries corresponding to the environment as seen by the code to be run there. Main way to connect is via ssh:
To enable SSH access into the container, you can use the RR_SSH_ENABLED and RR_SSH_PORT options in the setup.env file.
RR_SSH_ENABLED: Set this to1to enable SSH access, or0to disable it.RR_SSH_PORT: Specify the port to use for SSH access. The default is20022.
When SSH is enabled, the script will mount the ~/.ssh folder from the host to the container. You can connect to the container using the same credentials as for the host as the default network mode is host.
With SSH enabled the container runs detached, with sshd as PID 1 (docker run -d), so its lifetime is the sshd lifetime — independent of the terminal that launched it, and startable from a script or service without a TTY. The workspace builds on first start (via ros2_ws/on_run.sh), so sshd may take a moment to start accepting connections; ./rr join waits for this. Stop the container with ./rr stop. With SSH disabled the container stays interactive (docker run -it, bash as PID 1) exactly as before.
ROS env over SSH: a non-interactive
ssh host 'cmd'does not inherit PID 1's environment (PAM stripsdocker -evars). The image sources ROS from/etc/profile.d/ros.shabove the non-interactive guard in~/.bashrcso login shells get it, andon_run.shre-emits the DDS/RMW env into/etc/profile.d/dds-env.shfor the same reason. If you add your own env, follow that pattern.
Connect using the following command:
ssh -l <username> -p <port> <container_ip>Replace <username> with the value of RR_USERNAME from setup.env, <port> with the value of RR_SSH_PORT, and <container_ip> with the IP address of the container.
Example:
ssh -l user -p 20022 192.168.1.100The container runs with --net=host, which means an uncontained DDS will
multicast SPDP discovery (239.255.0.1) onto your real NICs and expose every
ROS 2 topic to any node on the LAN sharing the same ROS_DOMAIN_ID. To prevent
this leak, ./config ships two loopback profiles that pin all RTPS traffic to
127.0.0.1:
config/cyclonedds_loopback.xml— forrmw_cyclonedds_cppconfig/fastdds_loopback.xml— forrmw_fastrtps_cpp(the ROS 2 default)
run bind-mounts ./config into the container at /ros2_ws/config and sets
CYCLONEDDS_URI / FASTRTPS_DEFAULT_PROFILES_FILE to point at them, so
containment is active out of the box for whichever RMW you use. The relevant
setup.env knobs:
RR_CYCLONEDDS_URI,RR_FASTRTPS_PROFILE— paths to the profiles. Blank both to opt out of containment entirely.RR_ROS_LOCALHOST_ONLY— kept0on purpose; the XML profile is the single source of truth (see the comments insetup.envfor why mixing the two breaks discovery).
Note: with the SSH dev interface,
docker -eenv vars reach the container's PID 1 and interactive shells but not PAM-spawned SSH login shells. If you work over SSH, also export these from a login profile (e.g./etc/profile.d).
Some convenience scripts are provided, they use a single setup.env file to configure the user and image names.
If you are running multiple instances, you can run each with a custom instance_name command line argument.
All scripts can be executed using the rr wrapper script for convenience:
./rr <script_name> [args]For example: ./rr run --name my_container
./rr buildBuild a Docker image with the username, user ID, group ID, image name, and ROS distro specified in the setup.env file.
./rr forcebuildForce build a Docker image with the --no-cache flag, useful when you need to rebuild from scratch.
./rr cleanupClean up the ros2_ws artifacts, specifically the build, install, and log directories.
./rr create_devcontainerCreate a devcontainer.json file from the devcontainer-template.json template by replacing placeholders with values from setup.env.
./rr join [--name <container_name>]Join a running container using the specified container name, username, user ID, and group ID from setup.env. The script waits for the container to be running and for the ROS 2 setup to be complete before joining.
Options:
--name <container_name>: Specify the container name to join. If not provided, the default container name from setup.env is used.
Description: If the container is running, this script will kill it. Usage:
./rr kill [--name <container_name>]Options:
--name <container_name>: Specify the container name to kill. If not provided, the default container name from setup.env is used.
Description: Run a Docker container with configurations defined in setup.env. This script automatically detects and configures:
- Display availability for GUI applications
- Joystick availability (
/dev/input/js0) - NVIDIA GPU availability
- SSH access (if enabled in setup.env)
Usage:
./rr run [--name <container_name>] [--device <device>]Options:
--name <container_name>: Specify a custom name for the Docker container. If not provided, a default name from setup.env is used.--device <device>: Specify a device to mount in the container. This option can be used multiple times to mount multiple devices.--help: Display usage information and exit.
Examples:
Run container with custom name and mount two devices, /dev/ttyUSB17 (mapped to /dev/ttyUSB0 inside the container) and /dev/ttyACM0:
./rr run --name my_container --device /dev/ttyUSB17:/dev/ttyUSB0 --device /dev/ttyACM0Run with joystick support (automatically detected if /dev/input/js0 exists):
./rr run --name my_robot_containerDescription: Stop a running container with the specified name. Usage:
./rr stop [--name <container_name>]Options:
--name <container_name>: Specify the container name to stop. If not provided, the default container name from setup.env is used.
The run script now includes automatic detection and configuration for:
- Display: Automatically mounts X11 display, DRI devices, and shared memory for GUI applications
- Joystick: Automatically mounts
/dev/input/js0if available for joystick support in ROS 2 - NVIDIA GPU: Automatically enables GPU support if
nvidia-smiis available - SSH: Mounts SSH keys if SSH access is enabled in
setup.env
The setup.env file contains all configuration options:
# Group ID of the user on the host machine
RR_USER_GID=$(id -g $(whoami))
# User ID of the user on the host machine
RR_USER_UID=$(id -u)
# Username to be used inside the container
RR_USERNAME=user
# Name of the Docker image to be built
RR_IMAGE_NAME=devcontainer_image
# ROS 2 distribution to be used (e.g., foxy, jazzy, etc.)
RR_ROS_DISTRO=jazzy
# Enable SSH access to the container (0 = disabled, 1 = enabled)
RR_SSH_ENABLED=0
# Port to use for SSH access if SSH is enabled
RR_SSH_PORT=20022- Squash previous WIP items.
- Offload config to separate
setup.envfile. - Allow multiple instances.
- Check/automate GPU handling.
- Check/automate/allow mounting devices (i.e.
/dev/ttyUSB0). - Add joystick support with automatic detection.
- Improve script documentation and add help options.
- Add
rrwrapper script for easier script execution. - Integration with github codespaces (get rid of mounts for X11 and GPUs).

