/home/josephspurrier

Deploying Go Apps to Google Cloud Run

A few of my projects use the same Makefile and Dockerfile to handle deployment to Google Cloud Run. Rather than reinvent the wheel every time, I packaged it up as a reusable template on GitHub: cloud-run-go-deployment. Drop it into a project, hand it to your AI of choice to adjust for your needs, and you’re most of the way there.

Why Cloud Run

Google Cloud Run is one of those tools that does exactly what it says with very little ceremony. You give it a Docker image, it runs it. You don’t manage servers, you don’t configure scaling rules, you don’t worry about uptime. It scales to zero when there’s no traffic which is great if you’re running side projects and it scales up automatically when requests come in. Because billing is per 100ms of actual execution, a low-traffic app can run for well under a dollar a month.

Compared to something like AWS, where deploying a containerized app typically involves Lambda with CloudFront or a load balancer in front, Cloud Run is refreshingly simple. You deploy a Docker image, map a custom domain, and get a free SSL certificate. That’s it. I wrote more about that tradeoff in my earlier article on Polar Bear Blog, a lightweight Go blogging system I built specifically because Cloud Run made self-hosting feel tractable.

Getting Started

The template uses a .env file for configuration. Copy these four values in and you’re ready to go:

GCP_PROJECT_ID=your-gcp-project-id
GCP_REGION=us-central1
GCP_IMAGE_NAME=helloworld
GCP_CLOUDRUN_NAME=helloworld

Before running anything, authenticate and set your project:

gcloud auth login
gcloud config set project YOUR_PROJECT_ID

Your GCP account will need a handful of IAM roles: cloudbuild.builds.editor to submit builds, storage.admin to push images to Container Registry, run.admin to deploy and manage services, and iam.serviceAccountUser so Cloud Build can act as the Cloud Run service account during deployment. If a deploy fails with a permissions error, that’s usually the first place to look.

From there, the workflow is a single command:

make

That runs gcp-push under the hood, which submits your source to Cloud Build, builds the Docker image, pushes it to Google Container Registry, and deploys the result to Cloud Run as a publicly accessible service. When it finishes, Cloud Run outputs the service URL.

This template builds the Docker image remotely via Cloud Build rather than on your local machine. If you prefer to build locally and push the image yourself, you can but the remote approach has a few practical advantages. If you’re already running inside a Docker container (a dev container or CI environment, for example), you avoid the complexity of nested Docker daemons. On a slow connection, the build happens entirely in GCP, so you’re not downloading base images and re-uploading the final result. And for large builds, it keeps your local CPU, memory, and disk out of the equation entirely.

One thing worth knowing: Cloud Run injects a PORT environment variable automatically. Your Go app needs to read it at startup:

port := os.Getenv("PORT")
if port == "" {
    port = "8080"
}

If you hardcode the port, the service will fail to start.

Environment Variables

The template handles the initial deploy, but you’ll likely need to update environment variables over time without a full redeploy. The gcloud CLI has a flag for that:

gcloud run services update SERVICE_NAME \
  --update-env-vars KEY1=VALUE1,KEY2=VALUE2

The full reference for this flag is in the Google Cloud Run documentation.

SSL and Custom Domains

Cloud Run gives you a free *.run.app URL out of the box with SSL already handled. If you want a custom domain, you can map one through the Cloud Run console or via gcloud:

gcloud run domain-mappings create \
  --service SERVICE_NAME \
  --domain your-domain.com \
  --region REGION

After that, you’ll add a CNAME or A record pointing your domain to the provided address. GCP automatically provisions and renews the SSL certificate once the DNS change propagates. The certificate provisioning can take anywhere from a few minutes to a couple of hours depending on DNS TTLs. Once it’s done, everything is managed so you don’t touch it again.

How Long Does It Take?

On the first deployment, Cloud Build is pulling dependencies, compiling, and pushing the image. Expect somewhere in the range of two to five minutes for a simple app. Subsequent deployments are faster once layers are cached - typically under two minutes for small changes. If you’re iterating quickly, that turnaround is fast enough to keep momentum.

For local development, make run starts the server directly without Docker. If you want hot reload while you’re working, air pairs well with this setup.

Cleaning Up

When you’re done with a service, make gcp-remove deletes it from Cloud Run. It won’t clean up images in Container Registry, so you may want to prune those separately if storage cost is a concern, though for most projects it’s negligible.

#go #gcp