Experiments with Docker and ROS2 Implementation

Was getting curious about docker containers and running Ros2 in a container. @KurtE showed me this where someone already did it: miguelgonrod/ros_arduino_uno_Q: ROS 2 Jazzy running in a container on the Arduino UNO Q to control the onboard LED via an RPC bridge between the Qualcomm MPU (Linux) and the STM32 microcontroller. A ROS 2 node subscribes to a boolean topic and triggers an RPC call to the MCU, enabling end-to-end ROS-native LED control on the UNO Q.

So to try to understand it better, started doing some digging:
The Complete Beginner’s Guide to Docker for ROS 2 Deployment (2025) | Robotair

How to Exit a Docker Container

ROS Development Inside Docker β€’ Didi Ruhyadi

So for the hell of it I log in via ./adb shell and ran the hello world test:

arduino@arduinoQ:/home$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
198f93fd5094: Pull complete
Digest: sha256:d4aaab6242e0cace87e2ec17a2ed3d779d18fbfd03042ea58f2995626396a274
Status: Downloaded newer image for hello-world:latest

Hel message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:

The Docker client contacted the Docker daemon.

The Docker daemon pulled the "hello-world" image from the Docker Hub.
(arm64v8)

The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.

The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:

For more examples and ideas, visit:
Get started | Docker Docs lo from Docker!

and of course since I am a glutton for punishment I ran the $ docker run -it ubuntu bash command just to see what happens. And to my surprise I was able to run Ubuntu in a container:
:/home$ docker run -it ubuntu bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
97dd3f0ce510: Pull complete
Digest: sha256:c35e29c9450151419d9448b0fd75374fec4fff364a27f176fb458d472dfc9e54
Status: Downloaded newer image for ubuntu:latest
root@2d53e949be0c:/# ls
bin boot dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
root@2d53e949be0c:/# cd usr
root@2d53e949be0c:/usr# ls
bin games include lib libexec local sbin share src
root@2d53e949be0c:/usr# cd lib
root@2d53e949be0c:/usr/lib# ls
aarch64-linux-gnu dpkg ld-linux-aarch64.so.1 lsb os-release systemd udev
apt init locale mime sysctl.d tmpfiles.d
root@2d53e949be0c:/usr/lib# exit
exit

An interesting command is to see what docker containers are running or stopped
docker ps -a -q
2d53e949be0c
d4bacefb5f58
e3d5c942c092

Guess more to follow. Interesting stuff. Opens up several possibilities.

Now wondering if doing this

Step 4: X11 forwarding for GUI applications

The devcontainer.json file already includes the necessary configuration for X11 forwarding. The DISPLAY environment variable is set to ${env:DISPLAY}, which forwards the X11 display from the host to the container. This allows you to run GUI applications such as RViz and Gazebo inside the container and display them on your host machine.

Any one have any thoughts about this?

Found this about linking docker containers:
Docker - Container Linking - GeeksforGeeks

1 Like

Been playing some more with docker after looking at these videos: Docker 101

After running some apps I did a docker ps -a which shows all containers running or not and saw:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
071cbd49b0a7 Package app-bricks/python-apps-base Β· GitHub "/run.sh" 33 minutes ago Exited (137) 22 minutes ago wirescanner-main-1
4b09e6b845b1 Package app-bricks/python-apps-base Β· GitHub "/run.sh" About an hour ago Exited (0) About an hour ago streamlit-test-graph-main-1
fb4a1f3bfff0 Package app-bricks/python-apps-base Β· GitHub "/run.sh" 2 hours ago Exited (0) About an hour ago streamlit_matplot_slider-main-1
d4bacefb5f58 hello-world "/hello" 26 hours ago Exited (0) 26 hours ago elegant_jackson
e3d5c942c092 Package app-bricks/python-apps-base Β· GitHub "/run.sh" 2 days ago Exited (137) 2 days ago blank-main-1

