Docker Guide: How to create and run a Docker Image

The following guide will discuss all the necessary steps in order to create a Docker image and also how to run a container from an image.

Installing Docker

The first step is to install Docker on our machine of interest. For Windows/MacOS we require Docker Desktop. This can be downloaded from here. An important note is that we prefer the Edge channel rather than the Stable channel, since the Edge channel enables us to build on x86 machines images for other architectures such as ARM (see Building Multi-Arch Images chapter).

For Linux Systems such as the Raspberry Pi:

$ sudo apt-get update && sudo apt-get upgrade
$ curl -fsSL https://get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh

Add a Non-Root User to the Docker Group:

$ sudo usermod -aG docker [user_name]

Check Docker version:

$ docker version

Docker Workflow

We begin by describing the general Docker workflow. Usually, we begin by creating a Dockerfile. In a nutshell, a Dockerfile is a script which describes the commands we would like to execute within a container. This container is then saved as a new Docker image. Let’s provide an example for the sake of understanding:

FROM alpine:latest

RUN apk update && apk add git && apk add bash

WORKDIR /src/

RUN git clone https://github.com/eclipse-cyclonedds/cyclonedds.git

This example clones the latest Alpine Linux image using the FROM command, updates and installs git and bash from the apk repo using the RUN command. Then creates a folder src moves into it and clones the CycloneDDS repo. The result is an image that runs Alpine Linux, has git and bash installed and the repo CycloneDDS in a src directory. The above described workflow can be seen in the following diagram.

title “Docker Workflow”
left to right direction

(Dockerfile)  --> (Docker\nImage)
(Docker\nImage) --> (Docker\nContainer)

The following link presents some of the Best practices for writing Dockerfiles. In addition, the following link is the Dockerfile reference guide.

Similarly to any other code hosting service (e.g. github, bitbucket etc.) there is a Docker Hub that hosts Docker images. As a result, in some cases the workflow from above can be changed to skip building the image from a Dockerfile to simply cloning a pre-existing image that satisfies that particular need.

Multi-stage Builds

Multi-stage builds refer to Dockerfiles that use various images throughout the building process. It is easier to explain this with an example, consider the following:

FROM alpine:latest AS build

RUN apk update && apk add git && apk add cmake && apk add --no-cache build-base && \
    apk add maven && apk fetch openjdk8 && apk add openjdk8 && apk add linux-headers && \
    apk add bash

WORKDIR /src/

RUN git clone https://github.com/eclipse-cyclonedds/cyclonedds.git

WORKDIR /dds/

WORKDIR /src/cyclonedds/build

RUN cmake -DCMAKE_INSTALL_PREFIX=/dds/ .. && \
    cmake --build . && cmake --build . --target install

FROM alpine:latest

RUN apk update && apk add bash

WORKDIR /dds/

COPY --from=build /dds/ ./

The first stage named BUILD will start from the latest Alpine Linux image and will install all the dependencies required by CycloneDDS (more on these dependencies can be found Setup Guide for CycloneDDS). Subsequently, it clones the CycloneDDS repo and builds it. Then we move to the second and final stage, to which we will refer to as the DEPLOY stage (separation of the two stages can be seen from the different FROM commands). In the DEPLOY stage, we start from the clean latest Alpine Linux image and we copy only the executables we compiled in the previous stage. This separation enables us to provide to the end user only the executables required for our application in a minimal Linux environment. Also, the resulting image is drastically smaller in size.

Building Docker Images

After creating our Dockerfile, it is time to build our image which can be done by navigating to the folder of our Dockerfile and executing:

$ docker build -t <image name> .

Similarly, we can also build images for each of the different stages we created, using:

$ docker build --target <stage name> -t <image name> .

Finally, using $ docker images we can see all the images in our system.

The build process can be seen in the following video.

Running Docker Containers

Containers are running instances of images. In order to create a new container we run the following command:

$ docker run --network="host" -ti --name <container name> <image name> bash

We provide a meaningful name for our new container and the name of an already existing image. Also, note that there are many different options, but in the case of DDS we want our container to have the IP address of the host, so that it can communicate over the host’s network.

Warning

Host networking does not work on Docker Desktop for Windows/MacOS. Basically, the command can be used as is but the IP address will not be the same as the one the host receives from its network. Nevertheless, containers can still communicate between each other and within the same container, just not with the outside network.

Furthermore, we can open another terminal on an already running container by using:

$ docker exec -it <existing container name> bash

Note

In all of the above mentioned commands we execute bash since it is installed on our images. If you do not have bash installed use /bin/sh instead.

Building Multi-Arch Images

Finally we describe how we can actually build images for different architectures than the one on the machine we are currently building on. This is useful in the case we want to build an image for the Raspebrry Pi on a x86 Windows Machine which has more processing power. A pre-requisite for this is to have the Edge channel of Docker Desktop installed. First we check what buildx builders are available and create a new builder using:

$ docker buildx ls
$ docker buildx create --name <builder name>

We select that specific builder using:

$ docker buildx use <mybuilder>
$ docker buildx inspect --bootstrap

Finally, we build and push our new image for the selected architecture to a new Docker Hub repo using:

$ docker buildx build --platform linux/arm/v7 -t <username/repo name>:<image name> --push .

Building Demo Image

The Dockerfile created for the C implementations of the Round trip as well as the different versions of the Matrix Board Communication can be found in the ~/src/docker/Build_C_MBC_Roundtrip/ directory of this repository. First, we need to build this image by navigating to the above mentioned directory and using:

$ docker build -t <image name> .

Then we can create a container of this image using:

$ docker run -it --network="host" --name <container name> <image name>

For more information on how to execute the experiments please refer to the docker sections of each respective Round trip and Flood in C and Matrix Board Communication in C demonstrator.