Docker: Getting started

For a general introduction to the Docker software container technology please have a look at the official Docker documentation website.

This will guide you to your fist steps on how to get started with Docker, that is:

  • Build and run an image as a container

  • Share images using Docker Hub

  • Deploy Docker applications using multiple containers with a database

  • Run applications using Docker Compose

For the O3R system specifics please see / follow the documentation provided below. This documentation includes:

  • How to build and run Docker containers on the O3R platform

  • Configuring logging on edge devices such as the O3R VPU (additional document)

  • Deploying on the O3R VPU (additional document)

  • Autostart Docker containers (additional document)

  • Utilizing the GPU on the O3R VPU device (additional document)

Build and run a Docker container for the O3R platform

In this document we guide you through building a container from scratch. We start by building a small container. This container will increase in size and complexity the further we go. We will use a Python base image and install the ifm3d (ifm3dpy) library.

If you want to use any of our available Docker images or directly build on top of our Dockerfiles, you can jump directly to this section or check out the list of Docker images officially supported by ifm.

Note that the O3R VPU (Video Processing Unit) is based on an NVIDIA Jetson system (TX2), which is arm64/aarch64 based. Building containers without the right base image will not run on the VPU, an arm64/aarch64 base image is needed. To build the container for an architecture different than the host’s, QEMU can be used.

A basic container

Every Docker container image is built by Docker using a Dockerfile. A Dockerfile contains all the necessary information for building a container image. Most of the Dockerfiles are starting with a base image that is retrieved from the Docker Hub during the build process. Docker will automatically fetch the image for the architecture hosting the build (arm64/aarch64). When building a container for an architecture other than the hosts’, the destination architecture needs to be specified in the Dockerfile. The Dockerfile is just a text file named Dockerfile without any file extension (watch out, it is case sensitive). You can use docker build [path to Dockerfile] to start the build process.

Our first container will use arm64v8/python:3.9.6-slim-buster as the base image. Let’s build the first container with that base image.

Dockerfile:

#arm64v8 is the pre-requisite for running the container on the VPU.
FROM arm64v8/python:3.9.6-slim-buster

Build the container

Building: To build a container use docker build [path/to/Dockerfile]. If an image tag (name) is needed, you can specify it within the docker build command.

# Assuming the Dockerfile is located within the same directory:
$ docker build . -t ifm3d

Note: For further information about docker build refer to the official Docker documentation

Build process:

$ docker build . -t ifm3d
Sending build context to Docker daemon  2.048kB
Step 1/1 : FROM arm64v8/python:3.9.6-slim-buster
 ---> 4770e646d0be
Successfully built 4770e646d0be
Successfully tagged ifm3d:latest

If the build was successful, you should be able to use docker image ls to display all built images:

$ docker image ls
REPOSITORY                TAG                 IMAGE ID       CREATED         SIZE
ifm3d                     latest              4770e646d0be   5 weeks ago     108MB

Troubleshooting: proxies

Depending on the network infrastructure, Docker might need the proxy information for building the container. You can input them directly when running the command:

#$HTTP_PROXY & $HTTPS_PROXY are variables containing the proxy address. for example: HTTPS_PROXY=https//[PROXY ADDRESS]
$ docker image build --build-arg http_proxy=$HTTP_PROXY --build-arg https_proxy=$HTTPS_PROXY -t jupyter .

You can also define the proxies in the config.json file. You should find the file within the home directory of the user executing Docker, in a directory called .docker, which contains config.json. For example ~/.docker/config.json. If not available, create and save a config.json file containing the following:

{
 "proxies":
 {
   "default":
   {
     "httpProxy": "http://192.168.1.12:3128",
     "httpsProxy": "http://192.168.1.12:3128",
     "noProxy": "*.test.example.com,.example2.com,127.0.0.0/8"
   }
 }
}

Run a container

Note: To run a container built for another chip architecture than the host system, you need to use QEMU to handle the virtualization. For further information see: Docker multi-CPU architecture

To run the container, we use docker run. We can specify the run command through several arguments: we want to start the container interactively (-it) and with a bash interface (/bin/bash), so we can play around inside the container.