This was after turning power off and back on. So question for ArduinoTeam. Do the apps get pruned after a while or do I have to clean them myself?

Another command is docker image ls which lists the images on the Q
REPOSITORY TAG IMAGE ID CREATED SIZE
ghcr.io/arduino/app-bricks/python-apps-base`` 0.6.2 34ba2f14f237 2 weeks ago 770MB
ghcr.io/arduino/app-bricks/ei-models-runner`` 0.6.1 6fcb2945445b 2 weeks ago 1.3GB
influxdb 2.7 7efe22d086df 6 weeks ago 393MB
ubuntu latest 9a84ec2d5dd7 2 months ago 101MB
hello-world latest ca9905c726f0 4 months ago 5.2kB

ubuntu and hello-world is what I created the other day as test cases. To remove the image you can use docker rmi <image_id_or_tag>. So to remove the ubuntu image:

arduino@arduinoQ:/$ docker ps -a 
CONTAINER ID   IMAGE                                               COMMAND     CREATED             STATUS                        PORTS     NAMES
071cbd49b0a7   ghcr.io/arduino/app-bricks/python-apps-base:0.6.2   "/run.sh"   About an hour ago   Exited (137) 57 minutes ago             wirescanner-main-1
4b09e6b845b1   ghcr.io/arduino/app-bricks/python-apps-base:0.6.2   "/run.sh"   2 hours ago         Exited (0) 2 hours ago                  streamlit-test-graph-main-1
fb4a1f3bfff0   ghcr.io/arduino/app-bricks/python-apps-base:0.6.2   "/run.sh"   2 hours ago         Exited (0) 2 hours ago                  streamlit_matplot_slider-main-1
d4bacefb5f58   hello-world                                         "/hello"    26 hours ago        Exited (0) 26 hours ago                 elegant_jackson
e3d5c942c092   ghcr.io/arduino/app-bricks/python-apps-base:0.6.2   "/run.sh"   2 days ago          Exited (137) 2 days ago                 blank-main-1
arduino@arduinoQ:/$ docker rm d4bacefb5f58
d4bacefb5f58
arduino@arduinoQ:/$ docker images -a
REPOSITORY                                    TAG       IMAGE ID       CREATED        SIZE
ghcr.io/arduino/app-bricks/python-apps-base   0.6.2     34ba2f14f237   2 weeks ago    770MB
ghcr.io/arduino/app-bricks/ei-models-runner   0.6.1     6fcb2945445b   2 weeks ago    1.3GB
influxdb                                      2.7       7efe22d086df   6 weeks ago    393MB
hello-world                                   latest    ca9905c726f0   4 months ago   5.2kB
arduino@arduinoQ:/$ docker rmi ca9905c726f0
Untagged: hello-world:latest
Untagged: hello-world@sha256:d4aaab6242e0cace87e2ec17a2ed3d779d18fbfd03042ea58f2995626396a274
Deleted: sha256:ca9905c726f06de3cb54aaa54d4d1eade5403594e3fbfb050ccc970fd0212983
Deleted: sha256:50163a6b11927e67829dd6ba5d5ba2b52fae0a17adb18c1967e24c13a62bfffa
arduino@arduinoQ:/$
1 Like

Great stuff, started watching video and found I need to watch his first one in that series, to remind me what is docker :face_with_spiral_eyes::laughing:

1 Like

Slowly going through the second video and trying things out as I go just to get familiar with the commands. At least the Ros2 link that you provided is making a little more sense, and I mean a little. :grinning_face: :shaking_face:

1 Like

How that I got a rudimentary understanding of docker decided to try the ros_arduino_uno_Q Following the instructions in the github readme I was finally able to get it to blink.

