An Incomplete Guide to Docker

6 minute read

Install docker on Armbian

To install latest docker, apt repository need to be updated, add docker official repository as follows:

$ sudo apt install -y \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common
$ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -

$ sudo apt-key fingerprint 0EBFCD88

$ sudo add-apt-repository \
   "deb [arch=arm64] https://download.docker.com/linux/debian \
   buster stable"

$ sudo apt update

NOTE:
The instruction in official doc does not work for Armbian, there is no docker binaries for Focal release, change $(lsb_release -cs) to buster to make it works.

Install docker engine:

$ sudo apt install docker-ce docker-ce-cli containerd.io=1.2.13-2
sudo usermod -aG docker $(whoami)
sudo reboot

The latest containerd has segmentation issue, so I pick version 1.2.13-2, other version besides 1.3.7 may also work, version string can be queried with:

$ apt-cache madison containerd.io
containerd.io |    1.3.7-1 | https://download.docker.com/linux/debian buster/stable arm64 Packages
containerd.io |   1.2.13-2 | https://download.docker.com/linux/debian buster/stable arm64 Packages
[...]

Using mirror to save time

For restricted network, using mirrors may speed up the docker pull process, thus save a lot of time, this can be done by either pass --registry-mirrors option to dockerd in /lib/systemd/system/docker.service, or add registry-mirrors KV-pair to daemon.json.

cat << EOF | sudo tee -a /etc/docker/daemon.json
{
   "registry-mirrors": [
       "https://hub-mirror.c.163.com",
       "https://docker.mirrors.ustc.edu.cn"
  ],
  "debug": true
}
EOF

If more than one mirrors are added, dockerd will try to use the first one, if it returns with errors or timed out, then next one will be used, and so forth, if all fails, it falls to the official repo.

After daemon configuration changing file , docker must be restarted:

sudo systemctl daemon-reload
sudo systemctl restart docker

The mirrors will be shown in the Registry Mirrors in the docker info output:

$ docker info | grep -A5 'Registry Mirrors'
WARNING: No swap limit support
 Registry Mirrors:
  https://hub-mirror.c.163.com/
  https://docker.mirrors.ustc.edu.cn/
 Live Restore Enabled: false

Now try to pull hello-world to see if it works:

$ docker pull hello-world

$ journalctl -fu docker.service
[...]
Oct 28 13:30:59 arm-64 dockerd[3300]: time="2020-10-28T13:30:59.176672354Z" level=debug msg="Calling POST /v1.40/images/create?fromImage=hello-world&tag=latest"
Oct 28 13:30:59 arm-64 dockerd[3300]: time="2020-10-28T13:30:59.343914257Z" level=debug msg="hostDir: /etc/docker/certs.d/hub-mirror.c.163.com"
Oct 28 13:30:59 arm-64 dockerd[3300]: time="2020-10-28T13:30:59.395723604Z" level=debug msg="Trying to pull hello-world from https://hub-mirror.c.163.com/ v2"
Oct 28 13:31:02 arm-64 dockerd[3300]: time="2020-10-28T13:31:02.553016126Z" level=debug msg="Pulling ref from V2 registry: hello-world:latest"
Oct 28 13:31:02 arm-64 dockerd[3300]: time="2020-10-28T13:31:02.553385090Z" level=debug msg="docker.io/library/hello-world:latest resolved to a manifestList object with 9 entries; looking for a unknown/arm64 match"
[...]

Private registry server deployment

A registry is an instance of the registry image, and runs within Docker, hosting docker images in local network saves a lot of time when running docker on several machines, the following example shows how to run a private registry on local host supporting multiple architecture with Image manifest.

Julio Suarez has a blog post on Deploying a Multi-Arch Docker Registry with much details.

I am going to setup local registry on Ubuntu, and running docker image on Phicomm N1, below command will registry in background listening to port 5000, and saves all the images to /mnt/registry, there are two options to achieve this, the first one is:

$ docker run -d \
    -p 5000:5000 \
    --restart=always \
    --name registry \
    -v /mnt/registry:/var/lib/registry \
    -e REGISTRY_STORAGE_DELETE_ENABLED=true \
    registry:2

Another way to deploy registry is using compose file:

cat << EOF >> $HOME/docker/docker-compose.yml
registry:
  restart: always
  image: registry:2
  ports:
    - 5000:5000
  environment:
    REGISTRY_STORAGE_DELETE_ENABLED: "true"
  volumes:
    - /mnt/registry:/var/lib/registry
EOF

$ docker-compose up -d

The container running status can be checked with:

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
c9176f53cbb2        registry:2          "/entrypoint.sh /etc…"   1 hours ago         Up 56 minutes       0.0.0.0:5000->5000/tcp   registry

To pull arm64 image from docker hub, experimental feature has to be enabled, add "experimental": true to /etc/docker/daemon.json and restart docker service.

Now push docker images to localhost, jellyfin will be running on ubuntu, and other two are supposed to run on arm64 box:

# pull images from docker hub or mirror registry

$ docker pull jellyfin/jellyfin
$ docker image tag jellyfin/jellyfin localhost:5000/jellyfin/jellyfin-amd64
$ docker push localhost:5000/jellyfin/jellyfin-amd64
$ docker rmi jellyfin/jellyfin
$ docker rmi localhost:5000/jellyfin/jellyfin-amd64

$ docker pull --platform arm64 jellyfin/jellyfin
$ docker image tag jellyfin/jellyfin localhost:5000/jellyfin/jellyfin-arm64
$ docker push localhost:5000/jellyfin/jellyfin-arm64
$ docker rmi jellyfin/jellyfin
$ docker rmi localhost:5000/jellyfin/jellyfin-arm64

$ docker pull pihole/pihole
$ docker image tag pihole/pihole localhost:5000/pihole/pihole-amd64
$ docker push localhost:5000/pihole/pihole-amd64
$ docker rmi pihole/pihole
$ docker rmi localhost:5000/pihole/pihole-amd64

$ docker pull --platform arm64 pihole/pihole
$ docker image tag pihole/pihole localhost:5000/pihole/pihole-arm64
$ docker push localhost:5000/pihole/pihole-arm64
$ docker rmi pihole/pihole
$ docker rmi localhost:5000/pihole/pihole-arm64

$ docker pull homeassistant/home-assistant:stable
$ docker image tag homeassistant/home-assistant:stable localhost:5000/homeassistant/home-assistant-amd64:stable
$ docker push localhost:5000/homeassistant/home-assistant-amd64:stable
$ docker rmi homeassistant/home-assistant:stable
$ docker rmi localhost:5000/homeassistant/home-assistant-amd64:stable

$ docker pull --platform arm64 homeassistant/home-assistant:stable
$ docker image tag homeassistant/home-assistant:stable localhost:5000/homeassistant/home-assistant-arm64:stable
$ docker push localhost:5000/homeassistant/home-assistant-arm64:stable
$ docker rmi homeassistant/home-assistant:stable
$ docker rmi localhost:5000/homeassistant/home-assistant-arm64:stable

$ docker system prune

The repositories can be listed with docker registry HTTP API:

$ curl http://localhost:5000/v2/_catalog
{"repositories":["homeassistant/home-assistant-amd64","homeassistant/home-assistant-arm64","jellyfin/jellyfin-amd64","pihole/pihole-amd64","pihole/pihole-arm64","ubuntu"]}

$ curl http://localhost:5000/v2/pihole/pihole/tags/list
{"name":"pihole/pihole","tags":["latest"]}

The final step is to create manifest for each image, this need to enable experimental in docker config file $HOME/.docker/config.json:

{
    "experimental": "enabled"
}

The syntax for creating manifest file is:

Usage: docker manifest create MANIFEST_LIST MANIFEST [MANIFEST…]

The manifest for tags we created previously can be inspected:

$ docker manifest inspect --insecure localhost:5000/jellyfin/jellyfin-arm64
$ docker manifest inspect --insecure localhost:5000/pihole/pihole-arm64
$ docker manifest inspect --insecure localhost:5000/homeassistant/home-assistant-arm64:stable

Create manifest for each repository and push them to registry:

$ docker manifest create --insecure localhost:5000/jellyfin/jellyfin localhost:5000/jellyfin/jellyfin-arm64 localhost:5000/jellyfin/jellyfin-amd64
$ docker manifest push --insecure localhost:5000/jellyfin/jellyfin

