Caddy for Astro + Podman (HTTPS and Canonical Host)

Category: On-Premises & Private Cloud

Tags: podman, caddy


Once the Astro container was answering on 127.0.0.1:4321, I still needed HTTPS and a single canonical hostname. I picked Caddy over nginx for exactly one reason — its built-in ACME client means I never touch certbot timers or renewal cron jobs.

This post is the thin layer that turns the private app port into a public HTTPS site.

Install Caddy

Caddy isn’t in the default Debian repos, so I added Cloudsmith’s stable channel:

sudo apt update
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list >/dev/null
sudo apt update
sudo apt install -y caddy

Reverse proxy and canonical host

Problem: Two DNS names pointed at the VPS — adrian-altner.de and www.adrian-altner.de. I wanted exactly one to live publicly, with the other 301-ing to it.

Implementation: Caddy’s site blocks do both jobs at once — TLS issuance and the redirect — with no extra config.

# /etc/caddy/Caddyfile
www.adrian-altner.de {
  redir https://adrian-altner.de{uri} permanent
}

adrian-altner.de {
  encode zstd gzip
  reverse_proxy 127.0.0.1:4321
}

Solution: Caddy fetches Let’s Encrypt certificates for both hostnames on first boot, serves adrian-altner.de via the reverse proxy, and answers www.adrian-altner.de with a permanent redirect.

Validate and reload

sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl restart caddy
sudo systemctl enable caddy
sudo systemctl status caddy --no-pager

A formatting warning on validate is harmless — caddy fmt cleans it up in place:

sudo caddy fmt --overwrite /etc/caddy/Caddyfile

Verifying the result

curl -I http://adrian-altner.de
curl -I https://adrian-altner.de
curl -I https://www.adrian-altner.de

What I was looking for:

  • HTTP redirects to HTTPS (Caddy does this automatically once TLS is in place).
  • www returns a 301 to the apex domain.
  • The TLS certificate is trusted and issued by Let’s Encrypt.

Network hygiene

With Caddy as the only public entrypoint, there’s no reason for port 4321 to be reachable from the outside world. Binding it to localhost in compose closes that door:

ports:
  - "127.0.0.1:4321:4321"

Now the Node process only listens on the loopback interface — Caddy alone handles 80/443.

What to take away

  • Caddy collapses “reverse proxy”, “TLS”, and “canonical host” into a handful of lines of config.
  • Automatic HTTPS means one less cron job — no certbot, no renewal hooks.
  • Bind the app container to 127.0.0.1 as soon as a proxy sits in front of it; there’s no upside to exposing it publicly.
  • One canonical hostname with the other redirecting avoids duplicate-content SEO surprises later.