Here are some hints and caveats to get it working.

  1. The run.sh command actually loads the sketch the to the Q as well as running the associated dockerfile and opens up in Ros2
    root@arduinoQ:/ros2_ws#
  2. Doing a docker ps -ain another window
    b2b0e314b9dc   ros_jazzy_ws                                        "/ros_entrypoint.sh …"   About a minute ago   Up About a minute                         ros_jazzy_container
    afa69fc28001   ghcr.io/arduino/app-bricks/python-apps-base:0.6.2   "/run.sh"                About a minute ago   Exited (0) About a minute ago             ros_arduino_uno_q-main-1
You wind up with 2 containers.  The first is ros2 and second is the app

3. Once in the container you have to type tmux and run ros2 run ros_led led

4. Then you have to a cltrl-b then type cto get a second tmux window. Then you can start turning the led on and off.

5. In the second tmux window that you just opened you type ros2 topic pub /led_status std_msgs/Bool "data: false" --once to turn off and you see the message:

root@arduinoQ:/ros2_ws# ros2 topic pub /led_status std_msgs/Bool "data: false" --once
publisher: beginning loop
publishing #1: std_msgs.msg.Bool(data=False)

6. To switch back to the first window you can type ctrl-b and then type a b (note you use this to switch back a forth between windows. Then you see

root@arduinoQ:/ros2_ws# ros2 run ros_led led
[INFO] [1767877139.102122708] [ros_led_node]: Node ros_led_node initialized
[INFO] [1767877293.347727492] [ros_led_node]: Message Received: False

7. To exit the first tmuxwindow type a ctrl-c. then you can do a ctrl-d to exit the containers.

To rerun the app you to do a . run.sh again but you get the error message from docker
docker: Error response from daemon: Conflict. The container name "/ros_jazzy_container" is already in use by container "b2b0e314b9dce207b588c5a8b6fe18d7c9a530685f204c07e93ff190b33c8207". You have to remove (or rename) that container to be able to reuse that name.
See 'docker run --help'.

The only way to get rid of this error and rerun the app you have to remove the app container. If you do a docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b2b0e314b9dc ros_jazzy_ws "/ros_entrypoint.sh …" 17 minutes ago Exited (0) 2 minutes ago ros_jazzy_container
afa69fc28001 Package app-bricks/python-apps-base Β· GitHub "/run.sh" 17 minutes ago Exited (0) About a minute ago ros_arduino_uno_q-main-1

to remain the app container, do a docker rm ros_arduino_uno_q-main-1 then removerthe docker rm ros_jazzy_container as well. Then do the `run command again and repeat the steps above. There is probably a way to fix this in the dockerfile but I am not there yet.

2 Likes

Thanks @Merlin513

I tried it again and was able to change the state of the LEDs.

As I mentioned I ran into issues with the first time I was trying it today with it
outputting:

docker: Error response from daemon: Conflict. The container name "/ros_jazzy_container" is already in use by container "dca503f759ebf53420e74a22717540c4bb4b89fbe48e2e97097caf6fe631b5ff". You have to remove (or rename) that container to be able to reuse that name.
See 'docker run --help'.

Your suggestion to do the command:
docker container prune

Did the trick.

In your step 6. I did not notice at first that the b was highlighted and not the a...
Took me a bit, but found: ctrl+b 0
got me back to the first window
and ctrl+b 1
got me to the second.

Thanks for the writeup! Still need to play a lot more as I am completely rusty with
ROS 2 stuff and docker and ...

1 Like

For me rusty isn’t close - just started learning. :slight_smile:

Now for the fun stuff. I found another way to make ros2 serial work with out getting the error message. If you add --rm to the docker run command in run.sh it will remover the container once you exit the ros2 workspace (I am using ctrl-d). In other words
docker run -it --net=host --name ros_jazzy_container --rm -v $(pwd)/src:/workspace/src -v /var/run/arduino-router.sock:/var/run/arduino-router.sock ros_jazzy_ws

Also if you want to be even more daring on first use, you dont have a ros_jazzy_ws image already setup.

  1. Load the sketch via ide 2.3.x
  2. Ignore the run.sh file and do the following commands
docker build -t ros_jazzy_ws python/.

docker run -it --net=host --name ros_jazzy_container --rm -v $(pwd)/src:/workspace/src -v /var/run/arduino-router.sock:/var/run/arduino-router.sock ros_jazzy_ws

Now on subsequent runs or if you already have the ros2 image set up just issue that last command.

docker run -it --net=host --name ros_jazzy_container --rm -v $(pwd)/src:/workspace/src -v /var/run/arduino-router.sock:/var/run/arduino-router.sock ros_jazzy_ws

of course you can make it easier to just put that command in a .sh file and run it after that.

Cheeers all

1 Like

:thinking:
I keep wondering if there are ways to turn it on it's head?

is there some way that the the Arduino sketch can be in control?

That is, some way for the Arduino sketch to launch python or c++ app?
:thought_balloon:

1 Like

Actually Ditto, everything has changed since the last time we played with ROS2 on the hexpods!

And I tried out your newer ways to launch and it works!

1 Like

You got me on that one. I know in the run.sh file it looks like the app is launch via:
arduino-app-cli app start user:ros_arduino_uno_Q

1 Like

I did notice that it used it in the .sh file to launch

I have not played directly very much with the arduino-app-cli, although should get more comfortable with it. That way I might remember things like, when things are not working right, to use it to clear the apps cache and the like.

Have no idea yet what the completion does, looks like it can create scripts for
different things like bash, but not sure what it actually does in those scripts.

Again lots of details I don't understand on how all of this works. For example if you do the arduino-app-cli app start ...

Does it rebuild the sketch every time? I feels like it does as slow as it is to start it up.
Does restart work differently?

But I still wonder if there are ways to be able to maybe run Linux commands from
the Arduino code and optionally return data... Maybe this would have to be built in
using the python code? Could be simple things like mount an SD Card device or
list files, or could be start up ROS2...

1 Like

I managed to run host Linux code and return data to MCU, via python bridge first and call host web service second (ran separately from ssh).

Inside docker container you can perfectly call url = f"http://172.17.0.1:5000/play?{query}"

Full example here agi-robot/python/main.py at main Β· msveshnikov/agi-robot Β· GitHub

1 Like

Alcon

Decided to update the Title of this thread to include ROS2 since that is where I was going with docker experiments.

Did get ROS2 installed in a Ubuntu container with some trial and errors. I did create a Github repo Arduino-Uno-Q-and-ROS2-Implementation. The repo gives instructions on building and running the docker container: mjs513/Arduino-Uno-Q-and-ROS2-Implementation and a bunch of notes on using the container: Arduino-Uno-Q-and-ROS2-Implementation/Docker Ros2 Notes at main Β· mjs513/Arduino-Uno-Q-and-ROS2-Implementation.

Just a quick summary. On building the Dockerfile you will see:
docker build --no-cache -t ros2-jazzy .
[+] Building 546.2s (16/16) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 2.59kB 0.0s
=> [internal] load metadata for ``docker.io/library/ubuntu:24.04`` 0.4s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> CACHED [ 1/12] FROM ``docker.io/library/ubuntu:24.04@sha256:c35e29c9450151419d9448b0fd75374fec4fff364a27f176fb4`` 0.0s
=> [ 2/12] RUN apt update && apt install -y --no-install-recommends locales curl gnupg2 lsb-release git ca- 93.4s
=> [ 3/12] RUN curl -sSL ``https://raw.githubusercontent.com/ros/rosdistro/master/ros.key`` | gpg --dearmor -o / 3.9s
=> [ 4/12] RUN echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gp 0.6s
=> [ 5/12] RUN apt update && apt install -y --no-install-recommends ros-jazzy-ros-base python3-colcon- 336.1s
=> [ 6/12] RUN rosdep init || true && rosdep update 35.5s
=> [ 7/12] RUN apt update && apt install -y --no-install-recommends tmux && rm -rf /var/lib/apt/lists/* 27.4s
=> [ 8/12] RUN mkdir -p /etc/tmux && printf "%s\n" "unbind C-b" "set -g prefix C-a" "bind C-a send-prefix" " 0.6s
=> [ 9/12] RUN echo "alias tmux='tmux -f /etc/tmux/tmux.conf'" >> /etc/bash.bashrc 2.1s
=> [10/12] RUN mkdir -p /opt/ros_ws/src 0.6s
=> [11/12] WORKDIR /opt/ros_ws 0.1s
=> [12/12] RUN printf "%s\n" "if [ -f /opt/ros/jazzy/setup.bash ]; then source /opt/ros/jazzy/setup.bash; fi" "i 2.0s
=> exporting to image 43.1s
=> => exporting layers 43.0s
=> => writing image sha256:38492779e458a65045954fc75e0c9aedff580f58cc7ca3268bdcf04f76fcf44f 0.0s
=> => naming to ``docker.io/library/ros2-jazzy

Now if you do a docker image ls

docker# docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ros2-jazzy latest 38492779e458 51 seconds ago 919MB
ghcr.io/arduino/app-bricks/python-apps-base`` 0.6.2 34ba2f14f237 4 weeks ago 770MB

and after setting up the demo_nodes per the notes :

So a good starting point. Now to create a launch file and get the MCU talking to the MPU with ROS2 bridge. Which may be problematic since there are latency issues as discussed else where.

1 Like

Great stuff!
Will try this soon! Although might wait until my 4 arrives :wink:
I did receive a shipment notification this morning :smiley:

2 Likes

Just checked and got my notification as well :slight_smile: Yes I broke down and ordered one.

2 Likes

@KurtE and all that are interested

Next step in the process was to see if I could take the ros_arduino_uno_Q setup and break it out so you could have a base install of ros2_jazzy and separate install for the ros_led package as configured in the repo. And after some discussion with copilot was able to get it working.

Basically you create a dockerfile that configures a a shared base image of Ros2_jazzy and then in a separate a merged build reference the base image and add in the ros_led package.

To start basically all you need is the python portion of the library and create a directory structure that looks like:
python/
└── src/
β”œβ”€β”€ ros_led/
β”œβ”€β”€
β”œβ”€β”€ resource/
β”œβ”€β”€ test/
β”œβ”€β”€ package.xml
β”œβ”€β”€ setup.cfg
└── setup.py
python/requirements.txt
docker/
β”œβ”€β”€ Dockerfile.base
β”œβ”€β”€ Dockerfile.led_ws

then build that docker image that you can run the ros_led package. Yes it works. To me this is easier since now I can configure other images for my needs based off of one shared image instead of rebuilding the whole container everytime.

More info and a detailed readme can be found here:
mjs513/Arduino-Uno-Q-and-ROS2-Implementation

1 Like

Quick update,

I have forked your Ros2_jazzy stuff on the Q and created the
shell files: in my case they are at:

arduino@bambie:~/Arduino-Uno-Q-and-ROS2-Implementation/Ros2_LED_node/ros2_led_no                 de$ ls
build.sh  docker  python  run.sh  src

And the build.sh succeeded :smiley:
I then loaded the Arduino sketch from the other project:
miguelgonrod/ros_arduino_uno_Q: ROS 2 Jazzy running in a container on the Arduino UNO Q to control the onboard LED via an RPC bridge between the Qualcomm MPU (Linux) and the STM32 microcontroller. A ROS 2 node subscribes to a boolean topic and triggers an RPC call to the MCU, enabling end-to-end ROS-native LED control on the UNO Q.

And many of the instructions from there:
Start.sh
tmux - Note: ctrl b is now ctrl a...
And it works:

root@bambie:/opt/ros_ws# run ros_led led
-bash: run: command not found
root@bambie:/opt/ros_ws# ros2 run ros_led led
[INFO] [1768683268.792886653] [ros_led_node]: Node ros_led_node initialized
[INFO] [1768683327.117769019] [ros_led_node]: Message Received: False
[INFO] [1768683360.535365719] [ros_led_node]: Message Received: True
[INFO] [1768683371.075964358] [ros_led_node]: Message Received: False

and

root@bambie:/opt/ros_ws# ros2 topic pub /led_status std_msgs/Bool "data: false" --once
publisher: beginning loop
publishing #1: std_msgs.msg.Bool(data=False)

root@bambie:/opt/ros_ws# ros2 topic pub /led_status std_msgs/Bool "data: true" --once
publisher: beginning loop
publishing #1: std_msgs.msg.Bool(data=True)

root@bambie:/opt/ros_ws# ros2 topic pub /led_status std_msgs/Bool "data: false" --once
publisher: beginning loop
publishing #1: std_msgs.msg.Bool(data=False)

root@bambie:/opt/ros_ws#

Other side note: looks like tmux mouse operations changed as well.
Right click no longer shows paste as an option.
However crl+rightclick worked.

Now for more playing

1 Like

Yep they changed a bit simpler now as well.

Actually shows 2 ways to do it either with tmux or using 2 ros windows but I forgot a summary of the new commands

For tmux - arrow keys can now be used as well

----------------------------------------------------------------------
                         TMUX CHEAT SHEET  
-----------------------------------------------------------------------


PREFIX KEY
  Ctrl+a                β†’ Main tmux prefix
  Ctrl+a then a         β†’ Send literal Ctrl+a to program

PANE MANAGEMENT
  Ctrl+a |              β†’ Split pane vertically (left/right)
  Ctrl+a -              β†’ Split pane horizontally (top/bottom)

PANE NAVIGATION (NO PREFIX)
  Alt + Left            β†’ Move to left pane
  Alt + Right           β†’ Move to right pane
  Alt + Up              β†’ Move to upper pane
  Alt + Down            β†’ Move to lower pane

PANE RESIZING (MOUSE)
  Drag pane borders     β†’ Resize panes
  Scroll wheel          β†’ Scroll through history

WINDOWS
  Ctrl+a c              β†’ Create new window
  Ctrl+a n              β†’ Next window
  Ctrl+a p              β†’ Previous window
  Ctrl+a w              β†’ List windows

COPY MODE (VI STYLE)
  Ctrl+a [              β†’ Enter copy mode
  k / j                 β†’ Move up / down
  /                     β†’ Search forward
  ?                     β†’ Search backward
  v                     β†’ Begin selection
  y                     β†’ Yank (copy)
  q                     β†’ Quit copy mode

STATUS BAR
  Left:  "tmux"
  Right: Current time (HH:MM)

MISC
  Ctrl+a d              β†’ Detach session
  tmux attach           β†’ Reattach to last session
  tmux ls               β†’ List sessions
  tmux kill-session -t X→ Kill session X

1 Like

@KurtE and all interested

Next step in the ROS2 journey was getting the MPU running Ros2 image communicating with the PC running ROS2 in WSL2. This of course complicates the issue but I did manage to get it working using the talker node on the UnoQ and listener on the PC side

I provide some detailed instructions on how I managed to this along with the docker files in the repo: mjs513/Arduino-Uno-Q-and-ROS2-Implementation

PS: Also added the TMUX cheat sheet to: Arduino-Uno-Q-and-ROS2-Implementation/Docker Ros2 Notes/README.md at main Β· mjs513/Arduino-Uno-Q-and-ROS2-Implementation

Next up is to get Rviz working in WSL2 and then for the fun stuff to actually get something real working.

1 Like

Getting Rviz2 working in WSL2 was easier than I thought:

Arduino-Uno-Q-and-ROS2-Implementation/Installing and Running Rviz in WSL2/Install RViz2.md at main Β· mjs513/Arduino-Uno-Q-and-ROS2-Implementation

1 Like