# Pattern 01 — systemd unit for a Python web service
## The pain
You wrote a Flask (or FastAPI, or Bottle, or anything-WSGI) app. It runs locally with `python app.py`. You SSH into a $5 VPS, `git pull`, and now you need it to run *forever*: restart on crash, restart on reboot, log somewhere you can `tail -f`, and let you `systemctl restart` it without remembering a magic incantation.
Half the internet will tell you to use Docker for this. Docker on a $5 VPS, for one process, with no horizontal scaling, is overkill. systemd has been on every Linux box since 2015 and it does exactly what you need in 12 lines.
## When to use it
- One Python process per service, on one Linux box.
- You want auto-restart on crash, auto-start on reboot, structured logging via `journalctl`.
- You don't need orchestration, service mesh, or rolling deploys (yet).
## The code
`/etc/systemd/system/myapp.service`:
```ini
[Unit]
Description=My Flask app
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/root/myapp
ExecStart=/usr/bin/python3 /root/myapp/app.py
Restart=on-failure
RestartSec=3
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
```
Then:
```bash
systemctl daemon-reload
systemctl enable --now myapp.service
# Check status
systemctl status myapp.service
# View logs (live)
journalctl -u myapp.service -f
# Restart after deploying new code
systemctl restart myapp.service
```
That's it. There's nothing else you need.
## When NOT to use it
This is a single-host pattern. If you need:
- More than one replica of the same service for load balancing → put nginx in front of N copies on different ports, OR move to a real orchestrator.
- Zero-downtime rolling deploys → systemd doesn't help; you need a process supervisor that can swap atomically (e.g. `systemd-socket-activation` with two services taking turns, or a real load balancer).
- Cross-machine service discovery → DNS, Consul, or any HTTP-based registry. systemd is per-host.
If you have ≥3 of those needs, look at Docker Swarm or Kubernetes. If you have 0 or 1, stay with this pattern. You'll save 100+ hours of yak-shaving.
## A note on `User=root`
Running as root is fine on a $5 personal VPS where you're the only user. It's NOT fine if multiple humans share the box, or if the service exposes file uploads / process exec / SQL injection surfaces. For those cases, create a dedicated user:
```bash
useradd -r -s /usr/sbin/nologin myapp
chown -R myapp:myapp /root/myapp
# Then in the unit:
# User=myapp
```
The unit file is unchanged otherwise.
## Further reading
- `man systemd.service` — the canonical reference, surprisingly readable
- `man systemd.unit` — the `[Unit]` section options, including `Requires=`, `After=`, `Wants=`
- Drew DeVault, "Use systemd, you'll thank me later" — https://drewdevault.com/2019/09/02/Why-I-use-old-software.html
- The systemd documentation index — https://www.freedesktop.org/wiki/Software/systemd/
## Real example from production
This is the actual unit file running my Funding Finder API service on a $5 Hetzner VPS (truncated from the full file). Resident memory: ~25 MB. Restart count over 30 days: 4 (all clean exits during deploys).
```ini
[Unit]
Description=Funding Finder API
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/root/project_30d/artifacts/funding_finder
ExecStart=/usr/bin/python3 /root/project_30d/artifacts/funding_finder/api.py
Restart=on-failure
RestartSec=5
Environment=PORT=8083
[Install]
WantedBy=multi-user.target
```
12 lines of config. Has been running for 10+ days uninterrupted across many `systemctl restart` deploys. Zero Docker. Zero Kubernetes. Zero regrets.