$ docker run -it ifm3d /bin/bash
WARNING: The requested image’s platform (linux/arm64) does not match the detected host platform (linux/amd64) and no specific platform was requested
root@ee24eff3c797:/#

Note: For further information about docker run, refer to the official documentation

Now we are within the container. The warning tells us that the base image was build for an arm64/aarch64 architecture, which is different from the architecture of the host (amd64).

We should be able to ask for the Python version and start a REPL:

root@ee24eff3c797:/$ python --version
Python 3.9.6
root@ee24eff3c797:/$ python
Python 3.9.6 (default, Jun 29 2021, 19:34:26)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print("hello")
hello
>>>

Save a container

The container is working, let’s save it so we can share it around. Docker already provides us with the right tool: docker save.

$ docker save ifm3d > ifm3d.tar

Load and start a container

To reload the content of a previously saved image, use:

$ docker load < ifm3d.tar

Start the Docker container like this on every other device:

$ docker run ifm3d

Note: The image name might be different than the saved container name. After docker load, Docker will show the name of the loaded image

Add features to the container

Until now, our container is not really useful. Let’s update the container’s kernel, install Python packages and create a user (this will improve security). To do that, we need to improve the Dockerfile:

Dockerfile:

# This Dockerfile is a documentation example and might not build after a copy/paste process

#arm64v8 is the pre-requisite for running the container on the VPU.
FROM arm64v8/python:3.9.6-slim-buster

#Security updates
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get -y update && apt-get -y upgrade

#Create and activate virtual environment. This is not needed right now, but useful for multistage builds.
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

# Your normal pip installation, within the venv. We also update pip.
RUN pip install -U pip && pip install numpy

#For security reasons, using a "user" is recommended
RUN useradd --create-home pythonuser
USER pythonuser

#Easier to debug the container if issues are happening
ENV PYTHONFAULTHANDLER=1

Build process:

$ docker build . -t ifm3d
Sending build context to Docker daemon  113.1MB
Step 1/9 : FROM arm64v8/python:3.9.6-slim-buster
 ---> 4770e646d0be
...
Step 6/9 : RUN pip install -U pip && pip install numpy
 ---> [Warning] The requested image's platform (linux/arm64) does not match the detected host platform (linux/amd64) and no specific platform was requested
 ---> Running in bb51c405bbdb
Requirement already satisfied: pip in /opt/venv/lib/python3.9/site-packages (21.1.3)
Collecting pip
  Downloading pip-21.2.2-py3-none-any.whl (1.6 MB)
Installing collected packages: pip
  Attempting uninstall: pip
    Found existing installation: pip 21.1.3
    Uninstalling pip-21.1.3:
      Successfully uninstalled pip-21.1.3
Successfully installed pip-21.2.2
...
Step 9/9 : ENV PYTHONFAULTHANDLER=1
 ---> [Warning] The requested image's platform (linux/arm64) does not match the detected host platform (linux/amd64) and no specific platform was requested
 ---> Running in 4ea430894bc7
Removing intermediate container 4ea430894bc7
 ---> 14db5d89303f
Successfully built 14db5d89303f
Successfully tagged ifm3d:latest

Note: For easier readability, the build process output was shortened.

The build process contains several layers and intermediate container builds (that we use for debugging). You can start the container with the typical commands and check if NumPy was installed:

$ docker run -it ifm3d:latest /bin/bash
WARNING: The requested image’s platform (linux/arm64) does not match the detected host platform (linux/amd64) and no specific platform was requested
pythonuser@319eb5ea67e0:/$ pip freeze
numpy==1.21.1

Install ifm3d in the container

ifm3dpy is the Python binding for the ifm3d library. You can install it from source (download it here) or use the Docker image provided by ifm which can be used on the VPU and contains the ifm3d and ifm3dpy libraries.

The Dockerfile could look similar to this:

# This Dockerfile is a documentation example and might not be build after a copy/paste process

FROM ubuntu:20.04 AS build

# if defined, we run unit tests when building ifm3d
ARG run_tests

