containerd can build and pull multi-platform images, but it doesn’t do it natively in the same way Docker does; it relies on external tools and configurations.

Let’s see containerd manage multi-platform images. Imagine you’re building a Docker image that needs to run on both amd64 (your laptop) and arm64 (a Raspberry Pi or an AWS Graviton instance).

First, you need a build tool that understands multi-platform builds and can output to a format containerd understands, like OCI. buildx is the standard tool for this. If you’re using Docker Desktop, buildx is usually included and configured. If you’re on a bare-metal Linux system, you might need to install it and set up a builder instance.

# Install buildx if not present
# Example for Debian/Ubuntu:
# apt-get update && apt-get install -y docker-buildx

# Create a new builder instance that supports multiple platforms
docker buildx create --name mybuilder --use
docker buildx inspect --bootstrap

This mybuilder instance is now capable of building for different architectures. The --use flag makes it the default for subsequent buildx commands. The inspect command confirms that the builder is running and lists the platforms it supports. You’ll likely see linux/amd64, linux/arm64, linux/arm/v7, etc., depending on your system and buildx configuration.

Now, let’s build our multi-platform image. We’ll use a simple Dockerfile.

# Dockerfile
FROM alpine:latest
RUN apk add --no-cache curl
CMD ["curl", "https://httpbin.org/ip"]

To build this for multiple platforms and push it directly to a registry (which containerd will then pull), we use buildx build. We’ll tag it as myregistry.com/myuser/multiarch-app:latest.

# Build for amd64 and arm64, and push to a registry
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t myregistry.com/myuser/multiarch-app:latest \
  --push \
  .

The --platform flag is crucial here. It tells buildx which target architectures to build for. The --push flag means the resulting image manifest list (which points to the architecture-specific layers) and the individual images will be uploaded to myregistry.com.

Once pushed, containerd can pull this multi-platform image. When containerd pulls myregistry.com/myuser/multiarch-app:latest, it consults the manifest list in the registry. Based on the architecture of the node where containerd is running, it will download the correct image layers for that specific architecture.

For example, if containerd is running on an amd64 machine, it will pull the linux/amd64 variant. If it’s on an arm64 machine, it will pull the linux/arm64 variant. This is handled automatically by the registry and containerd’s OCI compatibility.

To verify this from a system running containerd directly (not through the Docker daemon), you’d typically use nerdctl (a Docker-compatible CLI for containerd) or the containerd API.

# Install nerdctl if you haven't already
# (Follow instructions for your OS from the nerdctl GitHub repo)

# Pull the multi-platform image using nerdctl
nerdctl pull myregistry.com/myuser/multiarch-app:latest

# Run a container and check its architecture
nerdctl run --rm myregistry.com/myuser/multiarch-app:latest

The output of nerdctl run will show the IP address from httpbin.org/ip. If you run this on an amd64 host, you’ll see an amd64 IP. If you run it on an arm64 host, you’ll see an arm64 IP. The nerdctl pull command itself doesn’t explicitly show which platform it chose, but the subsequent run command confirms it.

The mental model is that buildx acts as the orchestrator for building, and it produces an OCI-compliant manifest list. This manifest list is the key to multi-platform images; it’s a single tag that points to multiple underlying images, each tagged with its specific architecture and OS. When containerd (or any OCI-compliant runtime) pulls a manifest list, it queries the registry for the best match for its current environment.

A crucial detail is how containerd resolves these manifest lists. It queries the registry for the manifest list associated with a given tag. The registry returns a JSON document that contains pointers to the actual image manifests for each platform. containerd then selects the manifest that matches OS and ARCH (and potentially VARIANT) of the host it’s running on and pulls the layers associated with that specific manifest.

What most people don’t realize is that buildx doesn’t just build separate images and then create a manifest list; it often leverages QEMU emulation or remote builders to execute the build steps for each target architecture, compiling or assembling the binaries directly for that platform before creating the manifest list. This means the resulting image layers are native to the target architecture, not just differently tagged versions of the same base image.

The next step you’ll likely encounter is managing image digests for precise versioning or dealing with caching strategies for multi-platform builds to speed up subsequent builds.

Want structured learning?

Take the full Containerd course →