Container Registry for Kamal, Secured with Tailscale
-
I think this is a niche confluence of technologies. Hopefully my experience can help someone else who’s trying to deploy services with Kamal, all while behind a Tailnet. This enables you to use your own hardware, secure in your Tailnet, to deploy easily and zero downtime with Kamal.
Since I’m only deploying personal web apps/containers with this setup, I tend to think of it as pretty ephemeral, but if you take the time with this (and maybe have a mirror), Zot is capable of being a complete container registry for docker images and any OCI Artifacts.
Setting up Zot
Zot has pretty good docs, and it’s pretty simple to setup too. We’re going to put it behind Tailscale, so if we’re a little lenient with some of the security options we should still be safe. Some of this is not best practices. At best, these are okay for a homelab setup when nothing is exposed to the internet.
In Appendix A, I have a bunch of configuration files to help get you started. Run the compose on any docker instance you want to use as your registry server. The server doesn’t need to be on your tailnet because we’ll put the registry on it directly.
Kamal requires a password be set for a registry it accesses through HTTPS, so you will need to setup a password to use in Zot.
Configure Kamal
You only need to set up Zot once, but Kamal you’ll configure for every project you want to deploy. I wrote another article that goes into more detail about the Kamal configuration for deploying on a Tailnet.
In Kamal’s deploy.yml file, the builder section must contain “driver: docker”. The docker driver has access to your machine’s DNS configuration, but Kamal’s default “docker-container” does not. I think this might change eventually, but I’m not sure. If you’re using a registry that doesn’t need Tailscale’s DNS to work, don’t worry about this setting.
I haven’t used a remote builder. I imagine it will need access to your Tailnet as well, to upload the built image. I think the docker driver limits the architectures we can build an image for, so remote might be important if your laptop and server have different architectures.
Appendix A: Zot Configuration Files
# example compose.yml with tailscale
name: registry
volumes:
registry-tailscale-state:
zot-data:
services:
sidecar:
image: ghcr.io/tailscale/tailscale:stable
hostname: registry
cap_add:
- NET_ADMIN
volumes:
- registry-tailscale-state:/var/lib/tailscale
- /path/to/configuration/directory:/config # holds the ts.json, below
devices:
- /dev/net/tun:/dev/net/tun
env_file: stack.env # put all the tailscale environment variables here
app:
image: ghcr.io/project-zot/zot:latest
depends_on:
- sidecar
volumes:
# zot configuration
- /share/Docker/stack-configs/ocireg/config.yaml:/etc/zot/config.yaml
# users and passwords
- /share/Docker/stack-configs/ocireg/htpasswd:/etc/zot/htpasswd
# where our images will be stored
- zot-data:/data/zot
command: serve /etc/zot/config.yaml
Example ts.json, Serve Functionality
{
"TCP": {
"443": {
"HTTPS": true
}
},
"Web": {
"${TS_CERT_DOMAIN}:443": {
"Handlers": {
"/": {
"Proxy": "http://app:8080"
}
}
}
},
"AllowFunnel": {
"${TS_CERT_DOMAIN}:443": false
}
}
# example Zot configuration, config.yaml
distSpecVersion: 1.0.1
storage:
rootDirectory: /data/zot
http:
address: 0.0.0.0
port: 8080
auth:
htpasswd:
path: "/etc/zot/htpasswd"
# accessControl:
# repositories:
# **:
# defaultPolicy:
# - read
# - create
# - update
# - delete
extensions:
ui:
enable: true
search:
enable: true