Skip to content

Instantly share code, notes, and snippets.

@kjlape
Last active July 10, 2025 12:32
Show Gist options
  • Save kjlape/040098454af630cc6cf6fa78bed9b018 to your computer and use it in GitHub Desktop.
Save kjlape/040098454af630cc6cf6fa78bed9b018 to your computer and use it in GitHub Desktop.
Self-Hosted Docker Registry with Kamal (as of version 2.2.2)

Problem

Kamal smooths over a lot of the rough edges of hosting an app on a server you control. One problem that needs a little more sanding is that kamal requires us to have a docker registry to push our images to. Unless you're doing open source, you probably want these app images to stay private! Until we get an official answer from kamal here's a workaround that I've been using.

Be aware that you still need some kind of public image hosting due to limitations in kamal as of version 2.2.2. There's a hack at the bottom of this document to work around this limitation as well.

Steps to deploy

  • Change values in the deploy config to suit your setup
  • Run kamal deploy
  • Run kamal htpasswd-set <username> <password> to set as many credentials as you need or rotate keys
  • Enjoy!

Optional: Self-host (Do this before deploying)

On your local machine…

  • docker run --volume ./auth:/auth --rm --entrypoint htpasswd httpd:2 -Bb /auth/htpasswd <username> <password>
  • docker run --volume ./auth:/auth --rm --port 5000:5000 -e "REGISTRY_AUTH=htpasswd" -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd registry:2
  • In another terminal ngrok http 5000
  • Copy the ngrok url, and place it in your kamal config under registry/server
  • Ensure the credentials you gave it are configured in your kamal secrets
  • Run the steps to deploy
  • Optional: Replace the ngrok url with your freshly deployed docker registry
  • I also have a video about this on YouTube
service: docker-registry
image: my-company/docker-registry
servers:
web:
- 1.3.3.7
proxy:
ssl: true
host: docker.my-company.com
app_port: 5000
healthcheck:
path: /
registry:
username: username
server: <some url>.ngrok-free.app
password:
- KAMAL_REGISTRY_PASSWORD
builder:
arch: arm64 # This is the platform I tested on, but it should work on any platform.
volumes:
- auth:/auth
env:
clear:
REGISTRY_HTTP_ADDR: 0.0.0.0:5000
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
aliases:
shell: app exec --interactive --reuse "sh"
htpasswd-set: server exec docker run --volume auth:/auth --rm --entrypoint htpasswd httpd:2 -Bb /auth/htpasswd
htpasswd-delete: server exec docker run --volume auth:/auth --rm --entrypoint htpasswd httpd:2 -D /auth/htpasswd
htpasswd-print: server exec docker run --volume auth:/auth --rm --entrypoint cat httpd:2 /auth/htpasswd
FROM registry:2
@waynehoover
Copy link

Can you explain why you need to run ngrok, and why we couldn't just spin up a docker registry locally and then use localhost:5000 as the registry:server:?

@kjlape
Copy link
Author

kjlape commented Nov 14, 2024

Can you explain why you need to run ngrok, and why we couldn't just spin up a docker registry locally and then use localhost:5000 as the registry:server:?

@waynehoover Sure! It's because the destination server can't access my local docker registry without some kind of tunnel. It needs to be accessible on the outside somehow.

@driveton
Copy link

@kjlape I just saw the YouTube video, nice. I did set a registry on a server but I will use the local option from now on, I'm a solodeveloper, ha.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment