Docker Registry

Learning Docker

I'm still getting my feet wet with Docker, and using it to post my blog page, as well as a few other services that I run for my own usage.. but it didn't take me long to find out about the rate limit that was implemented in early November this year.

For the most part, I don't run into issues - my stuff doesn't turn over that quick, and I'm not developing a bunch of custom images that pull in a bunch of images or tags, so generally.. this isn't a problem.

For the heck of it, I wondered about setting up a local cache of Docker images. It seems that this is basically supported out of the box, and a recipe exists to make a pull-through cache. This all seemed pretty straightforward and all, but I lacked the know-how to tell if the cache was actually getting used or not.

So, I went to find a way to manage the registry - some sort of UI or interface that I could use to manage the packages stored by the registry. I found docker-registry-ui, an image with a healthy number of pulls (a reasonable indication of popularity?), along with some very convenient examples that made the process easy to wire up.

Testing, empirically

Other than monitoring logs, I wasn't really clear how to "prove" that this was working. One example floating around the web talked about checking downloads times.

Starting from another machine on my network, without the proxy set, I'll pull the Alpine image:

 1timatlee@CT-TLA-WIN102:~$ docker image ls
 2REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
 3klakegg/hugo        latest-ext          2cebd049c8d2        5 days ago          816MB
 5timatlee@CT-TLA-WIN102:~$ docker image ls
 6REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
 7klakegg/hugo        latest-ext          2cebd049c8d2        5 days ago          816MB
 9timatlee@CT-TLA-WIN102:~$ time docker pull alpine
10Using default tag: latest
11latest: Pulling from library/alpine
12188c0c94c7c5: Pull complete
13Digest: sha256:c0e9560cda118f9ec63ddefb4a173a2b2a0347082d7dff7dc14272e7841a5b5a
14Status: Downloaded newer image for alpine:latest
17real    0m4.232s
18user    0m0.138s
19sys     0m0.142s

Now, setting the proxy and removing the image from my machine:

 1timatlee@CT-TLA-WIN102:~$ docker image ls
 2REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
 3klakegg/hugo        latest-ext          2cebd049c8d2        5 days ago          816MB
 4alpine              latest              d6e46aa2470d        2 weeks ago         5.57MB
 5timatlee@CT-TLA-WIN102:~$ docker image rm alpine -f
 6Untagged: alpine:latest
 7Untagged: alpine@sha256:c0e9560cda118f9ec63ddefb4a173a2b2a0347082d7dff7dc14272e7841a5b5a
 8Deleted: sha256:d6e46aa2470df1d32034c6707c8041158b652f38d2a9ae3d7ad7e7532d22ebe0
 9Deleted: sha256:ace0eda3e3be35a979cec764a3321b4c7d0b9e4bb3094d20d3ff6782961a8d54
10timatlee@CT-TLA-WIN102:~$ docker image ls
11REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
12klakegg/hugo        latest-ext          2cebd049c8d2        5 days ago          816MB

Pulling again should yield the same download time (ish), and it should show up in the registry UI:

 1timatlee@CT-TLA-WIN102:~$ time docker pull alpine
 2Using default tag: latest
 3latest: Pulling from library/alpine
 4188c0c94c7c5: Pull complete
 5Digest: sha256:c0e9560cda118f9ec63ddefb4a173a2b2a0347082d7dff7dc14272e7841a5b5a
 6Status: Downloaded newer image for alpine:latest
 9real    0m3.333s
10user    0m0.091s
11sys     0m0.077s

Likewise, I see this image in the library:

Docker UI

So, theoretically, if I remove the local image and re-download it, it should come from the cache - right?

 1timatlee@CT-TLA-WIN102:~$ docker image rm alpine -f
 2Untagged: alpine:latest
 3Untagged: alpine@sha256:c0e9560cda118f9ec63ddefb4a173a2b2a0347082d7dff7dc14272e7841a5b5a
 4Deleted: sha256:d6e46aa2470df1d32034c6707c8041158b652f38d2a9ae3d7ad7e7532d22ebe0
 5Deleted: sha256:ace0eda3e3be35a979cec764a3321b4c7d0b9e4bb3094d20d3ff6782961a8d54
 6timatlee@CT-TLA-WIN102:~$ time docker pull alpine
 7Using default tag: latest
 8latest: Pulling from library/alpine
 9188c0c94c7c5: Pull complete
