Anders är en webbutvecklare och hårdrockare som gillar brädspel, kaffe och öl.

VPS med Podman, Caddy och privat Container Registry

Jag vill först och främst adressera anledningen till att jag bytt VPS. Yourserver.se är ett jävla skitföretag, om du har tjänster som behöver vara uppe måste jag starkt avråda från denna oseriösa leverantör av VPS!

Min önskelista för ny VPS är följande:

  • Debian 12 istället för Ubuntu server. Snaps är inget jag känner mig hjälpt av, och kurerade stabila paket med bra grundkonfiguration är trevligt.
  • VPS placerad inom EU. Den nya ligger i Helsingfors.
  • Brandvägg som låser ute all inkommande trafik förutom SSH, HTTP och HTTPS.
  • Alla tjänster ska köras via en container eller en Pod, för detta används Podman då det passar mig bättre än Docker.
  • Privat Container Registry för att kunna ha allt som behövs för deploy på VPS.
  • SSL-certifikat ska skapas och förnyas automatiskt. Detta hanteras med reverse-proxyn Caddy då det blir allt i en lösning, i jämförelse med att köra nginx och certbot.

Först av allt: hantera lösenord och nycklar på ett säkert ställe!

Att sätta upp en VPS innebär att flera SSH-nycklar, root-lösenord och andra hemligheter skapas. Se därför till att spara dessa på ett bra och säkert ställe.

Exempelvis:

  • 1Password
  • LastPass
  • Bitwarden
  • Keepass
  • PWSafe
  • Post-It i en skrivbordslåda med lås. Återanvänd inga lösenord!

Välj ett alternativ och ha nära till hands.

Installera paket

apt update
apt install \
  podman \
  ufw \
  caddy

Skapa användare

Två typer av användare ska finnas på VPS: en användare som äger containern för Container registryt, och en användare för varje tjänst som kör på VPS. I denna artikel kommer jag bara att nämna användarne madrse, den användare som äger denna webbplats.

På detta sätt kan styrkan i att Podman kör containers som rootless.

Generera gärna lösenorden (och SSH-nycklarna) med den valda lösenordshanteraren!

useradd -m -s /bin/bash registry
passwd registry
useradd -m -s /bin/bash madrse
passwd madrse

För smidigare åtkomst, skapa SSH-nycklar och lägg i ~/.ssh/authorized_keys.

För Debian: Konfigurera och aktivera brandvägg

Debian har ingen brandvägg påslagen från start. Debians wiki om UFW är en bra start för att lära sig.

Kör nedanstående som root för att aktivera brandväggen, initialt blockera allt inkommande och tillåta allt utgående. Tilåt därefter enbart inkommande anslutningar av SSH, HTTP och HTTPS.

ufw enable
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow http
ufw allow https

Slutresultatet ska se ut såhär:

root@aginor:~# ufw status numbered
Status: active

     To                         Action      From
     --                         ------      ----
[ 1] 22/tcp                     ALLOW IN    Anywhere
[ 2] 80/tcp                     ALLOW IN    Anywhere
[ 3] 443                        ALLOW IN    Anywhere

För Debian: Tillåt användartjänster att köra även när användare inte är inloggade

Debian har som standard att inte tillåta användarens egna daemons och tjänster utanför användarsessioner. För att låta containers köra även när användaren inte är inloggad behöver en inställning ändras i loginctl.

loginctl enable-linger registry
loginctl enable-linger madrse

Verifiera att användarna har aktiverat:

root@aginor:~/.config/systemd/user$ ls /var/lib/systemd/linger
madrse  registry

Sätt upp privat container registry

En dedikerad användare har redan skapats för att starta container registryt via en container. Använd denna användare för detta avsnitt, kör ej som root!

Detta avsnitt är baserat på Digital Oceans guide.

Preppa autentisering

Glöm inte att spara i lösenordshanteraren!

mkdir -p ~/docker-registry/auth
cd ~/docker-registry/auth
htpasswd -Bc registry.password username

Hämta och starta registry-tjänsten

Skapa först katalog.

mkdir -p ~/docker-registry/data

Hämta och starta container registry.

podman run -d \
  --name registry \
  -p 5000:5000 \
  -e REGISTRY_AUTH=htpasswd \
  -e REGISTRY_AUTH_HTPASSWD_REALM=Registry \
  -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/registry.password \
  -e REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/data \
  -v '/home/registry/docker-registry/auth:/auth' \
  -v '/home/registry/docker-registry/data:/data' \
docker.io/registry:2

Använd podman för att generera systemd-användartjänst, och aktivera den därefter.

mkdir -p .config/systemd/user
(cd .config/systemd/user && podman generate systemd --new --files --name registry)
systemctl --user daemon-reload
systemctl --user enable container-registry
systemctl --user start container-registry

Konfigurera bortstädning av äldre container images

För att spara på diskutrymme så konfigureras registryt att bara behålla de 2 senaste pushade versionerna av en container image, och att städa bort alla äldre.

Detta baseras på andrey-pohilko/registry-cli.

För att också städa bort gamla oanvända hämtade containers körs även podman system prune.

Skapa först cleanup.sh med följande innehåll. Ersätt inloggningsuppgifterna med de sparade i lösenordshanteraren.

# touch cleanup.sh
# chmod +x cleanup.sh
podman run --rm \
  docker.io/anoxis/registry-cli \
  -l '<registry username>:<registry password>' \
  -r 'https://registry.madr.se' \
  --delete \
  --num 2
podman system prune -f

Nu kan städning ske när som helst med ssh registry@vps ./cleanup.sh. För egen del föredrar jag också att skapa en crontab som städar regelbundet, t ex varje lördag morgon.

0 6 * * 6 ~/.cleanup.sh 2>/dev/null 1>&2

Lägg till container registry i Caddyfile

Lägg till en reverse proxy för port 5000 för att få HTTPS. Öka även storleken på request_body så att det går att skicka container images på några hundra MB till registryt.

registry.madr.se {
    request_body {
        max_size 500MB
    }
    file_server
    reverse_proxy localhost:5000
}

För madr.se

Nu är VPS:en redo för den första tjänsten, och det råkar vara denna webbplats. Här är en lista över saker som ska finnas.

  • En pod som kör CMS, databas till CMS och webbplatsens WWW-rot.
  • Ett par containers i registryt för att kunna göra en deploy, t ex en container med Metalsmith.
  • En entry i Caddyfile för att aktivera HTTPS och ge publik åtkomst.

Pusha upp containers

Logga först in på container registry.

podman login registry.madr.se

Bygg därefter varje container som behövs och pusha till registry.

podman build .
podman tag <container id> registry.madr.se/madrse-<name>
podman push registry.madr.se/madrse-<name>

Skapa pod och starta containers

Inloggad som madr.se-användaren på VPS, skapa pod för att köra alla containers för tjänsten. kör ej som root! Sätt 8055 respektive 1337 är publika portar för CMS och WWW-rot.

podman pod create \
  --name madrse \
  -p 8055:8055 \
  -p 1337:80

Starta därför varje enskild container i poden, para ihop med podden görs med flaggan med --pod=madrse. Skippa portmappning då poden hanterar detta automatiskt. Lägg också till --replace för att enkelt kunna ersätta containern med en nyare version. Låt databasens och CMS:ets filer hanteras som volymer för att minska risken för dataförlust!

Såhär görs detta för madr.se. (alla miljövariabler är maskade, kika på respektive tjänsts dokumentation för att se vad som behövs).

podman run -d \
  --pod=madrse \
  --name=db \
  --replace \
  -v /home/madrse/directus/data:/var/lib/postgresql/data \
docker.io/postgres:alpine

podman run -d \
  --pod=madrse \
  --name=directus \
  --replace \
  -v /home/madrse/directus/uploads:/directus/uploads \
docker.io/directus/directus:10.10.7

podman run -d \
  --pod=madrse \
  --name=www \
  --replace \
registry.madr.se/madrse-w3:latest

Kör pod med systemd

Systemd är kontroversiellt, men är fortfarande det fördragna sättet att köra användartjänster kopplade till bootprocessen.

Använd Podman för att generera och aktivera tjänsten.

mkdir -p .config/systemd/user
(cd .config/systemd/user && podman generate systemd --new --files --name madrse)
systemctl --user daemon-reload
systemctl --user enable pod-madrse
systemctl --user start pod-madrse

Lägg till madr.se i Caddyfile

CMS och WWW-rot kör på egna subdomäner. För maximal flexibilitet aktiveras även file_server för enklare felsidor, favikoner etc.

cms.madr.se {
    file_server
    reverse_proxy localhost:8055
}

madr.se {
    file_server
    reverse_proxy localhost:1337
}

Städa bort oanvända containers

För att inte gamla och ej använda containers med bihang ska ligga och sluka disk, schemalägg ett cronjob som städar. En gång per vecka räcker gott, jag föredrar lördag morgon.

0 6 * * 6 podman system prune -f 2>/dev/null 1>&2

Nästa steg

Sammanfattningsvis är detta nu klart. CMS, databas och WWW-rot finns på plats. Skeppande av ny kod sker med push till registryt och en omstart av WWW-rotens container.

Ny deploy av madr.se WWW-rot:

ssh vps podman pull registry.madr.se/madrse-w3:latest
ssh vps podman run -d \
  --pod=madrse \
  --name=www \
  --replace \
registry.madr.se/madrse-w3:latest

För omstart av podden:

ssh vps systemctl --user restart pod-madrse

Det är förmodligen smidigt att automatisera Zero downtime deploy. Detta kan nog bli en egen artikel när jag landat i hur jag vill göra.

Eventuellt kommer jag även skriva om vad som krävdes för att få till Directus att lira med Metalsmith.

Fel och fixar

Det händer ibland att skumma fel uppstår, särskilt i centrala delar. I detta avsnitt samlas de jag vet jan uppstå och en lösning till dem.

"Failed to connect to bus: No medium found" vid systemctl --user

Kan uppstå vid installation direkt via tty istället för ssh, eller på en användare som skapats under installationsprocessen av Debian. Relaterat till DBUS.

Lägg till i .bashrc eller motsvarande:

if [ -z "$DBUS_SESSION_BUS_ADDRESS" ]; then
    eval $(dbus-launch --sh-syntax)
    export DBUS_SESSION_BUS_ADDRESS
    export DBUS_SESSION_BUS_PID
    export XDG_RUNTIME_DIR=/run/user/$(id -u)
fi

Om felet kvarstår, installera följande paket:

apt install dbus-x11

Sidenote: prefixet "-x11" är ren humbug, då detta inte kräver x11. Jag skyller detta på Debian som namngivit illa.