Home Docker for Robotics
Post
Cancel

Docker for Robotics

Docker Overview

Docker is a tool that helps you create, share, and run applications in containers. Containers are small, lightweight packages that include everything your application needs, like the code, libraries, and settings. They are faster and use fewer resources compared to virtual machines (VMs).

Difference Between Containers and Virtual Machines:

  • Virtual Machines (VMs): Full operating systems running on top of a hypervisor. They are resource-heavy and slow to start.
  • Docker Containers: Share the host OS kernel, making them faster, more lightweight, and more resource-efficient.

Key components:

  • Images: Think of an image like a recipe. It tells Docker what to include in the container (software, libraries, etc.).
  • Containers: These are the actual “live” versions of the image. It’s like cooking from the recipe — your container is the meal ready to eat.
  • Dockerfile: A script that defines how an image is built, including base images, commands, and configurations.
  • Volumes: Persistent storage that containers can use to save data.
  • Docker Hub: A cloud-based repository where pre-built Docker images are shared.

Why Use Docker in Robotics?

Working on robots means dealing with lots of software and tools, which can sometimes conflict or break things. Docker helps solve these problems:

  1. Easily Switch Between Setups:
    • Robots often use specific software versions, like different ROS distributions (Robot Operating System) or Ubuntu versions.
    • For example, if you’re using an Nvidia Jetson board with JetPack (their software toolkit), Docker lets you use the version you want, even if Nvidia hasn’t updated JetPack in a while.
  2. Consistent Building and Testing:
    • Docker ensures you build and test your software in the same environment every time, no matter where you run it.
  3. Same Environment for Everyone:
    • All developers on a team can work with the exact same setup using Docker. This avoids problems where something works on one computer but not on another.
  4. Easy to Update Robots:
    • When you need to update a robot’s software, Docker lets you make small, controlled changes without messing up the entire system.
  5. Code Defines the Environment:
    • Docker lets you describe your software environment in code, so it’s easy to share, version, and reproduce.
  6. Cloud Development:
    • Docker makes it easy to set up powerful computers in the cloud to develop and test your robotics software. You can even connect to these remote setups with tools like VS Code.

Docker Commands

Images

  • Build an image from a Dockerfile:

    1
    
      $ docker build -t <image_name>
    
  • Pull an image from a Docker Hub:

    1
    
      $ docker image pull <image_name>:<tag>
    
  • Search Hub for an image:

    1
    
      $ docker image search <image_name>
    
  • List local images:

    1
    2
    
      $ docker image ls
      $ docker images
    
  • Delete an image:

    1
    2
    
      $ docker image rm <image_name>
      $ docker rmi <image_name>
    
  • Remove all unused images:

    1
    
      $ docker image prune
    

Containers

  • Create and run a container from an image, with a custom name:

    1
    
      $ docker run --name <container_name> <image_name>
    
  • Run a container with terminal:

    1
    
      $ docker run -it <image_name>
    
  • Start or stop an existing container:

    1
    
      $ docker start|stop <container_name> (or <container-id>)
    
  • Start an existing container with terminal:

    1
    
      $ docker start -i <container_name>
    
  • List running containers:

    1
    2
    
      $ docker container ls
      $ docker ps
    
  • List all containers (even the stopped ones):

    1
    2
    
      $ docker container ls -a
      $ docker ps -a
    
  • Remove a stopped container:

    1
    
      $ docker rm <container_name>
    
  • Remove all available containers:

    1
    
      $ docker container prune
    
  • Open terminal inside a running container:

    1
    
      $ docker container exec -it <container_name> /bin/bash
    

    For any commands within a running container:

    1
    
      $ docker container exec -it <container_name> <command>
    

Working with Volumes

  • Mount Host Directory to Container: This is how we can make a directory on host available inside the container
1
$ docker run -it -v <absol_path_on_host>:<absol_path_in_container> <image_name>
1
$ docker run -it --network=host --ipc=host -v <absol_path_on_host>:<absol_path_in_container> <image_name>

Any files created in a container in a shared volume will be locked — can be accessed only by the root.

Note: docker run always creates a new container. We lose any changes we make to the environment every time we run the container.

Setting up a Dockerfile

The Dockerfile contains the steps for creating an image. It typically starts with a base image and includes commands for installing software, setting environment variables, and defining the container’s entrypoint.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# FROM <base_image_name>
FROM osrf/ros:humble-desktop-full

