Integrating Docker images into a Yocto distribution is not supported by the meta-virtualization layer, which only focuses on building OCI images. This post presents the meta-dockin layer, a simple integration solution that follows the Yocto philosophy but also has some drawbacks compared to a less conventional approach.

Integration in Yocto

Integrating Docker images into a Yocto distribution means providing a ready-to-use Docker data-root at runtime containing the required images, without having to download them with docker pull.

In this post we only consider Docker images that are already built and available in a public or private registry.

Before introducing the meta-dockin solution, let’s see the limitations of the meta-virtualization layer, then present two possible approaches to solve this problem.

Limitations of meta-virtualization

The meta-virtualization layer provides the docker-moby_git.bb recipe to install Docker-CE (moby + Docker CLI). It also allows building OCI images directly from BitBake recipes.

For example, the app-container.bb image recipe located in recipes-demo/images/ inherits the image-oci.bbclass class to build an OCI image from the generated rootfs by adding the "oci" filesystem type.

However, the layer currently does not support integrating container images directly into the rootfs.

This choice is explained by Bruce Ashfield — the maintainer of meta-virtualization — in the mailing list discussion Importing Docker Images and in his talk Building and deploying containers with meta-virtualization: now & in the future.

As a result, image integration is still left to the developer.

Build-time integration

The idea behind this approach is to create a BitBake recipe that pulls Docker images into a local data-root, which is then installed into the rootfs.

To do this, a task runs the Docker daemon (dockerd) in the background with the --data-root option pointing to a directory inside the recipe WORKDIR.

Each image can then be downloaded using docker pull. Once all images are fetched, the daemon is stopped.

While this approach is relatively easy to implement and provides a ready-to-use data-root at runtime, it also has several drawbacks:

  • The daemon requires privileged rights. Using sudo is considered bad practice in Yocto, and configuring rootless Docker can be complicated.
  • The build cannot run alongside another Docker daemon on the same machine.
  • If the build runs inside a Docker container, additional permissions are required (mount, unshare), which can lead to permission errors.
  • Overall, this approach does not really follow the Yocto philosophy.

The meta-embedded-containers layer provides a proof-of-concept implementation of this solution, refer to How to embed a Docker image into Yocto’s root filesystem.

Two-step integration

The second approach works in two steps.

First, Docker images are downloaded as archives and included in the rootfs.
Then, during the first boot, the archives are loaded into the Docker data-root using the docker load command. This approach is less restrictive during the build process.

However, the Docker data-root must be stored on persistent storage to avoid loading the archives on every boot. Thus, the overhead of this solution is limited to the first boot and the archives can be removed once loaded.

This method is demonstrated in the talk OCI/Docker containers with meta-virtualization and OE / the Yocto Project.

Meta-dockin

There are several ways to implement this two-step solution.

The meta-dockin layer that I designed during a client project is one example of such an implementation.
The goal was to install one or more Docker images along with additional elements such as a Docker Compose file.

To do this, the layer allows Docker images to be declared directly in the recipe SRC_URI variable. Multiple Docker images can be installed in a single recipe.

Example recipe installing the hello-world Docker image:

inherit dockin

SRC_URI += "docker://hello-world"

The layer is composed of three main elements:

  • A docker fetcher used to download Docker image archives via SRC_URI
  • A BitBake class dockin used to compress and install the archives into the rootfs
  • A preload systemd service per image to load the archives into the Docker data-root at runtime

Let’s take a closer look at these elements.

Docker fetcher

The layer implements a fetcher in lib/dockin/dockerfetcher.py to download Docker images in archive format.

The rootless and daemonless skopeo tool is used, and must be installed on the host machine. HOSTTOOLS += "skopeo" is added in layer.conf.

The Docker image archive architecture is set from the TARGET_ARCH variable.

The image tag can be specified using the tag URL parameter, for instance:

SRC_URI = "docker://docker.io/library/busybox;tag=1.36.1"

By default, the latest tag is fetched.

Note: For private registries, skopeo login is required before running BitBake.

Dockin class

The dockin BitBake class helps integrate Docker image archives into the rootfs.

Docker image archives are installed in ${datadir}/dockin/archive.
The path can be changed in the recipe with:

docker_archivedir = "/custom/path"

Archive compression

Docker images downloaded by Skopeo are uncompressed archives.
To reduce their size in the rootfs, the dockin class compresses them.

The compression method is configured with the following variables defined in the dockin bbclass:

COMPRESSION_TYPE ?= "xz"
COMPRESSION_CMD:xz ?= "xz -f -k -9 -T0 -c"
COMPRESSION_RDEPENDS:xz ?= "xz"

Runtime preload service

A systemd preload service is created for each Docker image and packaged in ${PN}-preload, which is installed by default. To disable preload services at boot, simply set in your recipe:

SYSTEMD_AUTO_ENABLE:${PN}-preload = "disable"

By default, the Docker image archive is removed once loaded to free space. If the data-root is non-persistent (volatile storage), this can be disabled in the recipe with:

DOCKIN_PRELOAD_FLUSH = "0"

Note: The Docker images are only preloaded if necessary. The dockin bbclass does not start the containers.

Examples

The layer provides two examples in recipes-demo/dockin:

  • hello-world.bb: A simple recipe to install the hello-world Docker image.
  • compose-example.bb: A recipe installing two images and providing a systemd service to run them with a Docker Compose file.