# if you are running unit tests against a camera at
# a different IP, set it here.
ENV IFM3D_IP 192.168.0.69
ENV DEBIAN_FRONTEND noninteractive

WORKDIR /home/ifm
RUN apt-get update && apt-get -y upgrade
RUN apt-get update && \
    apt-get install -y libboost-all-dev \
                       git \
                       libcurl4-openssl-dev \
                       libgtest-dev \
                       libgoogle-glog-dev \
                       libxmlrpc-c++8-dev \
                       libopencv-dev \
                       libpcl-dev \
                       libproj-dev \
                       python3-dev \
                       python3-pip \
                       build-essential \
                       coreutils \
                       findutils \
                       cmake \
                       locales \
                       ninja-build
RUN apt-get clean

# install python
RUN apt-get -y install --no-install-recommends build-essential \
    python3-dev

#install(Update) python packages and dependencies separate - improves Docker caching etc.
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# build pybind11 with cmake - but first clone from the official github repo
RUN git clone --branch v2.3.0 https://github.com/pybind/pybind11.git && \
    cd /home/ifm/pybind11 && \
    mkdir -p build && \
    cd build && \
    cmake -DPYBIND11_TEST=OFF .. && \
    make && \
    make install

# First clone ifm3d repo via username and personal access token into the container and than build the ifm3d
# this build include ifm3d pybind for a python access via pybind11
ARG IFM3D_CLONE_REPO
RUN mkdir src && \
    cd src && \
    git clone --branch o3r/main ${IFM3D_CLONE_REPO} && \
    cd ifm3d && \
    echo "Building from current branch" && \
    mkdir build && \
    cd build && \
    cmake -GNinja -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_MODULE_OPENCV=ON -DBUILD_MODULE_PCICCLIENT=ON -DBUILD_MODULE_PYBIND11=ON -DPYTHON_EXECUTABLE=/usr/bin/python3 .. && \
    ninja && \
    ninja package && \
    ninja repackage
RUN ls -1 /home/ifm/src/ifm3d/build/*.deb | grep -iv 'unspecified' | xargs dpkg -i

# multistage to reduce image size, hide secrets and add ifm user
FROM ubuntu:20.04

COPY --from=build /usr /usr

RUN apt-get update \
    && DEBIAN_FRONTEND=noninteractive apt-get install -y && apt-get clean

Note: You should leverage the layering from Docker to improve the build speed if you need to build again. QEMU emulates a ARM64 CPU in software on a x86 System which is slow. In case you are planning to build large application from source please consider to run this on a ARM64 based host.

We provide up-to-date images containing the ifm3d library, both on the DockerHub here and on GitHub here. We recommend using the image available on GitHub, as it does not come with rate limits. You can simply pull it like so:

$ docker pull ghcr.io/ifm/ifm3d:stable
stable: Pulling from ifm/ifm3d
...
Digest: sha256:f54a5890d75618c5bd21535dfa71e1cd9b1a8515902fb8e1912e6f586e0685a3
Status: Downloaded newer image for ghcr.io/ifm/ifm3d:stable
ghcr.io/ifm/ifm3d:stable

Note: Due to easier readability, the pull process output was shortened

Let’s try the image and see if we can connect to a (physically connected) VPU:

$ docker run -it ghcr.io/ifm/ifm3d:stable
ifm@1f21eb1f98d2:/$ ifm3d dump
{
  "device": {
    "clock": {
      "currentTime": 1581111542490926304,
      ...

If this is working, we can also try the ifm3dpy implementation:

ifm@8a167fde9edc:/$ python3
Python 3.6.9 (default, Apr 18 2020, 01:56:04)
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import ifm3dpy
>>> o3r = ifm3dpy.O3RCamera()
>>> o3r.to_json()
{'device': {'clock': {'currentTime': ...

Building on top of the ifm base image

Now you want your own container, with your Python script to run. Base your Dockerfile simply on the ghcr.io/ifm/ifm3d:stable image:

FROM ghcr.io/ifm/ifm3d:stable

You can now include your application code.

Once the image is built, you can deploy it to the VPU. Read more here.