Last summer, I wrote a couple of posts about my lightweight, roll-your-own approach to deploying PSGI (Dancer) web apps:
In those posts, I described how I avoided heavyweight deployment tools by writing a small, custom Perl script (app_service
) to start and manage them. It was minimal, transparent, and easy to replicate.
It also wasn’t great.
What Changed?
The system mostly worked, but it had a number of growing pains:
- It didn’t integrate with the host operating system in a meaningful way.
- Services weren’t resilient — no automatic restarts on failure.
- There was no logging consolidation, no dependency management (e.g., waiting for the network), and no visibility in tools like
systemctl
. - If a service crashed, I’d usually find out via
curl
, notjournalctl
.
As I started running more apps, this ad-hoc approach became harder to justify. It was time to grow up.
Enter psgi-systemd-deploy
So today (with some help from ChatGPT) I wrote psgi-systemd-deploy — a simple, declarative deployment tool for PSGI apps that integrates directly with systemd
. It generates .service
files for your apps from environment-specific config and handles all the fiddly bits (paths, ports, logging, restart policies, etc.) with minimal fuss.
Key benefits:
-
- Declarative config via
.deploy.env
- Optional
.env
file support for application-specific settings - Environment-aware templating using
envsubst
- No lock-in — it just writes
systemd
units you can inspect and manage yourself
- Declarative config via
- Safe — supports a
--dry-run
mode so you can preview changes before deploying - Convenient — includes a
run_all
helper script for managing all your deployed apps with one command
A Real-World Example
You may know about my Line of Succession web site. This is one of the Dancer apps I’ve been talking about. To deploy it, I wrote a .deploy.env
file that looks like this:
1 2 3 4 5 6 7 8 |
WEBAPP_SERVICE_NAME=succession WEBAPP_DESC="British Line of Succession" WEBAPP_WORKDIR=/opt/succession WEBAPP_USER=succession WEBAPP_GROUP=psacln WEBAPP_PORT=2222 WEBAPP_WORKER_COUNT=5 WEBAPP_APP_PRELOAD=1 |
And optionally a .env
file for app-specific settings (e.g., database credentials). Then I run:
1 |
$ /path/to/psgi-systemd-deploy/deploy.sh |
And that’s it. The app is now a first-class systemd
service, automatically started on boot and restartable with systemctl
.
Managing All Your Apps with run_all
Once you’ve deployed several PSGI apps using psgi-systemd-deploy
, you’ll probably want an easy way to manage them all at once. That’s where the run_all
script comes in.
It’s a simple but powerful wrapper around systemctl
that automatically discovers all deployed services by scanning for .deploy.env
files. That means no need to hard-code service names or paths — it just works, based on the configuration you’ve already provided.
Here’s how you might use it:
1 2 3 4 5 6 7 8 |
# Restart all PSGI apps $ run_all restart # Show current status $ run_all status # Stop them all (e.g., for maintenance) $ run_all stop |
And if you want machine-readable output for scripting or monitoring, there’s a --json
flag:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ run_all --json is-active | jq . [ { "service": "succession.service", "action": "is-active", "status": 0, "output": "active" }, { "service": "klortho.service", "action": "is-active", "status": 0, "output": "active" } ] |
Under the hood, run_all
uses the same environment-driven model as the rest of the system — no surprises, no additional config files. It’s just a lightweight helper that understands your layout and automates the boring bits.
It’s not a replacement for systemctl
, but it makes common tasks across many services far more convenient — especially during development, deployment, or server reboots.
A Clean Break
The goal of psgi-systemd-deploy
isn’t to replace Docker, K8s, or full-featured PaaS systems. It’s for the rest of us — folks running VPSes or bare-metal boxes where PSGI apps just need to run reliably and predictably under the OS’s own tools.
If you’ve been rolling your own init scripts, cron jobs, or nohup
-based hacks, give it a look. It’s clean, simple, and reliable — and a solid step up from duct tape.