Docker Installation#

This page walks you through running the simulation inside a Docker container. This approach isolates Gazebo Harmonic from any Gazebo Classic installation on your host machine.

The pre-built Docker image includes:

  • ROS 2 Jazzy desktop installation

  • Gazebo Harmonic simulator

  • Husarion ROSbot simulation packages (robot model with LiDAR, camera, and IMU sensors)

Terminology

Throughout this guide, two terms are used to distinguish between your machine and the Docker container:

  • Host – your physical Ubuntu machine (where Docker is installed and where you run docker commands).

  • Container (or remote) – the isolated Docker environment where ROS 2, Gazebo, and the simulation packages run. When VS Code is attached to the container, it refers to it as a remote environment.

Step 1: Install Docker Engine#

Install Docker Engine on Ubuntu

Follow the official guide: Install Docker Engine on Ubuntu

After installation, add your user to the docker group so you can run containers without sudo:

sudo usermod -aG docker $USER
  • usermod -aG docker adds your user to the docker group.

  • Without this, every docker command would require sudo.

Log out and log back in for the group change to take effect. Verify with:

docker run hello-world
  • This downloads a tiny test image and runs it.

  • You should see a “Hello from Docker!” message confirming Docker is working.

Install NVIDIA Container Toolkit (GPU acceleration)

GPU acceleration is strongly recommended – without it, Gazebo will fall back to software rendering and be very slow.

Note

If you do not have an NVIDIA GPU, skip this step entirely. The simulation will still work but will run more slowly using software rendering.

Add the NVIDIA container toolkit repository and install:

curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \
    | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \
    | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \
    | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt update
sudo apt install -y nvidia-container-toolkit
  • The first two commands download NVIDIA’s GPG key and add their package repository to your system.

  • nvidia-container-toolkit is the package that allows Docker containers to access your NVIDIA GPU.

Configure Docker to use the NVIDIA runtime:

sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
  • nvidia-ctk runtime configure registers the NVIDIA runtime with Docker so containers can use GPU hardware.

  • Restarting Docker applies the new configuration.

Verify GPU access inside Docker:

docker run --rm --gpus all nvidia/cuda:12.3.1-base-ubuntu22.04 nvidia-smi
  • --rm removes the container after it exits (this is just a test).

  • --gpus all passes all available GPUs into the container.

  • nvidia-smi prints GPU information. You should see your GPU listed in the output.

Step 2: Pull the Docker Image#

Pull the pre-built image

The course Docker image is hosted on Docker Hub. Pull it with:

docker pull zeidk/enpm605-sim:latest
  • docker pull downloads a pre-built image from Docker Hub to your machine.

  • zeidk/enpm605-sim:latest is the image name – it contains ROS 2 Jazzy, Gazebo Harmonic, and the ROSbot simulation workspace already compiled.

  • The download is ~4–6 GB. No further compilation is needed (unless indicated)

Verify the image was downloaded:

docker images | grep enpm605-sim

You should see a row showing zeidk/enpm605-sim with the latest tag.

Step 3: Create and Start the Container#

Allow X11 Display Access

Before starting the container, allow Docker to access your display server so Gazebo and RViz windows appear on your screen:

xhost +local:docker
  • xhost controls access to your X11 display server (the system that draws windows on your screen).

  • +local:docker grants display access to all local Docker containers.

  • You will see: non-network local connections being added to access control list

Tip

Run this command once per login session. If you log out and back in, you need to run it again.

Create the Container

Important

You only need to run docker run once to create the container. After that, use docker start / docker exec to restart it (see Container Lifecycle).

docker run -it \
    --name enpm605 \
    --gpus all \
    -e DISPLAY=$DISPLAY \
    -e QT_X11_NO_MITSHM=1 \
    -v /tmp/.X11-unix:/tmp/.X11-unix:rw \
    -v ~/enpm605_ws:/home/zeid/enpm605_ws \
    --network host \
    zeidk/enpm605-sim:latest \
    bash
