This is pre-release Docker stuff. Although agreed in principle at time of writing, this may never become part of Docker or may be changed in significant ways. Once this is supported by Docker, you should use their documentation.

I used Stefan Scherer’s WhoAmI image to help me understand this functionality.

In an earlier post, I mentioned that I have set up a Swarm cluster at home built out of Raspberry Pis for testing OpenFAAS. The beauty of this serverless framework is that it can run on anything that runs Docker Swarm or Kubernetes. So, because I will likely graduate from using it on a Pi to using it on a “proper” server one day, I thought it might be a nice idea to write all my functions so that they can be used in multiple architectures.

With QEMU and using the Resin containers, there’s a well-established way of building an ARM container on an AMD64 machine (see Docker). This way I can build all the different Docker images in my continuous integration tool, publish to the Docker Hub and get my Swarm cluster to pull down the relevant images.

BUT!!!

If it all worked that easily, then life would be far too simple. When I deployed the images to my Swarm cluster, I would wait hours and the containers would be stuck in a pending state. Eventually, I found that the Swarm was reporting that there was no suitable nodes available. Confused, I ran the image on a Pi and it all worked fine.

After lots of Googling, I did a docker inspect on the container and found that the architecture was reporting as amd64. You can still see this on the Resin containers.

docker pull resin/raspberry-pi-alpine-node
docker inspect resin/raspberry-pi-alpine-node:latest | grep -i arch

Returns:

         "io.resin.architecture": "armhf",
"io.resin.architecture": "armhf",
"Architecture": "amd64",

It appears that the Architecture key is set by whatever the machine that builds the container is, not by what the image is. This is a right pain in the arse and would mean being unable to build ARM containers on my CI machine.

Manifest To The Rescue

The concept of the manifest seems fairly simple and not dissimilar to that of a symbolic link. You upload your Docker image as normal, with a descriptive name (for example, linux-amd64-latest and linux-arm-latest). Then you create the manifest with the tag latest, tell it which images are included within it and what the operating system and architecture is. Then, when you pull the latest tag down, it will get the relevant image dependent upon your operating system and architecture.

A Working Example

This is based upon the Makefile in my Distance Finder function. This is written as a series of commands so they can be explained, but something like a Makefile would be more usable. This will build an AMD64 and an ARM version - you can add further variants as required.

DOCKER_MANIFEST_URL=https://6582-88013053-gh.circle-artifacts.com/1/work/build/docker-linux-amd64
IMG_NAME=my-username/my-image-name
VERSION=1.2.3

# Build your Docker image
docker build --file ./Dockerfile --tag ${IMG_NAME}:linux-amd64-latest .
docker build --file ./Dockerfile.arm --tag ${IMG_NAME}:linux-arm-latest .

# Create tags
docker tag ${IMG_NAME}:linux-amd64-latest ${IMG_NAME}:linux-amd64-${VERSION}
docker tag ${IMG_NAME}:linux-arm-latest ${IMG_NAME}:linux-arm-${VERSION}

# Push images to Docker
docker push ${IMG_NAME}:linux-amd64-latest
docker push ${IMG_NAME}:linux-amd64-${VERSION}
docker push ${IMG_NAME}:linux-arm-latest
docker push ${IMG_NAME}:linux-arm-${VERSION}

# Download Docker beta with manifest support
curl -L ${DOCKER_MANIFEST_URL} -o ./docker
chmod +x ./docker

# Create manifest for the version tag
./docker -D manifest create "${IMG_NAME}:${VERSION}" \
"${IMG_NAME}:linux-amd64-${VERSION}" \
"${IMG_NAME}:linux-arm-${VERSION}"

# Set the architecture to ARM for the ARM image
./docker -D manifest annotate "${IMG_NAME}:${VERSION}" "${IMG_NAME}:linux-arm-${VERSION}" --os=linux --arch=arm --variant=v6

# Push the manifest
./docker -D manifest push "${IMG_NAME}:${VERSION}"

# Repeat for the latest tag
./docker -D manifest create "${IMG_NAME}:latest" \
"${IMG_NAME}:linux-amd64-latest" \
"${IMG_NAME}:linux-arm-latest"
./docker -D manifest annotate "${IMG_NAME}:latest" "${IMG_NAME}:linux-arm-latest" --os=linux --arch=arm --variant=v6
./docker -D manifest push "${IMG_NAME}:latest"

Fortunately, you won’t need to get your downloaded Docker binary to log in as it will get anything it needs from the main Docker binary.