Deploying Dancer Apps – The Next Generation
Last summer, I wrote a couple of posts about my lightweight, roll-your-own approach to deploying PSGI (Dancer) web apps:
* Deploying Dancer Apps
* Deploying Dancer Apps: Addendum
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`, not `journalctl`.
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
* **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:
Shell
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
12345678 | WEBAPP_SERVICE_NAME=successionWEBAPP_DESC="British Line of Succession"WEBAPP_WORKDIR=/opt/successionWEBAPP_USER=successionWEBAPP_GROUP=psaclnWEBAPP_PORT=2222WEBAPP_WORKER_COUNT=5WEBAPP_APP_PRELOAD=1
---|---
And optionally a `.env` file for app-specific settings (e.g., database credentials). Then I run:
Shell
$ /path/to/psgi-systemd-deploy/deploy.sh
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:
Shell
# Restart all PSGI apps $ run_all restart # Show current status $ run_all status # Stop them all (e.g., for maintenance) $ run_all stop
12345678 | # 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:
Shell
$ 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" } ]
123456789101112131415 | $ 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.
➡️ View the code on GitHub
### Share this:
* Tweet
* * Click to share on Reddit (Opens in new window) Reddit
* More
*
* Click to email a link to a friend (Opens in new window) Email
* Click to print (Opens in new window) Print
* * Share on Tumblr
* Pocket
* *
### _Related_