Keeping the Website Running After a VPS Reboot

Category: On-Premises & Private Cloud

Tags: podman, systemd


The first time the VPS rebooted for a kernel update, the site stayed down until I SSH’d in and ran podman-compose up -d by hand. That’s the kind of thing you only accept once. A deployment that survives its own runtime but not the box underneath it isn’t really production.

This post is the minimal systemd wiring that makes reboot a non-event.

The setup

  • VPS: Debian, Podman running rootful, compose stack under /opt/website.
  • Reverse proxy: Caddy (from the earlier post in this series) in front of the app container.
  • Goal: after sudo reboot, everything — container, Caddy, TLS — is back on its own.

Restart policy in Compose

Inside compose.yml:

restart: unless-stopped

Problem: restart: unless-stopped is necessary but not sufficient. It tells Podman to restart the container if it crashes, but on a full reboot there’s nobody running podman-compose up in the first place — nothing tells Podman about this compose stack at all.

Implementation: A systemd unit that owns the compose lifecycle end-to-end.

A systemd service for the compose stack

/etc/systemd/system/website-compose.service:

[Unit]
Description=Website Podman Compose Stack
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/website
ExecStart=/usr/bin/podman-compose -f compose.yml up -d
ExecStop=/usr/bin/podman-compose -f compose.yml down
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target

Two details worth calling out:

  • Type=oneshot with RemainAfterExit=yespodman-compose up -d returns immediately after starting the container, so systemd sees it exit; RemainAfterExit tells systemd to still consider the unit “active” afterwards.
  • After=network-online.target — without this, the compose stack can race the network on cold boot and fail to pull or resolve anything.

Enable and start it:

sudo systemctl daemon-reload
sudo systemctl enable website-compose.service
sudo systemctl start website-compose.service
sudo systemctl status website-compose.service --no-pager

Make sure Caddy also starts on boot

Caddy’s Debian package enables its unit by default — but it’s worth confirming rather than assuming:

sudo systemctl enable caddy
sudo systemctl status caddy --no-pager

The reboot test

The only way to know the wiring is right is to actually reboot the machine:

sudo reboot

After reconnecting over SSH:

systemctl status website-compose.service --no-pager
podman ps
curl -I http://127.0.0.1:4321
curl -I https://adrian-altner.de

What I expect to see:

  • website-compose.service active.
  • The website container running.
  • Both the local and public health checks return 200.

Troubleshooting after reboot

  • Container not running. Logs first, always: journalctl -u website-compose.service -n 100 --no-pager
  • Caddy up but site down. The app container is the usual suspect: podman logs --tail=200 website
  • TLS or proxy issues. Caddy’s journal tells you which hostname failed and why: journalctl -u caddy -n 100 --no-pager

What to take away

  • restart: unless-stopped covers crashes; systemd covers reboots — you need both.
  • After=network-online.target prevents cold-boot races that look random and are hard to debug later.
  • Type=oneshot + RemainAfterExit=yes is the right shape for a unit that wraps a tool which detaches after starting.
  • Reboot the machine on purpose, once, before you ever need to trust the setup unattended.