$ docker manifest create --insecure localhost:5000/pihole/pihole localhost:5000/pihole/pihole-arm64 localhost:5000/pihole/pihole-amd64
$ docker manifest push --insecure localhost:5000/pihole/pihole

$ docker manifest create --insecure localhost:5000/homeassistant/home-assistant:stable localhost:5000/homeassistant/home-assistant-arm64:stable localhost:5000/homeassistant/home-assistant-amd64:stable
$ docker manifest push --insecure localhost:5000/homeassistant/home-assistant:stable

Now we can can see the two architectures in the newly created manifest:

$ docker manifest inspect --insecure localhost:5000/jellyfin/jellyfin
{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
   "manifests": [
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1165,
         "digest": "sha256:010e197041008ffa00d2c94f31b87d64666e9a93a9cf3314ce7f87134b801a5f",
         "platform": {
            "architecture": "amd64",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1376,
         "digest": "sha256:0826942c3085471463bb5cd30d9bd413449498bf5fb0c381d70cd7409e317d01",
         "platform": {
            "architecture": "arm64",
            "os": "linux"
         }
      }
   ]
}

This usually not needed, unless you do NOT want the registry any more:

$ docker container stop registry && docker container rm -v registry

Last but not least, image tag can also pushed from another machine running docker, enable this by adding "insecure-registries" : ["ip.add.re.ss:5000"], to daemon.json

$ docker image tag jellyfin/jellyfin:stable ip.add.re.ss:5000/jellyfin/jellyfin-arm64:stable
$ docker push ip.add.re.ss:5000/jellyfin/jellyfin-arm64:stable

Troubleshooting

segmentation in containerd

After a reboot from freshly installation of docker, docker service failed to start, the log says, the dependency failed to start:

sudo systemctl status docker
● docker.service - Docker Application Container Engine
     Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
     Active: inactive (dead)
TriggeredBy: ● docker.socket
       Docs: https://docs.docker.com

Oct 28 05:50:05 arm-64 systemd[1]: Dependency failed for Docker Application Container Engine.
Oct 28 05:50:05 arm-64 systemd[1]: docker.service: Job docker.service/start failed with result 'dependency'.

From /lib/systemd/system/docker.service we see the most probable dependency is containerd:

BindsTo=containerd.service

Here BindsTo means dockerd depends on containerd, refer to description of systemd directives.

And when I check its version, it crashes due to segfault:

$ containerd --version
unexpected fault address 0xaaaad6c4f920
fatal error: fault
[signal SIGSEGV: segmentation violation code=0x2 addr=0xaaaad6c4f920 pc=0xaaaad56fe85c]

goroutine 1 [running, locked to thread]:
runtime.throw(0xaaaad6401a57, 0x5)
	/usr/local/go/src/runtime/panic.go:774 +0x54 fp=0x400007bdd0 sp=0x400007bda0 pc=0xaaaad571b2cc
runtime.sigpanic()
	/usr/local/go/src/runtime/signal_unix.go:401 +0x394 fp=0x400007be00 sp=0x400007bdd0 pc=0xaaaad5730fcc
runtime.evacuate_fast64(0x400007bf08, 0xaaaad57294a0, 0xaaaad771b3e0)
	/usr/local/go/src/runtime/map_fast64.go:409 +0x114 fp=0x400007bec0 sp=0x400007be10 pc=0xaaaad56fe85c
runtime: unexpected return pc for runtime.doInit called from 0x300000002

The solution for this issue is to downgrade containerd to a lower version.

server gave HTTP response to HTTPS client

After the local registry setup completed, I try to test it by creating jellyfin service with compose file, I’ve noticed the pull speed was not as expected, dockerd complains about server gave HTTP response to HTTPS client:

dockerd[7266]: time="2020-10-29T23:48:39.573558660+08:00" level=warning msg="Error getting v2 registry: Get https://ip.add.re.ss:5000/v2/: http: server gave HTTP response to HTTPS client"

Most people got this resolved by adding

"insecure-registries" : ["ip.add.re.ss:5000"],

to file /etc/docker/daemon.json

This is not the case for me, I already done this before, double checking this file reveals the problem, before the ip address, it’s NOT http, it must be a copy and paste issue.