Introduction

As part of creating a multi-architecture ESP8266 build container I had to get to grips with docker’s experimental manifest commands. It seems like they’re fairly immature and not that well understood and documented at the moment.

The high-level process for building a multi-architecture container is to:

  • Create a container for each architecture independently.

  • Create a manifest - which looks like a container - but isn’t and rather links to separate containers, for example one for each supported architecture.

There’s an extra complexity if you want to use a single Dockerfile for the containers for both architectures, and if you need to build them from different base containers - as I have done with the ESP8266 SDK build container. There I’ve used an Ubuntu base for x86-64 and Rasbian for ARM. In order to share a Dockerfile you need a single common FROM command at the beginning of the Dockerfile, hence you need to create a manifest to create a multiple architecture FROM container, from which you then build the architecture specific containers. See here for more details on that approach.

Enabling Manifest Commands

Before you can use docker’s manifest commands, because they are currently an experimental feature, you need to enable these on both the server and client.

On Ubuntu to enable experimental features on the server edit your docker config file:

sudo vi /etc/systemd/system/docker.service.d/docker.conf

To add the experimental argument:

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -s overlay2 -H fd:// --experimental=true

Now restart the docker service:

sudo systemctl restart docker

Then enable experimental features on the client by editing the client config file:

vi /home/$USER/.docker/config.json

So it contains the following (just add the experimental bit to the existing config file within the curly braces):

{
        "experimental": "enabled"
}

If you now run docker version you should see that experimental features are enabled for both the client and server:

$ docker version
Client:
 Version:           18.06.1-ce
 API version:       1.38
 Go version:        go1.10.3
 Git commit:        e68fc7a
 Built:             Tue Aug 21 17:24:56 2018
 OS/Arch:           linux/amd64
 Experimental:      true

Server:
 Engine:
  Version:          18.06.1-ce
  API version:      1.38 (minimum version 1.12)
  Go version:       go1.10.3
  Git commit:       e68fc7a
  Built:            Tue Aug 21 17:23:21 2018
  OS/Arch:          linux/amd64
  Experimental:     true

I don’t know precisely what version of docker you need for manifest support - but expect it’s at least 18 onwards.

Building a Manifest

Once you have enabled experimental features and have the containers built for each architecture, and have them uploaded to your repo server (such as hub.docker.com), first create the manifest like this:

docker manifest create my-repo/my-manifest:version my-repo/container-amd64:version my-repo/container-arm:version

Then annotate each container, with the correct architecture and OS, like so:

docker manifest annotate my-repo/my-manifest:version my-repo/container-amd64:version --arch amd64 --os linux
docker manifest annotate my-repo/my-manifest:version my-repo/container-arm:version --arch arm --os linux

Next check your manifest looks correct by inspecting it (the example here is pinched from ubuntu:18.04):

$ docker manifest inspect my-repo/my-manifest:version
{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
   "manifests": [
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1150,
         "digest": "sha256:6b9eb699512656fc6ef936ddeb45ab25edcd17ab94901790989f89dbf782344a",
         "platform": {
            "architecture": "amd64",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1150,
         "digest": "sha256:5b0b4ee0a78af051edb7f0ebd3a02ed08bc957162a978f641cd78ea3512d59dc",
         "platform": {
            "architecture": "arm",
            "os": "linux",
            "variant": "v7"
         }
      },
   ]
}

Finally, push the manifest to the repo server. It is important you use the purge option, which clears the manifest locally once uploaded - otherwise you won’t be able to create a new manifest in future, as there doesn’t appear to be a docker manifest delete command.

docker manifest push --purge my-repo/my-manifest:version

Running the Manifest as a Container

You can now run the combined manifest as if it were a container thus on any supported architecture:

docker run my-repo/my-manifest:version
comments powered by Disqus