Skip to content

Cookie settings

Choose which kinds of cookies you allow. You can change your choice at any time via Cookie settings at the bottom of every page. Privacy statement

Necessary

Always on

Needed to log in, keep your cart and remember your choices. These are always on.

Which cookies?
  • weboke_sessie, weboke_klant, weboke_remember: your session and staying logged in
  • weboke_basket: your cart
  • wkBtwIncl: showing prices with or without VAT
  • wok_cookies: your cookie choice, 12 months

Measures which ad leads to a visit or order, so we do not pay for ads that do nothing.

Which cookies?
  • Google Ads via Google Tag Manager (_gcl_au, _gcl_aw), up to 90 days
  • OpenAI Ads, ads in ChatGPT
  • wok_aff: which partner referred you to us (affiliate programme), 60 days

4.6

Country and language

Each country has its own site, with prices and VAT for that country.

View

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:80

Before you start

  1. Docker Engine with the compose plugin (lesson 9.2).
  2. Two DNS records pointing to your server, A and AAAA (lesson 7.1): status.example.com and kluis.example.com.
  3. 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.
  4. 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
└── .env

compose.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-opdracht

Only 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 prune

pull 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, DOMAIN set to the https address, sign-ups closed after your own account, ADMIN_TOKEN as an Argon2 hash in a .env with 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-data directory, and update with docker compose pull and up -d at a fixed moment.

Quiz

1. Why do Uptime Kuma and Vaultwarden not publish any ports themselves in this recipe?



2. Why doesn't the Vaultwarden web vault work properly over plain http?



3. You have created your own Vaultwarden account. What is the next step?



4. You put the Argon2 hash of the admin token directly under environment: in compose.yaml and get warnings about unknown variables. Why?



5. How do you update these services?



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.

Exercise Z9.4: Prove that your container is running

Click "Show my code" and add the header X-Weboke-Cursus with that code to your Caddyfile, for example: header { X-Weboke-Cursus "<code>" }. From the outside, nobody can see whether something runs in a container, so you also paste the output of docker compose ps (we don't store it).




What you enter and what we find isn't stored. Your browser remembers your answers and whether you've passed the exercise. If you're signed in, we only save that you got the exercise (almost) right in My courses; you can delete that there too.

Further reading

One moment…

Set up your domains

For each domain, arrange the nameservers, the link to your hosting and who becomes the owner.

Loading your domains…