For years, most of my Perl web apps lived happily enough on a VPS. I had full control of the box, I could install whatever I liked, and I knew where everything lived.
In fact, over the last eighteen months or so, I wrote a series of blog posts explaining how I developed a system for deploying Dancer2 apps and, eventually, controlling them using systemd. I’m slightly embarrassed by those posts now.
Because the control that my VPS gave me also came with a price: I also had to worry about OS upgrades, SSL renewals, kernel updates, and the occasional morning waking up to automatic notifications that one of my apps had been offline since midnight.
Back in 2019, I started writing a series of blog posts called Into the Cloud that would follow my progress as I moved all my apps into Docker containers. But real life intruded and I never made much progress on the project.
Recently, I returned to this idea (yes, I’m at least five years late here!) I’ve been working on migrating those old Dancer2 applications from my IONOS VPS to Google Cloud Run. The difference has been amazing. My apps now run in their own containers, scale automatically, and the server infrastructure requires almost no maintenance.
This post walks through how I made the jump – and how you can too – using Perl, Dancer2, Docker, GitHub Actions, and Google Cloud Run.
Why move away from a VPS?
Running everything on a single VPS used to make sense. You could ssh in, restart services, and feel like you were in control. But over time, the drawbacks grow:
-
You have to maintain the OS and packages yourself.
-
One bad app or memory leak can affect everything else.
-
You’re paying for full-time CPU and RAM even when nothing’s happening.
-
Scaling means provisioning a new server — not something you do in a coffee break.
Cloud Run, on the other hand, runs each app as a container and only charges you while requests are being served. When no-one’s using your app, it scales to zero and costs nothing.
Even better: no servers to patch, no ports to open, no SSL certificates to renew — Google does all of that for you.
What we’ll build
Here’s the plan. We’ll take a simple Dancer2 app and:
-
Package it as a Docker container.
-
Build that container automatically in GitHub Actions.
-
Deploy it to Google Cloud Run, where it runs securely and scales automatically.
-
Map a custom domain to it and forget about server admin forever.
If you’ve never touched Docker or Cloud Run before, don’t worry – I’ll explain what’s going on as we go.
Why Cloud Run fits Perl surprisingly well
Perl’s ecosystem has always valued stability and control. Containers give you both: you can lock in a Perl version, CPAN modules, and any shared libraries your app needs. The image you build today will still work next year.
Cloud Run runs those containers on demand. It’s effectively a managed starman farm where Google handles the hard parts – scaling, routing, and HTTPS.
You pay for CPU and memory per request, not per server. For small or moderate-traffic Perl apps, it’s often well under £1/month.
Step 1: Dockerising a Dancer2 app
If you’re new to Docker, think of it as a way of bundling your whole environment — Perl, modules, and configuration — into a portable image. It’s like freezing a working copy of your app so it can run identically anywhere.
Here’s a minimal Dockerfile for a Dancer2 app:
-
FROM perl:5.42— starts from an official Perl image on Docker Hub. -
Cartonkeeps dependencies consistent between environments. -
The app is copied into
/app, andcarton install --deploymentinstalls exactly what’s in yourcpanfile.snapshot. -
The container exposes port 8080 (Cloud Run’s default).
-
The
CMDruns Starman, serving your Dancer2 app.
To test it locally:
Then visit http://localhost:8080. If you see your Dancer2 homepage, you’ve successfully containerised your app.
Step 2: Building the image in GitHub Actions
Once it works locally, we can automate it. GitHub Actions will build and push our image to Google Artifact Registry whenever we push to main or tag a release.
Here’s a simplified workflow file (.github/workflows/build.yml):
Once that’s set up, every push builds a fresh, versioned container image.
Step 3: Deploying to Cloud Run
Now we’re ready to run it in the cloud. We’ll do that using Google’s command line program, gcloud. It’s available from Google’s official downloads or through most Linux package managers — for example:
|
1 2 3 4 |
# Fedora, RedHat or similar sudo dnf install google-cloud-cli # or on Debian/Ubuntu: sudo apt install google-cloud-cli |
Once installed, authenticate it with your Google account:
Once that’s done, you can deploy manually from the command line:
This tells Cloud Run to start a new service called myapp, using the image we just built.
After a minute or two, Google will give you a live HTTPS URL, like:
Visit it — and if all went well, you’ll see your familiar Dancer2 app, running happily on Cloud Run.
To connect your own domain, run:
|
1 2 3 |
gcloud run domain-mappings create \ --service=myapp \ --domain=myapp.example.com |
Then update your DNS records as instructed. Within an hour or so, Cloud Run will issue a free SSL certificate for you.
Step 4: Automating the deployment
Once the manual deployment works, we can automate it too.
Here’s a second GitHub Actions workflow (deploy.yml) that triggers after a successful build:
You can take it further by splitting environments — e.g. main deploys to staging, tagged releases to production — but even this simple setup is a big step forward from ssh and git pull.
Step 5: Environment variables and configuration
Each Cloud Run service can have its own configuration and secrets. You can set these from the console or CLI:
|
1 2 |
gcloud run services update myapp \ --set-env-vars="DANCER_ENV=production,DATABASE_URL=postgres://..." |
In your Dancer2 app, you can then access them with:
|
1 |
$ENV{DATABASE_URL} |
It’s a good idea to keep database credentials and API keys out of your code and inject them at deploy time like this.
Step 6: Monitoring and logs
Cloud Run integrates neatly with Google Cloud’s logging tools.
To see recent logs from your app:
If you prefer a UI, you can use the Cloud Console’s Log Explorer to filter by service or severity.
Step 7: The payoff
Once you’ve done one migration, the next becomes almost trivial. Each Dancer2 app gets:
-
Its own Dockerfile and GitHub workflows.
-
Its own Cloud Run service and domain.
-
Its own scaling and logging.
And none of them share a single byte of RAM with each other.
Here’s how the experience compares:
| Aspect | Old VPS | Cloud Run |
|---|---|---|
| OS maintenance | Manual upgrades | Managed |
| Scaling | Fixed size | Automatic |
| SSL | Let’s Encrypt renewals | Automatic |
| Deployment | SSH + git pull | Push to GitHub |
| Cost | Fixed monthly | Pay-per-request |
| Downtime risk | One app can crash all | Each isolated |
For small apps with light traffic, Cloud Run often costs pennies per month – less than the price of a coffee for peace of mind.
Lessons learned
After a few migrations, a few patterns emerged:
-
Keep apps self-contained. Don’t share config or code across services; treat each app as a unit.
-
Use digest-based deploys. Deploy by image digest (
@sha256:...) rather than tag for true immutability. -
Logs are your friend. Cloud Run’s logs are rich; you rarely need to
sshanywhere again. -
Cold starts exist, but aren’t scary. If your app is infrequently used, expect the first request after a while to take a second longer.
-
CI/CD is liberating. Once the pipeline’s in place, deployment becomes a non-event.
Costs and practicalities
One of the most pleasant surprises was the cost. My smallest Dancer2 app, which only gets a handful of requests each day, usually costs under £0.50/month on Cloud Run. Heavier ones rarely top a few pounds.
Compare that to the £10–£15/month I was paying for the old VPS — and the VPS didn’t scale, didn’t auto-restart cleanly, and didn’t come with HTTPS certificates for free.
What’s next
This post covers the essentials: containerising a Dancer2 app and deploying it to Cloud Run via GitHub Actions.
In future articles, I’ll look at:
-
Connecting to persistent databases.
-
Using caching.
-
Adding monitoring and dashboards.
-
Managing secrets with Google Secret Manager.
Conclusion
After two decades of running Perl web apps on traditional servers, Cloud Run feels like the future has finally caught up with me.
You still get to write your code in Dancer2 – the framework that’s made Perl web development fun for years – but you deploy it in a way that’s modern, repeatable, and blissfully low-maintenance.
No more patching kernels. No more 3 a.m. alerts. Just code, commit, and dance in the clouds.