10Digest: sha256:c0e9560cda118f9ec63ddefb4a173a2b2a0347082d7dff7dc14272e7841a5b5a
11Status: Downloaded newer image for alpine:latest
14real    0m1.214s
15user    0m0.094s
16sys     0m0.069s

Success, I suppose.

Testing, with errors

My other approach in testing is to download an image over and over (and over) to see if it errors out. The anonymous limit is 100 pulls (well, API calls) and 200 for authenticated. Alpine is a pretty small and only has a few layers (~50?), but the Debian image isn't quite as small. 200 cycles of removing the image and pulling the image should ought to do:

1for i in {0..199}
3  docker image rm debian -f
4  docker pull debian

While that's pondering, I thought about seeing what traffic was going to/from my server, dobby. I use iftop to see whose talking to who:

iftop results

I can see a decent amount of traffic between dobby and my laptop, ct-tla-win102. Likewise, there's some minimal traffic going to a couple AWS addresses - that seem to match the A records of

 1dig a
 3; <<>> DiG 9.11.5-P4-5.1+deb10u2-Debian <<>> a
 4;; global options: +cmd
 5;; Got answer:
 6;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 18664
 7;; flags: qr rd ra; QUERY: 1, ANSWER: 8, AUTHORITY: 0, ADDITIONAL: 1
10; EDNS: version: 0, flags:; udp: 512
12;          IN      A
14;; ANSWER SECTION:   59      IN      A   59      IN      A   59      IN      A   59      IN      A   59      IN      A   59      IN      A       ****   59      IN      A       ****   59      IN      A

It seems that there's some requests going out to Docker Hub, but the bulk of the traffic is coming from my proxy. Neat.

Now I just need to wait for 200 iterations of removing and re-downloading the Debian image to complete to see if it errored in any meaningful way.

It didn't.


Now, there's a bunch of stuff that I'm not cool about with this arrangement:

  • All this is intended to run on my private network, however port 80 and 443 on the host in question are already occupied.
  • Internal addressing is really annoying - it's run from ye olde wireless router, which doesn't enable me to easily configure a new host with new host name with SSL.
  • My proxy is thus running on a non-standard port, and over HTTP.

I think the end goal is to have:

  • Docker registry running on its own internal hostname. This shouldn't be available outside the network, but it should be available while connected to a VPN.
  • SSL encryption by LE, because I'm lazy and it generally work. Which means wiring up certbot differently than I've got it wired today.


Moreso as a reminder for myself, since a lot of this I've already linked elsewhere.


The docker-compose file that runs all this stuff.

 1version: '3'
 3  docker-registry:
 4    image: registry:2
 5    volumes:
 6      - /var/lib/docker-registry/:/var/lib/registry
 7      - ./etc/config.yml:/etc/docker/registry/config.yml
 8    restart: always
 9    networks:
10      - registry-ui-net
12  docker-registry-ui:
13    image: joxit/docker-registry-ui:static
14    ports:
15      - 82:80
16    environment:
17      - REGISTRY_TITLE=Tim's Private Docker Registry
18      - REGISTRY_URL=http://docker-registry:5000
19      - DELETE_IMAGES=true
20    depends_on:
21      - docker-registry
22    networks:
23      - registry-ui-net
26  registry-ui-net:

/etc/docker/registry/config.yml, linked to ./etc/config.yml

You will need to populate your own Docker Hub credentials in the proxy: section.

In this config, I have delete: enabled: true, but it's worth noting that this isn't working...

 1version: 0.1
 3  fields:
 4    service: registry
 6  delete:
 7    enabled: true
 8  cache:
 9    blobdescriptor: inmemory
10  filesystem:
11    rootdirectory: /var/lib/registry
13  addr: :5000
14  headers:
15    X-Content-Type-Options: [nosniff]
16    Access-Control-Allow-Origin: ['']
17    Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS', 'DELETE']
18    Access-Control-Allow-Headers: ['Authorization', 'Accept']
19    Access-Control-Max-Age: [1728000]
20    Access-Control-Allow-Credentials: [true]
21    Access-Control-Expose-Headers: ['Docker-Content-Digest']
23  storagedriver:
24    enabled: true
25    interval: 10s
26    threshold: 3
28  remoteurl:

Client configuration

This is the default + customizations on Windows.

This file is located in /etc/docker/daemon.json on my Linux host.

2  "insecure-registries" : [""],
3  "registry-mirrors" : [""],
4  "debug": false,
5  "experimental": false,
6  "features": {
7    "buildkit": true
8  }