Module 9 · 9.4
Recipes: Uptime Kuma and Vaultwarden
Module 9: Docker and docker compose · Reading time: 13 minutes
Learning objective: After this lesson you can run Uptime Kuma and Vaultwarden with docker compose behind Caddy, with https, closed sign-ups, backups of the volumes and a fixed way of updating.
Key concepts: reverse proxy container, Caddyfile, automatic https, Uptime Kuma, Vaultwarden, DOMAIN, SIGNUPS_ALLOWED, ADMIN_TOKEN, Argon2, .env, websockets, backing up volumes, docker compose pull, custom header
What we are building
Two handy services on your own server, each on its own subdomain:
- Uptime Kuma on
status.example.com: a monitoring program with a web interface that keeps an eye on your websites, ports and certificates and sends you an alert when something goes down (lesson 8.3). Note: monitoring that runs on the same server as what it monitors won't notice when that whole server goes down. So use it for your other sites and services, or put it on a second server. - Vaultwarden on
kluis.example.com: a lightweight, independent server version that works with the Bitwarden apps and browser extensions. A password vault is sensitive software: only do this if you are willing to keep it properly updated and backed up. It is also perfectly fine to simply use a password manager from a provider.
In front of them we put Caddy in a container: it takes ports 80 and 443, handles the Let's Encrypt certificates itself (lessons 7.3 and 7.4) and forwards the traffic to the right container. Uptime Kuma and Vaultwarden publish no ports themselves (lesson 9.3).
internet ──443──> caddy ──> uptime-kuma:3001
└─> vaultwarden:80Before you start
- Docker Engine with the compose plugin (lesson 9.2).
- Two DNS records pointing to your server, A and AAAA (lesson 7.1):
status.example.comandkluis.example.com. - Ports 80 and 443 free on the server. If a system nginx or Caddy is already running on those ports, the Caddy container can't use them. Then choose: either stop the system web server, or let it do the proxy work and publish the containers on
127.0.0.1(lesson 9.3). In this recipe the Caddy container does it. - In ufw, open 80/tcp, 443/tcp and, for HTTP/3, also 443/udp (lesson 5.2). Strictly speaking Docker bypasses ufw, but this way your firewall overview matches reality.
The files
Everything goes in one directory, /srv/diensten/:
/srv/diensten/
├── compose.yaml
├── Caddyfile
└── .envcompose.yaml
services:
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
uptime-kuma:
image: louislam/uptime-kuma:2
restart: unless-stopped
volumes:
- uptime-kuma:/app/data
vaultwarden:
image: vaultwarden/server:1.37.5
restart: unless-stopped
environment:
DOMAIN: "https://kluis.example.com"
SIGNUPS_ALLOWED: "true"
ADMIN_TOKEN: ${VW_ADMIN_TOKEN}
volumes:
- ./vw-data:/data
volumes:
caddy-data:
caddy-config:
uptime-kuma:Before you start, check which version of Vaultwarden is the latest (the releases on GitHub) and put it in the tag. Only Caddy publishes ports; those have to be public. The volumes caddy-data (certificates), uptime-kuma and the directory vw-data contain everything you need to keep.
Caddyfile
{
email beheer@example.com
}
status.example.com {
reverse_proxy uptime-kuma:3001
header {
X-Weboke-Cursus "jouw-code-uit-de-opdracht"
defer
}
}
kluis.example.com {
reverse_proxy vaultwarden:80
}Caddy automatically fetches a certificate for both names and redirects http to https. uptime-kuma and vaultwarden are the service names from the compose file: Caddy finds them on the internal network. The header block adds an extra header line to every response from status.example.com; you use it in the exercise as proof that your proxy container is running. With defer the header is set after the backend service has given its response. If you also want HSTS and the other security headers from lesson 7.4, put them in the same block.
Setting up Vaultwarden securely
Four things make the difference between a safe vault and a dangerous one.
One: https only. The Bitwarden web vault uses the browser's cryptography, and browsers only make it available on https pages. So without https it won't work properly, and that's a good thing. The Vaultwarden documentation recommends a reverse proxy for https, such as Caddy here. Set DOMAIN to the full https address.
Two: sign-ups closed. With SIGNUPS_ALLOWED: "true", anyone who finds the page can create an account on your server. So start it, create your own account(s) straight away, set the value to "false" and run docker compose up -d again. If you want to add someone later, use the invitation feature in the admin page.
Three: a hashed admin token. The admin page (/admin) is protected with ADMIN_TOKEN. Don't put a readable password there, but an Argon2 hash. You create it with Vaultwarden itself:
anna@web1:/srv/diensten$ sudo docker compose exec vaultwarden /vaultwarden hash
Password:
Confirm Password:
ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$...'Put that line in .env (so VW_ADMIN_TOKEN='$argon2id$...', with single quotes) and make the file readable by root only: sudo chmod 600 .env. In a .env file you don't need to double the $ characters; if you put the hash directly under environment: in the compose file, you do (every $ becomes $$), otherwise Compose tries to fill them in as variables. You log in to /admin with the password you typed, not with the hash. Note: settings you save in the admin page go into vw-data/config.json and then take precedence over the environment variables.
Four: backup and restore. Everything is in vw-data: the database, attachments and keys. Include that directory in your restic backup (lesson 8.2), and for the database follow the backup instructions in the Vaultwarden wiki, so you don't copy a half-written file. Test the restore (exercise O8.2): a vault without a working backup is a risk to all your passwords.
Notifications between devices (websockets) use the same port in recent versions of Vaultwarden; Caddy passes them through without any extra setting.
Setting up Uptime Kuma
After docker compose up -d, open https://status.example.com. On the first visit, Uptime Kuma asks you to create an administrator account. Do that straight away: as long as that account doesn't exist, anyone who finds the page can create it. If you don't want that risk at all, leave the status block out of the Caddyfile first, create the account through an SSH tunnel (lesson 2.3) and then put the block back.
Then add monitors: an HTTP monitor for your website, a TCP monitor for a port, a certificate expiry warning, and a notification channel (email, a chat app or a push service). Also turn on two-factor authentication in the Uptime Kuma settings.
Starting and checking
anna@web1:/srv/diensten$ sudo docker compose up -d
anna@web1:/srv/diensten$ sudo docker compose ps
NAME SERVICE STATUS PORTS
diensten-caddy-1 caddy Up 2 minutes 0.0.0.0:80->80/tcp, [::]:80->80/tcp, 0.0.0.0:443->443/tcp, ...
diensten-uptime-kuma-1 uptime-kuma Up 2 minutes (healthy)
diensten-vaultwarden-1 vaultwarden Up 2 minutes (healthy)
anna@web1:/srv/diensten$ sudo docker compose logs caddy | grep -i "certificate obtained"
anna@web1:~$ curl -sI https://status.example.com | grep -i x-weboke
x-weboke-cursus: jouw-code-uit-de-opdrachtOnly Caddy has ports. If the certificate fails, Caddy's logs tell you why: usually the DNS doesn't point to the server yet, or port 80 isn't reachable (lesson 7.3).
Updating
A container is not updated by unattended-upgrades (lesson 4.2). Make it a fixed moment, for example every two weeks:
anna@web1:/srv/diensten$ sudo docker compose pull
anna@web1:/srv/diensten$ sudo docker compose up -d
anna@web1:/srv/diensten$ sudo docker image prunepull fetches the latest images within the tag you chose (so caddy:2 stays within version 2). For Vaultwarden with a version number, first change the tag after reading the release notes. Always read the release notes for a new major version: sometimes something in your settings has to change. Back up the volumes before a big update.
Also follow the security announcements from the developers. A vulnerability in Vaultwarden or Caddy only gets fixed on your server when you fetch the new version.
In practice
This recipe works on any VPS with Docker, at WebOké or at another provider. It is self-help: your VPS provider doesn't manage anything inside your containers. Take a snapshot before you set it up for the first time, so you can experiment with peace of mind.
Summary
- One compose file with Caddy as a reverse proxy container; only Caddy publishes ports 80 and 443, the services publish nothing.
- Caddy handles certificates and https automatically and can set custom header lines with
header. - Vaultwarden: https required,
DOMAINset to the https address, sign-ups closed after your own account,ADMIN_TOKENas an Argon2 hash in a.envwith permissions 600. - Uptime Kuma: create the administrator account straight away and turn on two-factor authentication; use it mainly to monitor other servers and sites.
- Back up the volumes and the
vw-datadirectory, and update withdocker compose pullandup -dat a fixed moment.
Quiz
Exercise
Exercise Z9.4: Prove that your container is running. Get your personal code in the exercise and put it in the Caddyfile of your Caddy container as header X-Weboke-Cursus for one of your subdomains (such as status.<jouwdomein>). Reload Caddy and enter the hostname. The program fetches https://<hostnaam>/ and checks that the certificate is valid and that the header contains exactly your code. Also paste the output of docker compose ps and answer which service is the only one that publishes ports.
Further reading
- Password managers and passkeys: how to keep your logins secure
- Uptime Kuma (installation with Docker): https://github.com/louislam/uptime-kuma
- Vaultwarden wiki, Using Docker Compose: https://github.com/dani-garcia/vaultwarden/wiki/Using-Docker-Compose
- Vaultwarden wiki, Enabling admin page: https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page
- Vaultwarden wiki, Enabling HTTPS: https://github.com/dani-garcia/vaultwarden/wiki/Enabling-HTTPS
- Caddy documentation, header: https://caddyserver.com/docs/caddyfile/directives/header
- VPS: from first login to a secure server 7.1: Uptime Kuma: monitor your sites and server
- VPS: from first login to a secure server 7.2: Vaultwarden: your own password vault
- Password managers in practice 1.4: All well-known password managers compared: country, features, reviews and scores
- Free course: Staying safe online for business owners 2.2: Choosing and using password managers
