Run Docker Registry at Home with HTTPS
Run Docker Registry at home with HTTPS using letsencrypt.
Background
Just like everyone (on twitter), I spent a lot of time vibe coding personal tools such as bookmark manager, postgres UI etc. I want to deploy these applications on my home server as containers. I don't want to push these containers to public/private registry because it is faster and cheaper to use a local registry.
You can run docker registry as a docker container easily:
docker run -d --name registry -p 5000:5000 registry:3
However, you can only access it via http unless you give it a cert for https, or configure docker daemon and other places to trust the insecure registry.
One easy way to get a cert is using tools like mkcert
to install a local CA and issue cert for e.g. docker.registry.lan. However,
this does not work when you run registry on one machine (e.g. ubuntu) and want
to access from other machines (e.g. mac). It also does not work when you access
from a container unless you install the CA in the container.
ChatGPT suggested using step-ca, after looking at the instructions, I decided to just google homelab https and found a reddit post. I know Let's Encrypt, but I always thought you need to run a live server to pass the verification to get a cert. Turns out you can also update DNS record and get the cert without running a server for the (wildcard) domain. There are tons of tools out there, certbot, acme.sh. I decided to use lego because it is written in Go.
Get cert using lego with domain managed by Cloudflare
First get a Cloudflare token, go to https://dash.cloudflare.com/profile/api-tokens
and use the Edit Zone DNS template:
- Zone, DNS, Edit
- Include, Specific zone,
<your-domain>, e.g.example.com
Install lego:
go install github.com/go-acme/lego/v4/cmd/lego@latest
Set the token via env (doc):
CLOUDFLARE_DNS_API_TOKEN=1234567890abcdefghijklmnopqrstuvwxyz \
lego --dns cloudflare -d '*.example.com' -d example.com run
Then you get files in ~/.lego/certificates/<your-domain>.crt|key. You can pass
the crt and key file to docker registry container directly:
# https://distribution.github.io/distribution/about/deploying/#get-a-certificate
docker run -d \
--restart=always \
--name registry \
-v "$(pwd)"/certs:/certs \
-e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
-e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
-p 443:443 \
registry:3
I have a proxy running on my home server, I just let the proxy run with https
and forward requests to registry container running plain http. The cert is for
wildcard domain *.example.com so I can route based on host in the proxy,
e.g. docker.example.com proxies to localhost:5000. This also allows all my
other applications to get https, e.g. pgui.example.com.
Using a proxy server like caddy could simplify the setup (caddy has builtin https issuance and renewal). But I decided to write (vibe) my own proxy server in Go.
Configure router to resolve domain
I got the cert for domain *.example.com. However, if I do not do anything in
my router, the DNS resolve will lead me to nothing because I don't even have a
DNS record for that domain. The DNS verification method only checks TXT record
for domain ownership. My router flint3
comes with OpenWRT and LuCI so I can update DNS to point the domain to local IP:
/example.com/192.168.1.123
Testing
Tag and push an image to the registry:
docker pull alpine:latest
docker tag alpine:latest docker.example.com/alpine:latest
docker push docker.example.com/alpine:latest
Pull the image from another machine on the same network:
docker pull docker.example.com/alpine:latest