docker run -it \
    --name enpm605 \
    -e DISPLAY=$DISPLAY \
    -e QT_X11_NO_MITSHM=1 \
    -e LIBGL_ALWAYS_SOFTWARE=1 \
    -v /tmp/.X11-unix:/tmp/.X11-unix:rw \
    -v ~/enpm605_ws:/home/zeid/enpm605_ws \
    --network host \
    zeidk/enpm605-sim:latest \
    bash

The differences from the GPU version:

  • --gpus all is removed (no GPU to pass through).

  • -e LIBGL_ALWAYS_SOFTWARE=1 is added, which forces OpenGL to use software rendering instead of looking for GPU hardware.

Here is what each flag does:

docker run

Create and start a new container from the image.

-it

Run interactively (-i) with a terminal (-t) so you get a bash prompt.

--name enpm605

Name the container enpm605 so you can refer to it easily later (stop, start, attach).

--gpus all

Pass all NVIDIA GPUs into the container for hardware- accelerated rendering (GPU tab only).

-e DISPLAY=$DISPLAY

Forward the DISPLAY environment variable so GUI applications (Gazebo, RViz) appear on your screen.

-e QT_X11_NO_MITSHM=1

Disable shared memory for Qt applications, which prevents rendering errors inside Docker.

-v /tmp/.X11-unix:/tmp/.X11-unix:rw

Mount the X11 socket so the container can communicate with your display server.

-v ~/enpm605_ws:/home/zeid/enpm605_ws

Mount your work directory. Files you create inside ~/enpm605_ws in the container are saved to ~/enpm605_ws on your host machine. Your code is preserved even if you delete the container.

--network host

Share the host’s network so ROS 2 nodes inside and outside the container can discover each other.

zeidk/enpm605-sim:latest

The Docker image to use.

bash

The command to run inside the container (a bash shell).

After running this command you will be dropped into a bash shell inside the container. Your working directory will be /home/zeid/. You now have access to ROS 2 and Gazebo.

Step 4: Verify the Setup#

Once inside the container, verify that everything works by launching a quick test simulation.

Quick Gazebo Test

From the container shell, run:

ros2 launch rosbot_gazebo empty_world.launch.py

This launches Gazebo Harmonic with the ROSbot in an empty world. You should see:

  • A Gazebo window opens on your screen showing an empty environment with the ROSbot model.

  • An RViz window opens showing the robot’s sensor data.

If both windows appear and the robot is visible, your setup is working correctly. Close both windows (Ctrl+C in the terminal) before proceeding.

Note

If Gazebo does not open or you see a cannot open display error, make sure you ran xhost +local:docker on the host before starting the container. See the Troubleshooting section below.

Working Inside the Container#

Key Directories

When you are inside the container, your home directory is /home/zeid/. The main workspace is:

~/enpm605_ws/

Your workspace (mounted from the host at ~/enpm605_ws/). This directory contains both the pre-built ROSbot simulation packages (under src/rosbot_sim/) and your own ROS 2 packages. Files persist on your host machine even if the container is deleted.

~/enpm605_ws/src/rosbot_sim/

The ROSbot simulation packages (robot model, Gazebo worlds, launch files). Do not modify these packages.

~/enpm605_ws/src/

Create your own ROS 2 packages here alongside rosbot_sim/.

Open Additional Terminals

To open a second (or third, etc.) terminal in the same running container:

docker exec -it enpm605 bash
  • docker exec runs a command inside an already-running container.

  • -it gives you an interactive terminal.

  • enpm605 is the container name you set with --name.

  • bash starts a new shell session.

This is useful for running ros2 topic echo or teleop_twist_keyboard in a separate terminal while the simulation is running.

Container Lifecycle#

Exiting the Container

To leave a container terminal, type:

exit
  • If this is the only terminal attached to the container (the one from docker run), the container stops.

  • If this is an additional terminal (opened with docker exec), only that shell closes – the container keeps running.

You can also press Ctrl+D to exit the shell.

Tip

If you want to detach from the container without stopping it, press Ctrl+P followed by Ctrl+Q. This returns you to the host shell while the container continues running in the background.

Stopping and Restarting the Container

The container persists after you exit, so you can restart it later without losing any installed packages or configuration changes inside the container.

