The first part of the session covers Docker fundamentals: how to create images, manage containers, and configure networks between applications. The second part focuses on deploying ROS and ROS2 inside containers, demonstrating how Docker simplifies experimentation, teaching setups, and development workflows. The session will also show how Docker can help bridge ROS and ROS2, easing the migration process between the two frameworks.
-
Update your existing packages:
sudo apt update sudo apt upgrade -y
-
Install required packages:
sudo apt install apt-transport-https ca-certificates curl software-properties-common -y
-
Add Docker’s official GPG key:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker.gpg -
Add the Docker repository:
echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
-
Install Docker Engine:
sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io -y
-
Verify installation:
docker --version
-
Remove old Docker versions (if any):
sudo dnf remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine
-
Install dependencies:
sudo dnf -y install dnf-plugins-core
-
Add the Docker repository:
sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo
-
Install Docker Engine:
sudo dnf install docker-ce docker-ce-cli containerd.io -y
-
Start and enable Docker:
sudo systemctl start docker sudo systemctl enable docker -
Verify installation:
docker --version
-
Update your system:
sudo pacman -Syu
-
Install Docker:
sudo pacman -S docker
-
Start and enable the Docker service:
sudo systemctl start docker sudo systemctl enable docker -
(Optional) Run Docker without
sudo:sudo usermod -aG docker $USER newgrp docker -
Verify installation:
docker --version
-
Add Docker to your NixOS configuration. Edit
/etc/nixos/configuration.nixand enable Docker:services.docker.enable = true;
-
Apply the configuration:
sudo nixos-rebuild switch
-
(Optional) Allow your user to run Docker without
sudo. Add your username to thedockergroup:users.users.yourusername.extraGroups = [ "docker" ];
Then rebuild again:
sudo nixos-rebuild switch
-
Verify installation:
docker --version
-
Download Docker Desktop for Windows from the official Docker site: 👉 https://www.docker.com/products/docker-desktop/
-
Run the installer and follow the setup wizard.
-
Ensure WSL 2 (Windows Subsystem for Linux) is enabled. If not, Docker Desktop will guide you through enabling it.
-
After installation, restart your machine if required.
-
Verify installation by opening PowerShell or Command Prompt:
docker --version
-
Download Docker Desktop for Mac from the official Docker site: 👉 https://www.docker.com/products/docker-desktop/
-
Open the
.dmgfile and drag Docker to your Applications folder. -
Launch Docker Desktop from Applications.
-
Follow the setup wizard and grant permissions if prompted.
-
Verify installation:
docker --version
After installing Docker, it’s a good idea to verify everything works correctly.
-
Check the Docker service status (Linux only):
sudo systemctl status docker
If it’s not running, start it:
sudo systemctl start docker
-
Run the Docker “Hello World” test image:
docker run hello-world
-
If Docker is installed correctly, you’ll see a message:
Hello from Docker! This message shows that your installation appears to be working correctly.
-
-
Download Docker images that are going to be used in this workshop (saves time)
docker pull ubuntu:latest ubuntu:20.04 jupyter/scipy-notebook theasp/novnc:latest alpine nginxdemos/hello ollama/ollama:0.11.5-rc5 ghcr.io/open-webui/open-webui:main osrf/ros:noetic-desktop-full-focal osrf/ros:jazzy-desktop-full mjpc13/foxglove-bridge andrejorsula/ros1_bridge:foxy
Create a simple Dockerfile:
#Dockerfile
FROM ubuntu
RUN apt-get -y update && apt-get -y install neofetch
CMD [ "neofetch" ]
Build the docker image with:
docker build -t docker-tutorial .
And create and run the container:
docker run --rm docker-tutorial
What if we want a specific Ubuntu version? In the Dockerfile replace FROM ubuntu with FROM ubuntu:20.04:
#Dockerfile
FROM ubuntu:20.04
RUN apt-get -y update && apt-get -y install neofetch
CMD [ "neofetch" ]
and build again the new image:
docker build -t docker-tutorial .
And create and run the container:
docker run --rm docker-tutorial
Public available images are usually found in DockerHub.
Create a new environmental variable, append to a file and print it to the console:
#Dockerfile
FROM ubuntu:20.04
RUN apt-get -y update && apt-get -y install neofetch nano
ENV VAR1="HELLO WORLD!!!"
WORKDIR /workspace
RUN echo "We just wrote $VAR1" >> example.txt
CMD [ "cat", "example.txt" ]
build again the Docker image:
docker build -t docker-tutorial .
and run it:
docker run docker-tutorial
Create a new .txt file in the same directory as the Dockerfile;
You use the COPY command to copy files:
#Dockerfile
FROM ubuntu:20.04
RUN apt-get -y update && apt-get -y install neofetch nano
ENV VAR1 "HELLO WORLD!!!"
WORKDIR /workspace
COPY example.txt example.txt
CMD [ "cat", "example.txt" ]
and run it:
docker run docker-tutorial
Jump into a bash session:
docker run -it docker-tutorial bash
Edit the file (add some lines or delete characters), with either:
- With
nano:nano example.txt - Append some text to it:
echo "\n I am too lazy to edit the file myself" >> example.txt
Mount directory example_dir into a docker container:
docker run -it --rm -v example_dir:/workspace/my_dir docker-tutorial bash
The Docker CLI is the main way to interact with Docker. Below are some of the most useful commands:
-
Check version
docker --version
Shows the installed Docker version.
-
List images
docker images
Displays downloaded container images.
-
Download an image
docker pull <image>
Fetches an image from Docker Hub.
-
Run a container
docker run <image>
Creates and starts a new container from an image.
-
List running containers
docker ps
Shows active containers.
-
List all containers (including stopped)
docker ps -a
-
Stop a container
docker stop <container_id>
-
Remove a container
docker rm <container_id>
-
Remove an image
docker rmi <image_id>
-
View logs of a container
docker logs <container_id>
Run a Jupyter scipy-notebook:
docker run --rm --name jupyter-scipy -p 8000:8888 jupyter/scipy-notebook
Create a Dockerfile with a gui application:
#Dockerfile
FROM ubuntu:20.04
RUN apt-get -y update && apt-get -y install x11-apps
CMD [ "xeyes" ]
build the image:
docker build -t vis-tutorial .
run a container:
docker run -it --rm --env="DISPLAY" --env="QT_X11_NO_MITSHM=1" --volume="/tmp/.X11-unix:/tmp/.X11-unix:rw" vis-tutorial
If you have the error Error: Can't open display: :0 you need to run the following command:
xhost +local:root
Watch the application in localhost
To use a VNC server we need to start multiple containers at a time. We can use Docker Compose to easily manage multiple containers.
Now we are starting multiple containers at a time:
#docker-compose.yaml
# Creates a network
networks:
vnc-net:
name: vnc-net
services:
#Creates and runs the VNC server
novnc:
image: theasp/novnc:latest
container_name: novnc
ports:
- "8085:8080"
environment:
DISPLAY_WIDTH: 3000
DISPLAY_HEIGHT: 1800
RUN_XTERM: "no"
networks:
- vnc-net
restart: unless-stopped
# Runs the xeyes application
xeyes:
image: vis-tutorial
container_name: xeyes
networks:
- vnc-net
depends_on:
- novncRun the compose file with:
docker compose up
The docker-compose.yaml example:
# Creates a network
networks:
example-net:
services:
#Pings APP2 from APP1
app1:
image: alpine
container_name: app1
networks:
- example-net
command: ["ping", "app2"]
#Pings APP1 from APP2
app2:
image: alpine
container_name: app2
networks:
- example-net
command: ["ping", "app1"]The docker-compose.yaml example:
services:
web-server:
image: nginxdemos/hello
container_name: web-host
network_mode: "host"Ollama Dockerfile with a light LLM (tinyllama):
FROM ollama/ollama:0.11.5-rc5
RUN ollama pull tinyllama
#RUN ollama serve & sleep 5 ; ollama pull tinyllama ; \
# echo "kill 'ollama serve' process" ; \
# ps -ef | grep 'ollama serve' | grep -v grep | awk '{print $2}' | xargs -r kill -9
networks:
ollama-net:
services:
ollama:
build:
context: .
dockerfile: Dockerfile
networks:
- ollama-net
open-webui:
image: ghcr.io/open-webui/open-webui:main
networks:
- ollama-net
ports:
- "3000:8080"
environment:
- OLLAMA_BASE_URL=http://ollama:11434#Dockerfile
FROM osrf/ros:noetic-desktop-full-focal
ENV DEBIAN_FRONTEND=noninteractive
ENV PYTHON_VERSION=python3
ENV ROS_WS=/catkin_ws
### INSTALL DEPENDENCIES ###
RUN apt-get update \
&& apt-get install -y curl git \
ros-$ROS_DISTRO-roscpp-tutorials \
ros-$ROS_DISTRO-rosbridge-suite \
${PYTHON_VERSION}-catkin-tools
### END INSTALL DEPENDENCIES ###
SHELL ["/bin/bash", "-l", "-c"]
WORKDIR ${ROS_WS}
#docker-compose.yaml
networks:
ros1-net:
services:
master:
image: osrf/ros:noetic-desktop-full-focal
container_name: master
networks:
- ros1-net
command: "roscore"
talker:
build:
context: .
dockerfile: Dockerfile
container_name: ros1-talker
environment:
- ROS_MASTER_URI=http://master:11311
depends_on:
- master
networks:
- ros1-net
command: ["rosrun", "roscpp_tutorials", "talker"]
listener:
build:
context: .
dockerfile: Dockerfile
container_name: ros1-listener
environment:
- ROS_MASTER_URI=http://master:11311
depends_on:
- master
networks:
- ros1-net
command: ["rosrun", "roscpp_tutorials", "listener"]
ros-bridge:
build:
context: .
dockerfile: Dockerfile
container_name: ros-bridge
environment:
- ROS_MASTER_URI=http://master:11311
depends_on:
- master
networks:
- ros1-net
ports:
- "9090:9090"
command: ["roslaunch", "rosbridge_server", "rosbridge_websocket.launch", "--wait"]#Dockerfile
FROM osrf/ros:jazzy-desktop-full
ENV DEBIAN_FRONTEND=noninteractive
ENV PYTHON_VERSION=python3
SHELL ["/bin/bash", "-c", "-l"]
# Install required tools and dependencies
RUN apt update && apt install -y \
ros-$ROS_DISTRO-example-interfaces \
ros-$ROS_DISTRO-ros-testing \
${PYTHON_VERSION}-colcon-common-extensions \
git \
&& rm -rf /var/lib/apt/lists/*
#docker-compose.yaml
networks:
ros2-net:
services:
talker:
build:
context: .
dockerfile: Dockerfile
networks:
- ros2-net
command: ["ros2", "run", "demo_nodes_cpp", "talker"]
listener:
build:
context: .
dockerfile: Dockerfile
networks:
- ros2-net
command: ["ros2", "run", "demo_nodes_cpp", "listener"]
foxglove-bridge:
image: mjpc13/foxglove-bridge
networks:
- ros2-net
ports:
- "9092:8765"#Dockerfile
###########################################
# BASE Image for TurtleBot3 Tutorial #
###########################################
FROM osrf/ros:jazzy-desktop-full AS base
ENV DEBIAN_FRONTEND=noninteractive
ENV PYTHON_VERSION=python3
### INSTALL DEPENDENCIES ###
RUN apt-get update \
&& apt-get install -y curl git wget \
ros-$ROS_DISTRO-ros-gz \
ros-$ROS_DISTRO-cartographer ros-$ROS_DISTRO-cartographer-ros \
ros-$ROS_DISTRO-navigation2 \
ros-$ROS_DISTRO-nav2-bringup \
${PYTHON_VERSION}-vcstool
RUN echo '#!/bin/bash\n\
set -e\n\
# Source ROS2\n\
source /opt/ros/$ROS_DISTRO/setup.bash\n\
# Build TurtleBot3 workspace if it exists\n\
if [ -d /turtlebot3_ws/src ]; then\n\
cd /turtlebot3_ws\n\
colcon build --symlink-install\n\
source install/setup.bash\n\
fi\n\
# Build your development workspace if it exists\n\
if [ -d /devel_ws/src ]; then\n\
cd /devel_ws\n\
colcon build --symlink-install\n\
source install/setup.bash\n\
fi\n\
exec "$@"' > /entrypoint.sh && chmod +x /entrypoint.sh
ENTRYPOINT [ "/entrypoint.sh" ]
###########################################
# OVERLAY IMAGE for TurtleBot3 Simulation #
###########################################
FROM base AS overlay
WORKDIR /turtlebot3_ws
RUN /bin/bash -c " \
mkdir src/ && \
wget https://raw.githubusercontent.com/ROBOTIS-GIT/turtlebot3/refs/heads/main/turtlebot3.repos && \
vcs import src < turtlebot3.repos && \
source /opt/ros/$ROS_DISTRO/setup.bash && \
colcon build --symlink-install"
ENTRYPOINT [ "/entrypoint.sh" ]
CMD [ "ros2", "launch", "turtlebot3_gazebo", "turtlebot3_house.launch.py" ]
###############################################
# Development Image for TurtleBot3 Simulation #
###############################################
FROM base AS devel
WORKDIR /devel_ws
# Dev container arguments
ARG USERNAME=devuser
ARG UID=1000
ARG GID=${UID}
# Install extra tools for development
RUN apt-get update && apt-get install -y --no-install-recommends \
gdb gdbserver nano vim
# Create new user and home directory
RUN groupadd --gid $GID $USERNAME \
&& useradd --uid ${GID} --gid ${UID} --create-home ${USERNAME} \
&& echo ${USERNAME} ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/${USERNAME} \
&& chmod 0440 /etc/sudoers.d/${USERNAME} \
&& mkdir -p /home/${USERNAME} \
&& chown -R ${UID}:${GID} /home/${USERNAME}
# Set the ownership of the overlay workspace to the new user
RUN chown -R ${UID}:${GID} /turtlebot3_ws/
# Set the user and source entrypoint in the user's .bashrc file
USER ${USERNAME}
RUN echo "source /entrypoint.sh" >> /home/${USERNAME}/.bashrc
docker-compose.yaml:
#docker-compose.yaml
networks:
ros2-net:
services:
#ROS2
turtlebot3:
image: turtlebot3_test:overlay
build:
context: .
dockerfile: Dockerfile
target: overlay
environment:
- GAZEBO_MODEL_PATH=$GAZEBO_MODEL_PATH:/turtlebot3_ws/src/turtlebot3/turtlebot3_simulations/turtlebot3_gazebo/models
- TURTLEBOT3_MODEL=waffle_pi
- DISPLAY=novnc:0.0
depends_on:
- novnc
networks:
- ros2-net
command: [ "ros2", "launch", "turtlebot3_gazebo", "turtlebot3_world.launch.py" ]
#Creates and runs the VNC server
novnc:
image: theasp/novnc:latest
container_name: novnc
ports:
- "9093:8080"
environment:
DISPLAY_WIDTH: 3000
DISPLAY_HEIGHT: 1800
RUN_XTERM: "no"
networks:
- ros2-net
#Foxglove Bridge to vizualize topics from ROS2
foxglove-bridge:
image: mjpc13/foxglove-bridge
networks:
- ros2-net
ports:
- "9092:8765"
turtlebot3-slam:
extends: turtlebot3
networks:
- ros2-net
command: [ "ros2", "launch", "turtlebot3_cartographer", "cartographer.launch.py", "use_sim_time:=True"]
turtlebot3-devel:
image: turtlebot3_test:devel
build:
context: .
dockerfile: Dockerfile
target: devel
volumes:
- ./devel_ws:/devel_ws
networks:
- ros2-net
command: ["ros2", "launch", "turtlebot_explorer", "explorer_launch.py"]networks:
ros1-net:
ros2-net:
services:
#ROS1 SERVICES
ros1-master:
image: osrf/ros:noetic-desktop-full-focal
networks:
- ros1-net
command: "roscore"
ros1-talker:
build:
context: ../01_ros1/
dockerfile: Dockerfile
environment:
- ROS_MASTER_URI=http://ros1-master:11311
depends_on:
- ros1-master
networks:
- ros1-net
command: ["rosrun", "roscpp_tutorials", "talker"]
#ROS1 to ROS2 Bridge
ros1-bridge:
image: andrejorsula/ros1_bridge:foxy
environment:
- ROS_MASTER_URI=http://ros1-master:11311
depends_on:
- ros1-master
networks:
- ros1-net
- ros2-net
#ROS2
listener:
build:
context: ../02_ros2/
dockerfile: Dockerfile
networks:
- ros2-net
command: [ "ros2", "run", "demo_nodes_cpp", "listener", "__log_disable_rosout:=true"]
#Foxglove Bridge to vizualize topics from ROS2
foxglove-bridge:
image: mjpc13/foxglove-bridge
networks:
- ros2-net
ports:
- "8765:8765"You can delete every image and container created in this tutorial:
Delete images pulled in this tutorial:
docker rmi -f ubuntu:latest ubuntu:20.04 jupyter/scipy-notebook theasp/novnc:latest alpine nginxdemos/hello ollama/ollama:0.11.5-rc5 ghcr.io/open-webui/open-webui:main osrf/ros:noetic-desktop-full-focal osrf/ros:jazzy-desktop-full mjpc13/foxglove-bridge andrejorsula/ros1_bridge:foxy docker-tutorial vis-tutorial Remove all unused images (be carefull):
docker image prune -a