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
4timatlee@CT-TLA-WIN102:~$
5timatlee@CT-TLA-WIN102:~$ docker image ls
6REPOSITORY TAG IMAGE ID CREATED SIZE
7klakegg/hugo latest-ext 2cebd049c8d2 5 days ago 816MB
8
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
15docker.io/library/alpine:latest
16
17real 0m4.232s
18user 0m0.138s
19sys 0m0.142s
20timatlee@CT-TLA-WIN102:~$
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
13timatlee@CT-TLA-WIN102:~$
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
7docker.io/library/alpine:latest
8
9real 0m3.333s
10user 0m0.091s
11sys 0m0.077s
12timatlee@CT-TLA-WIN102:~$
Likewise, I see this image in the library:
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
12docker.io/library/alpine:latest
13
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}
2do
3 docker image rm debian -f
4 docker pull debian
5done
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:
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 registry-1.docker.io
:
1dig a registry-1.docker.io
2
3; <<>> DiG 9.11.5-P4-5.1+deb10u2-Debian <<>> a registry-1.docker.io
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
8
9;; OPT PSEUDOSECTION:
10; EDNS: version: 0, flags:; udp: 512
11;; QUESTION SECTION:
12;registry-1.docker.io. IN A
13
14;; ANSWER SECTION:
15registry-1.docker.io. 59 IN A 18.232.227.119
16registry-1.docker.io. 59 IN A 3.218.162.19
17registry-1.docker.io. 59 IN A 34.238.187.50
18registry-1.docker.io. 59 IN A 52.20.56.50
19registry-1.docker.io. 59 IN A 52.4.20.24
20registry-1.docker.io. 59 IN A **54.85.107.53**
21registry-1.docker.io. 59 IN A **52.54.232.21**
22registry-1.docker.io. 59 IN A 107.23.149.57
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.
Todo...
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.
Configurations
Moreso as a reminder for myself, since a lot of this I've already linked elsewhere.
docker-registry/docker-compose.yml
The docker-compose file that runs all this stuff.
1version: '3'
2services:
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
11
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
24
25networks:
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
2log:
3 fields:
4 service: registry
5storage:
6 delete:
7 enabled: true
8 cache:
9 blobdescriptor: inmemory
10 filesystem:
11 rootdirectory: /var/lib/registry
12http:
13 addr: :5000
14 headers:
15 X-Content-Type-Options: [nosniff]
16 Access-Control-Allow-Origin: ['http://home.timatlee.com:82']
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']
22health:
23 storagedriver:
24 enabled: true
25 interval: 10s
26 threshold: 3
27proxy:
28 remoteurl: https://registry-1.docker.io
Client configuration
This is the default + customizations on Windows.
This file is located in /etc/docker/daemon.json
on my Linux host.
1{
2 "insecure-registries" : ["home.timatlee.com"],
3 "registry-mirrors" : ["http://home.timatlee.com:82"],
4 "debug": false,
5 "experimental": false,
6 "features": {
7 "buildkit": true
8 }
9}