Quadlets captured from the live units on bob, with the real immutable tags (0.1.44-ge06652a) rather than a placeholder, plus the hanzalova vhost that mounts the relay on /relay-api/ of the existing name. Verified end to end: /relay-api/health returns ok through the prefix, the relay's nested /v1 routes answer 401 rather than 404 (so the strip reaches them and they are genuinely protected), the remote's own /v1 is unaffected, and the served SPA bundle now carries the origin-relative /relay-api base. The vhost is committed because it is half the design: the relay only works on a path because nginx strips the prefix, and a future kanban.l4ir.net needs exactly this location block and nothing else.
deploy
The podman quadlets for the self-hosted stack, as deployed on
bob.hanzalova.internal in /etc/containers/systemd/. Fronted by nginx on
hanzalova.internal as https://kanban.internal.
The image is built and published by .gitea/workflows/container.yml in this
repo as git.lair.cafe/lair/vibe-kanban-remote.
Ordering is load-bearing
db -> server -> electric, and not merely cosmetically:
- The server's sqlx migrations are what create the
electric_syncrole and the publication Electric attaches to, so Electric started first has nothing to attach to. Notify=healthyon the server holds systemd in "starting" until/v1/healthanswers, so Electric genuinely waits for the migrations rather than racing them.vibe-kanban-electricsetsRequires=vibe-kanban.service, so restarting the server stops Electric but does not bring it back. Restart it explicitly.
Pinned images, deliberately
Image= names an immutable ${version}-g${sha} tag, not :latest, and
AutoUpdate=registry is deliberately absent. podman-auto-update.timer is
enabled on bob, so a floating tag would mean the running version is decided by
whenever the timer last fired. Deploys here are a one-line edit plus a restart.
To roll forward: take the tag the container workflow prints, edit Image=,
systemctl daemon-reload, restart the server, then restart Electric.
Secrets
/etc/vibe-kanban/env (mode 0600, root:root) holds the postgres DSN and the
local-auth settings; it mirrors pass lair/vibe-kanban/*. It is not in this
repo. Note the DSN embeds the password inline, so redact on the URL shape rather
than on variable names when pasting any of it around.
Auth is local single-account mode (SELF_HOST_LOCAL_AUTH_*) with no OAuth
provider; the server refuses to start unless at least one provider is
configured. Moving to a real IdP means adding a provider, not editing these
files.
Networking
vibe-kanban.network is a private bridge with dns_enabled, so the containers
resolve each other by name via aardvark-dns on the bridge address. That requires
the bridge to sit in firewalld's trusted zone — firewall-cmd --reload drops
runtime-only zone assignments, which silently kills all DNS inside these
containers and surfaces as login 500s (failed to fetch local auth user by email ... Temporary failure in name resolution). The assignment is made
permanent on bob; if it is ever lost:
firewall-cmd --permanent --zone=trusted --add-interface=podman1
firewall-cmd --reload