<- blog list | ↓ oldest blog | ↑ newest blog | Authored on: 24-11-2024

How to Add Docker Support to a Yocto Project

In this blog, we'll walk through the process of adding docker to an already existing Yocto project step-by-step. I don't go into detail on how to use the docker or Yocto and I presume you know how to use and run docker, more or less.

But why would you want docker in your embedded image in the first place anyways?

Integrating docker into an embedded Linux environment can bring significant advantages, such as simplifying application deployment and management. We can easily and safely add more features into the image during runtime for different devices, by using specific images in the docker itself. It might be difficult to roll out newer versions of the whole kernel for the device, when we can plug in and out specific versions of the software using the docker runtime.

Of course, lxc would be a better solution if you have to care about limitations on the power/size of your embedded device, but because you are reading this blog post, then I guess you are more familiar with docker as well as the previous thing isn't a concern for you.

Let's go through all that we need to do to get to the promised land of docker in embedded system.

Step 0: Quick check

First let's talk what we have:

  • we are on poky version kirkstone (but we can use any other yocto version, as long as it is also supported for the meta-virtualization repo)
  • we want to add the docker binary to be used in our produced image
  • we are doing everything in the poky repo (just becuase it's simpler for the example we will discuss here)
  • we have some custom image designed in the meta-my-layer layer, that implements my-docker-image image

Step 1: Add the required layer

docker support is generally provided by the meta-virtualization layer. You’ll need to clone and add this layer to your Yocto build.

repos/poky/

# Clone the meta-virtualization layer
~/repos/poky $ git clone git://git.yoctoproject.org/meta-virtualization

repos/poky/build/conf/bblayers.conf

# Add the layer to your build configuration, edit config to include meta-virtualization
BBLAYERS ?= " \
  ${TOPDIR}/../meta \
  ${TOPDIR}/../meta-poky \
  ${TOPDIR}/../meta-yocto-bsp \
  ${TOPDIR}/../meta-virtualization \
"

Step 2: Enable docker in the build configuration

With the layer added, you can now enable docker support in your build.

repos/poky/build/conf/local.conf

IMAGE_INSTALL_append = " docker"

This will ensure that docker is included in the final image.

If you use packagegroup's to actually append stuff to the final image, you can add it to a new one or one of your current ones.

You might need to add support for certain kernel features required by docker. Edit your build/conf/local.conf to include:

repos/poky/build/conf/local.conf

DISTRO_FEATURES_append = " virtualization"

docker also has specific runtime dependencies, such as cgroup support and certain kernel modules. If these features are not enabled in your kernel, you may need to enable them manually. More on this in the Appendix.

Step 3: Include the docker recipe in the image

In some cases, adding docker to IMAGE_INSTALL_append might not be enough. You might need to explicitly include the docker recipe in your image recipe.

repos/poky/meta-my-layer/recipes-core/images/my-docker-image.bb

DESCRIPTION = "My custom Docker-enabled image"
LICENSE = "MIT"

inherit core-image

IMAGE_FEATURES += "ssh-server"
IMAGE_INSTALL += "docker docker-contrib"

The docker-contrib package can be added for additional tools and scripts that complement the Docker setup.

Step 4: Build the image

With all the configuration changes in place, build your image using the following command:

~/workdir/build $ bitbake my-docker-image

This process may take some time, depending on the complexity of your setup and the hardware used. Once completed, the image will include the Docker binary and any dependencies needed for Docker to function.

Step 5: Deploy and test docker on the built image

After succesfully building and flashing the new image on your device (or running using qemu), you should be able now to test out the docker binary that you have included into your image.

(qemu)/ $ docker --version
(qemu)/ $ docker version

If you don't have the docker daemon running, try getting it up. If that fails to run, you might be running into one of the issues that are listed in the Appendix, scroll down for some tips on how to resolve that.

You should see a message displaying the Docker version. To test Docker functionality, try running a simple container:

(qemu)/ $ docker run hello-world

This should pull the hello-world image and run it successfully, indicating that Docker is properly configured.

If you don't have network access on your device, you can also import an image and load it with docker load command.

Appendix: common issues and troubleshooting

If you are running some problems in getting the docker daemon working, the below might be a good load of things to make sure is correctly configured and set up so that docker has everything it needs to handle containers.

Configure docker daemon with .json config file:

You can create a daemon.json configuration file at /etc/docker/ on the target device to set options like:

/etc/docker/daemon.json on the device

{
  "storage-driver": "overlay2",
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "2"
  }
}

Minimize image size:

Use docker-slim or similar tools to minimize the footprint of your Docker images, as embedded devices often have limited storage.

Kernel configuration:

Ensure that your kernel is configured with the required options. Docker requires support for:

  • Namespaces (CONFIG_NAMESPACES)
  • Control Groups (CONFIG_CGROUPS)
  • Overlay Filesystem (CONFIG_OVERLAY_FS)

If you’re using the Yocto kernel recipes, you can modify these settings in the kernel configuration files under meta/recipes-kernel/linux/.

You can also use this(taken from moby repository) script on the image, to check where you are with the specific kernel modules.

File permissions:

Ensure that the docker daemon has correct file permissions set up. If you are using a specific cgroup docker user, make sure he can access whatever you give it in the daemon.json config file.

Network configuration on device:

Make sure that your custom routing rules are playing nicely with the docker rules.