# Commands to perform on base image
RUN apt-get -y update \
 && apt-get -y install some_package \
 && git clone https://github.com/some_user/some_repository some_repo \
 && cd some_repo \
 && mkdir build \
 && cd build \
 && cmake .. \
 && make -j$(nproc) \
 && make install \
 && rm -rf /var/lib/apt/lists/*

# Install additional packages
RUN apt-get update && apt-get install -y \
    python3-pip \
    ros-humble-turtlebot3-simulations

# Set up workspace
ENV WS_DIR="/root/ros2_ws"
WORKDIR ${WS_DIR}
RUN /bin/bash -c "source /opt/ros/humble/setup.bash && colcon build"

# Default command
CMD ["/bin/bash"]

# COPY <configuration_file to copy> <direction in image to be copied into>
COPY config/ site_config/

# Define the script that should be launched upon start of the container
ENTRYPOINT ["/root/ros2_ws/src/my_script.sh"]
  • All commands run in the docker container will run as root.
  • The COPY command assumes paths are relative to the build context specified in the docker-compose.yml or the docker build command.
  • To build and run the Docker image, go into the directory which will be the new image
1
2
$ docker image build -t <new_image_name> <directory>
$ docker run -it <new_image_name>

Entrypoint Scripts

Entrypoint scripts automate container setup at runtime. It runs every time the container is brought up.

  • Create a new file called entrypoint.sh inside the directory.
1
2
3
#!/bin/bash
source /opt/ros/humble/setup.bash
exec "$@"
  • Add it to the Dockerfile
1
2
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

GUI in Docker

1
$ docker run -it --network=host --ipc=host -v <absol_path_on_host>:<absol_path_in_container> -v /tmp/.X11-unix:/tmp/.X11-unix:rw --env=DISPLAY <image_name>

Docker Compose

Docker Compose simplifies multi-container applications by allowing you to define and manage them through a YAML file (docker-compose.yml).

For newer versions (Docker v2.0 and later), use docker compose instead of docker-compose.

Basic Commands

  • Start and run all services defined in the docker-compose.yml file:
1
$ docker compose up
  • Start services in detached mode (background):
1
$ docker compose up -d
  • Stop all running services:
1
$ docker compose stop
  • Stop and remove all services, networks, and volumes:
1
$ docker compose down
  • Restart all services:
1
$ docker compose restart

Configuration Management

  • Validate the docker-compose.yml file:
1
$ docker compose config
  • View the service logs (real-time streaming):
1
$ docker compose logs
  • View logs of a specific service:
1
$ docker compose logs <service_name>
  • Build or rebuild services
1
$ docker compose build
  • Build a specific service:
1
$ docker compose build <service_name>
  • Pull service images defined in the docker-compose.yml file:
1
$ docker compose pull

Service Management

A service represents a single containerized application or component in a multi-container setup. Each service corresponds to a container, and the docker-compose.yml file is used to define the configuration for these services.

  • Start a specific service
1
$ docker compose up <service_name>
  • Stop a specific service:
1
$ docker compose stop <service_name>
  • Remove stopped service containers:
1
$ docker compose rm
  • Remove a specific service container:
1
$ docker compose rm <service_name>

Network and Volume Management

  • View networks created by Docker Compose:
1
$ docker network ls
  • View volumes created by Docker Compose:
1
$ docker volume ls
  • Remove unused networks:
1
$ docker network prune
  • Remove unused volumes:
1
$ docker volume prune

Writing and launching a Docker-Compose file

Example docker-compose.yml for a ROS 2 project:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
version: '3.8'
services:
  ros-master:
    image: osrf/ros:humble-ros-core
    container_name: ros-master
    networks:
      - ros-network

  turtlebot-sim:
    image: osrf/ros:humble-desktop
    container_name: turtlebot-sim
    depends_on:
      - ros-master
    networks:
      - ros-network

networks:
  ros-network:
    driver: bridge

After having created both a Dockerfile as well as a docker-compose.yml you can launch them with:

1
2
$ docker compose -f docker-compose.yml build
$ docker compose -f docker-compose.yml up

where with the option -f a Docker-Compose file with a different filename can be provided. If not given it will default to docker-compose.yml.

More general docker-compose.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
version: "3.9"
services:
  some_service: # Name of the particular service (Equivalent to the Docker --name option)
    build: # Use Dockerfile to build image
      context: . # The folder that should be used as a reference for the Dockerfile and mounting volumes
      dockerfile: Dockerfile # The name of the Dockerfile
    container_name: some_container
    stdin_open: true # Equivalent to the Docker -i option
    tty: true # Equivalent to the Docker docker run -t option
    volumes:
      - /a_folder_on_the_host:/a_folder_inside_the_container # Source folder on host : Destination folder inside the container
  another_service:
    image: ubuntu/20.04 # Use a Docker image from Dockerhub
    container_name: another_container
    volumes:
      - /another_folder_on_the_host:/another_folder_inside_the_container
volumes:
  - ../yet_another_folder_on_host:/a_folder_inside_both_containers # Another folder to be accessed by both images

If instead you wanted only to run a particular service you could do so with:

1
$ docker compose -f docker-compose.yml run my_service

Then similar to the previous section, we can connect to the container from another console with

1
$ docker compose exec <docker_name> sh

where <docker_name> is given by the name specified in the docker-compose.yml file and sh stands for the type of comand to be execute, in this case we open a shell.

Docker Registry

  • Build image locally:
1
$ docker compose build <servie_name>
  • Tag the resulting image for Docker Hub:
1
$ docker tag <service_name> <your_dockerhub_username>/<name>:<tag>
  • Push the image to Docker Hub:
1
$ docker push <your_dockerhub_username>/<name>:<tag>

In case you’re not logged in, use

1
$ docker login -u <username>

And then enter the password.

It is necessary to include your Docker Hub username in the tag.

Building Docker Images for Multiple Architectures

  • Ensure qemu emulation is enabled: You need to have qemu-user-static installed and properly configured for cross-platform builds.
1
2
$ sudo apt-get install -y qemu-user-static
$ docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

This ensures that the qemu emulator is registered for the required architectures.

When building or running Docker images for a different architecture:

  1. Build Process: QEMU emulates the target architecture (e.g., arm64) on the host (e.g., amd64), enabling you to compile binaries and packages for the target system.
  2. Run Process: QEMU interprets arm64 instructions so that the container can run on an amd64 host without errors.
  • Setup buildx
1
2
$ docker buildx create --name multiarch --use
$ docker buildx inspect --bootstrap
  • Tag the image that you want to push
1
$ docker tag <service_name> <your_dockerhub_username>/<name>:<tag>
  • Build the multi-arch image
1
2
3
4
$ docker buildx build --platform linux/amd64,linux/arm64/v8 \
  -t your-dockerhub-username/your-image-name:tag \
  --push \
  -f /path/to/Dockerfile /path/to/context
  • The image will be pushed to Docker Hub

Additional Resources

This post is licensed under CC BY 4.0 by the author.