Stop the container (when you are done working):

docker stop enpm605
  • This gracefully shuts down the container. It is not deleted – all state inside the container is preserved.

Restart and reattach (next time you want to work):

xhost +local:docker
docker start enpm605
docker exec -it enpm605 bash
  • xhost +local:docker re-enables display access (needed once per login session).

  • docker start restarts a previously stopped container with all its state intact.

  • docker exec attaches a new terminal to it.

Permanently remove the container (if you want a fresh start):

docker rm enpm605
  • This deletes the container and all state inside it.

  • Files in ~/enpm605_ws on your host machine are not affected because they live on the host, not inside the container.

  • After removing, you can create a new container with docker run again.

Connecting with VS Code#

VS Code can attach directly to the running container, giving you a full editor with IntelliSense, integrated terminal, and file browsing – all running inside the Docker environment.

The following diagram shows the complete pipeline:

Pipeline showing how to connect VS Code to the Docker container

Step-by-step instructions

Important

The container must be running before VS Code can attach to it. Always start the container from a terminal first (using docker run or docker start), then attach VS Code.

1. Install the Dev Containers extension

Open VS Code on your host machine and install the Dev Containers extension (ms-vscode-remote.remote-containers):

code --install-extension ms-vscode-remote.remote-containers

This extension allows VS Code to connect to Docker containers and use them as a full development environment.

2. Start the container (if not already running)

In a host terminal (not VS Code), run:

xhost +local:docker
docker start enpm605

If you have not created the container yet, run the docker run command from Step 3 first.

3. Attach VS Code to the container

  • Open the VS Code Command Palette (Ctrl+Shift+P)

  • Type Dev Containers: Attach to Running Container...

  • Select enpm605 from the list

VS Code will open a new window connected to the container. The bottom-left corner will show Container enpm605 confirming you are working inside the container.

4. Open a folder

Once attached, use File > Open Folder and navigate to:

  • /home/zeid/enpm605_ws/ – your workspace (mounted from host – changes are saved on your machine)

5. Install extensions inside the container

Extensions run inside the container and need to be installed there separately from your host VS Code. VS Code will prompt you to install recommended extensions. You can also install them manually:

  • Python (ms-python.python)

  • ROS (ms-iot.vscode-ros)

6. Use the integrated terminal

Open a terminal inside VS Code with Ctrl+` ` – this terminal runs inside the container and has full access to ROS 2 and Gazebo commands. You can open multiple terminals from within VS Code, eliminating the need to run docker exec from separate host terminals.

Launching the Simulation#

Once your environment is set up (and verified with the quick test in Step 4), launch the full simulation using the shared instructions on the Launching the Simulation page.

Troubleshooting#

package 'ros_gz_bridge' not found

The ROS–Gazebo bridge packages are not installed in the container. Install them:

sudo apt update
sudo apt install -y ros-jazzy-ros-gz

Then source the environment again:

source /opt/ros/jazzy/setup.bash
source ~/enpm605_ws/install/setup.bash
cannot open display error

The container cannot connect to your display server.

  • Ensure you ran xhost +local:docker on the host before starting the container.

  • Check that the DISPLAY variable is set inside the container:

echo $DISPLAY

It should show :0 or :1. If it is empty, the -e DISPLAY=$DISPLAY flag was not passed correctly.

Gazebo is extremely slow (no GPU)

The simulator is likely using software rendering.

  • Run nvidia-smi inside the container. If it fails, the GPU is not being passed through.

  • Ensure --gpus all was included in your docker run command.

  • Ensure the NVIDIA Container Toolkit is installed on the host (see Step 1).

  • Restart Docker on the host: sudo systemctl restart docker.

permission denied when running Docker commands

Your user is not in the docker group.

sudo usermod -aG docker $USER

Then log out and log back in. Alternatively, run newgrp docker to apply the change in your current session only.

docker: Error response from daemon: Conflict

A container named enpm605 already exists. Either restart it:

docker start enpm605
docker exec -it enpm605 bash

Or remove it and create a new one:

docker rm enpm605
# Then run the docker run command again