In last week’s post I showed how to run a modern Dancer2 app on Google Cloud Run. That’s lovely if your codebase already speaks PSGI and lives in a nice, testable, framework-shaped box.
But that’s not where a lot of Perl lives.
Plenty of useful Perl on the internet is still stuck in old-school CGI – the kind of thing you’d drop into cgi-bin on a shared host in 2003 and then try not to think about too much.
So in this post, I want to show that:
If you can run a Dancer2 app on Cloud Run, you can also run ancient CGI on Cloud Run – without rewriting it.
To keep things on the right side of history, we’ll use nms FormMail rather than Matt Wright’s original script, but the principle is exactly the same.
Prerequisites: Google Cloud and Cloud Run
If you already followed the Dancer2 post and have Cloud Run working, you can skip this section and go straight to “Wrapping nms FormMail in PSGI”.
If not, here’s the minimum you need.
-
Google account and project
-
Go to the Google Cloud Console.
-
Create a new project (e.g. “perl-cgi-cloud-run-demo”).
-
-
Enable billing
-
Cloud Run is pay-as-you-go with a generous free tier, but you must attach a billing account to your project.
-
-
Install the
gcloudCLI-
Install the Google Cloud SDK for your platform.
-
Run:
and follow the prompts to:
-
log in
-
select your project
-
pick a default region (I’ll assume “europe-west1” below).
-
-
-
Enable required APIs
In your project:
-
Create a Docker repository in Artifact Registry
That’s all the GCP groundwork. Now we can worry about Perl.
The starting point: an old CGI FormMail
Our starting assumption:
-
You already have a CGI script like nms FormMail
-
It’s a single “.pl” file, intended to be dropped into “cgi-bin”
-
It expects to be called via the CGI interface and send mail using:
On a traditional host, Apache (or similar) would:
-
parse the HTTP request
-
set CGI environment variables (
REQUEST_METHOD,QUERY_STRING, etc.) -
run
formmail.plas a process -
let it call
/usr/sbin/sendmail
Cloud Run gives us none of that. It gives us:
-
a HTTP endpoint
-
backed by a container
-
listening on a port (
$PORT)
Our job is to recreate just enough of that old environment inside a container.
We’ll do that in two small pieces:
-
A PSGI wrapper that emulates CGI.
-
A sendmail shim so the script can still “talk” sendmail.
Architecture in one paragraph
Inside the container we’ll have:
-
nms FormMail – unchanged CGI script at
/app/formmail.pl -
PSGI wrapper (
app.psgi) – usingCGI::CompileandCGI::Emulate::PSGI -
Plack/Starlet – a simple HTTP server exposing
app.psgion$PORT -
msmtp-mta – providing
/usr/sbin/sendmailand relaying mail to a real SMTP server
Cloud Run just sees “HTTP service running in a container”. Our CGI script still thinks it’s on a early-2000s shared host.
Step 1 – Wrapping nms FormMail in PSGI
First we write a tiny PSGI wrapper. This is the only new Perl we need:
-
CGI::Compileloads the CGI script and turns itsmainpackage into a coderef. -
CGI::Emulate::PSGIfakes the CGI environment for each request. -
The CGI script doesn’t know or care that it’s no longer being run by Apache.
Later, we’ll run this with:
Step 2 – Adding a sendmail shim
Next problem: Cloud Run doesn’t give you a local mail transfer agent.
There is no real /usr/sbin/sendmail, and you wouldn’t want to run a full MTA in a stateless container anyway.
Instead, we’ll install msmtp-mta, a light-weight SMTP client that includes a sendmail-compatible wrapper. It gives you a /usr/sbin/sendmail binary that forwards mail to a remote SMTP server (Mailgun, SES, your mail provider, etc.).
From the CGI script’s point of view, nothing changes:
We’ll configure msmtp from environment variables at container start-up, so Cloud Run’s --set-env-vars values are actually used.
Step 3 – Dockerfile (+ entrypoint) for Perl, PSGI and sendmail shim
Here’s a complete Dockerfile that pulls this together.
-
We never touch
formmail.pl. It goes into/appand that’s it. -
msmtp gives us
/usr/sbin/sendmail, so the CGI script stays in its 1990s comfort zone. -
The entrypoint writes
/etc/msmtprcat runtime, so Cloud Run’s environment variables are actually used.
Step 4 – Building and pushing the image
With the Dockerfile and docker-entrypoint.sh in place, we can build and push the image to Artifact Registry.
I’ll assume:
-
Project ID:
PROJECT_ID -
Region:
europe-west1 -
Repository:
formmail-repo -
Image name:
nms-formmail
First, build the image locally:
