Files
vaultwarden-deployment/quadlets/caddy.container.j2

62 lines
2.5 KiB
Django/Jinja

[Unit]
Description=Caddy reverse proxy (custom build with googleclouddns + Coraza)
Wants=network-online.target vaultwarden.service
After=network-online.target vaultwarden.service
# Wants= (not Requires=) on vaultwarden.service: a Vaultwarden restart
# returns a brief 502 from reverse_proxy rather than tearing down Caddy
# (which would drop TLS termination at 443). Client cache covers the gap.
OnFailure=status-email-root@%n.service
[Container]
# Custom build pushed by cloudbuild.yaml. :latest is what `podman
# auto-update` watches; bump deliberately by re-tagging a known-good <sha>
# tag as :latest in Artifact Registry. The repo grants
# roles/artifactregistry.reader to allUsers (declared in Pulumi) so the
# host pulls without auth.
#
# The image path is templated from `caddy_image` which Ansible receives via
# --extra-vars at deploy time, sourced from the Pulumi output `imageRepo`.
Image={{ caddy_image }}
ContainerName=caddy
AutoUpdate=registry
Network=vaultwarden.network
# 443 on the host. The sysctl ip_unprivileged_port_start=443 (set by the
# podman role) lets rootless caddy bind without CAP_NET_BIND_SERVICE.
PublishPort=443:443
# The distroless image runs Caddy as nonroot (65532). keep-id maps the host
# vault_user onto that uid, so the bind-mounted data/log dirs (owned by
# vault_user) are writable inside; without it they appear as root:root 0750
# and Caddy can't open its logs or store certs.
UserNS=keep-id:uid=65532,gid=65532
# The host sysctl above doesn't carry into the container's own netns, where
# nonroot would otherwise be refused :443 (and :80, which Caddy binds for
# redirects but isn't published). Same as Docker's per-container default.
Sysctl=net.ipv4.ip_unprivileged_port_start=0
# Caddy's default storage is $HOME/.local/share/caddy — the container layer,
# wiped on every auto-update recreate. Keep certs on the /data mount instead.
Environment=XDG_DATA_HOME=/data
# Caddyfile bind-mount, plus Caddy's data dir (certs, OCSP staples) and
# log dir (access log + Coraza audit). All :Z under SELinux.
Volume=%h/caddy/Caddyfile:/etc/caddy/Caddyfile:Z,ro
Volume=%h/caddy/data:/data:Z
Volume=%h/caddy/logs:/var/log/caddy:Z
# VAULT_FQDN, WG_SUBNET + GCP_PROJECT come from the Ansible-rendered env file.
EnvironmentFile=%h/caddy/caddy.env
# GCP SA JSON for DNS-01. Podman mounts the secret at /run/secrets/gcp-sa-dns
# by default for type=mount.
Environment=GOOGLE_APPLICATION_CREDENTIALS=/run/secrets/gcp-sa-dns
Secret=gcp-sa-dns,type=mount
[Service]
Restart=always
RestartSec=10s
TimeoutStartSec=300
[Install]
WantedBy=default.target