Docker

Installation

For more details follow the official installation guide to install in Ubuntu. In essence

# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Add the repository to Apt sources:
echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

# Install docker packages:
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Note

If you plan to use docker as github runner for CI/CD, provide privileges to the docker.sock files:

sudo chmod 666 /var/run/docker.sock

More details here.

Arch install

Link to the Arch Wiki. To install the docker engine:

sudo pacman -Sy docker

Then either the docker.service or the docker.socket system service must be enabled. The former loads docker at boot up (thus more load time could be required), while the latter loads on first usage:

sudo systemctl enable docker.socket
# Or alternatively
# sudo systemctl enable docker.socket

As discussed in the Arch wiki page and here, to run docker with sudo privileges it is recommended to add the own user to the docker group. You can check the existance of the group by calling

grep docker /etc/group

If the group exists, then add the user simply with the command

sudo usermod -aG docker $USER  # or set the <user_name>

or alternatively create the group:

sudo groupadd docker

After this operation, remember to restart the docker service:

sudo systemctl restart docker

Useful commands

To pull a image from the DockerHub, call

docker pull <image_name>:<tag>

For instance to pull the ROS2 humble image call docker pull osrf/ros:humble-desktop-full (this image comes also with GUI applications, such RViz).

To see the already pulled images that are ready to run call

docker image ls

To create a container, call

# docker image run <image>
# or, shorter,
docker run <image>

In general, configuration arguments that must be passed to the image on boot up must be called prior to the image name, while arguments that follows are executed on the spinned contained:

docker run <docker_arguments> <image> <commands>

A useful argument is -ti that boots the image and starts an i interactive t terminal.

To see currently runnint containers, call

docker container ls
# or,
# docker ps

To see already spinned containers that have been terminated, append the -a flag to the command. Containers can be deleted with docker container rm <container_name> -f, with -f the force argument. Additionally, you can pass the --rm flag to the docker run call to automatically delete the container when it stops spinning.

You can also start an already start a closed container or close a running container with the following commands:

docker start <container_name>
docker stop <container_name>

Run arguments

The following is a list of useful launch arguments that can be paired with the run command to extend docker capabilities.

  • -it: as already mentioned, starts the docker container and enters in the interactive terminal;

  • --rm: removes the container instance after the container stops spinning;

  • --env <VAR>:<VAL> or -e <VAR>:<VAL>: sets an environment variable VAR with VALUE on the container;

  • --net=<CONFIG>: sets the networking configuration. Details can be found here, but available configurations are bridge (default), host (recommended), none, overlay, ipvlan, macvlan.

X11 windows forwarding

To enable X11 windows forwarding, you should provide the run command the following arguments:

-e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix:rw

This command forwards the DISPLAY argument to the container and binds the X11 socket of the host machine to the container one (see here for further referencing).

If still you are unable to see the display and the terminal reports an error of the type cannot open display, call the following command to enable docker to control the local X11 server:

xhost +local:docker

Dockerfiles

To create a custom Docker image, it is simply necessary to write a Dockerfile. Once the file is written, the image can be build with the command

docker build -t <image_name> <path_to_Dockerfile><:tag (optional)>

So let us now see how to write a Dockerfile. In general the first line of a Dockerfile is the FROM command, which specifies the base image. The base image can be either already built in the system or can be pulled from the Docker Hub registry. For instance, the following is the statement to pull the Ubuntu 22.04 image from the Docker Hub registry:

FROM ubuntu:22.04

After the FROM command are placed all other instructions. Keep in mind that since image building is a layer-based process, the order of the instructions is important to correctly use caching.

For instance, RUN <cmd> runs a given command in the shell and than layers the changes on the image.

To copy some files from the host machine to the image, the COPY <host_dir> <img_dir> command is used.

To set the working directory of the image, the WORKDIR <dir> command is used.

To set the user of the image, the USER <user> command is used; by default, root is selected, thus commands can be installed withouth the sudo keyword.

The last command of the Dockerfile is usually either CMD or ENTRYPOINT. The main is that CMD is used to specify the default command to run when the container is started (if it was not specified through CLI), while ENTRYPOINT is always executed and optional arguments are appended to it. For further details, this is a good article.

FROM ubuntu:22.04

# This is a good practice to install packages:
#  1. to help caching, collect all the installation procedure in a single RUN command
#  2. to avoid caching issues and save image space, base images come with available
#     package lists that are empty.t So always first update the package list, then
#     install the packages.
#  3. to avoid caching issues, remove the package list after installation
RUN apt-get update \
    && apt-get install -y <packages> \
    && rm -rf /var/lib/apt/lists/*

# This command copies the content of the current directory to the /application directory of the image
COPY . /application

WORKDIR /application
RUN touch test.txt

ENTRYPOINT ["echo", "Hello World!"]

Docker Compose

As the docker run comman starts having lots of different argument that must be provided to it, it becomes hard to remember the exact command to start a container. To overcome this limitation there are different methods, namely:

  • write the command into a shell .sh script, make it executable (chmod +x) and run it from the terminal;

  • for containers that are often used by the user, create an alias in the shell configuration file (.bashrc, .zshrc) to make it easier to run the command;

  • or, the preferrable way, is to use Docker Compose.

Docker Compose is a tool that enables to configure and run multiple containers using a single .yaml configuration file. Virtually, everything that can be done with the docker run command can be done with Docker Compose.

As an example, the file

# File: docker-compose.yml

services:
  myubuntu:
    image: ubuntu:22.04
    environment:
      - DISPLAY=${DISPLAY}
      - PRINTNAME=${USER}
    volumes:
      - /tmp/.X11-unix:/tmp/.X11-unix
    command: bash -c "echo Hello, World!"

has the final result of the following command:

docker run \
    --rm \
    -e DISPLAY=$DISPLAY \
    -e PRINTNAME=$USER \
    -v /tmp/.X11-unix:/tmp/.X11-unix \
    ubuntu:22.04 \
    bash -c "echo Hello, World!"

To properly run the container through the Docker Compose command, first go to the directory where the docker-compose.yaml file is located and run:

docker-compose up

This would yield the following output:

[+] Running 1/0
  Container test-myubuntu-1  Created                                                                                                                     0.0s
Attaching to myubuntu-1
myubuntu-1  | Hello, World!
myubuntu-1 exited with code 0

In this case the docker-compose.yaml file was placed in a folder named test, thus the docker compose runtime created a container named test-myubuntu-1. The output of the command echo Hello, World! was overlayed in the terminal and since all containers (1 in this case) finished, the docker compose command completed as well.

In general, to stop all containers created by the docker compose command, it is simply necessary to call docker-compose down.

The complete list of available commands that can be used in Docker compose .yaml files can be found here.