Skip to content

03 — Cloud Services Explained

Previous: 02 — Terraform Overview

This chapter explains each cloud service we'll use. The guide implements this infrastructure on Google Cloud Platform (GCP), but the concepts apply across cloud providers.


Cloud platform overview

A cloud platform provides compute, storage, databases, networking, and more — each service is independent and managed through Terraform or the provider's console.

We'll use a subset of these services. Let's go through each one.


Cloud Run

What it is

Cloud Run is a serverless container platform. You give it a Docker image and it runs it as a service that: - Starts when requests come in - Scales to zero when idle (no cost) - Scales up automatically under load - Handles HTTPS automatically - Gives you a public URL

Why we use it

Instead of managing VMs or Kubernetes clusters, you just deploy a Docker image and Cloud Run handles everything else. It's the simplest way to run a production web app on GCP.

Key concepts

  • Service — the running web application. Has a URL, revision history, traffic splitting.
  • Revision — a specific version of the service (created on each deploy).
  • Container — a running instance of your Docker image.
  • Concurrency — how many requests each container handles simultaneously.

Cost

Free tier: 2 million requests + 360K CPU GB-seconds per month. After that: ~$0.00004 per request.

With --min-instances=0 (scale to zero), you pay nothing when there's no traffic.


Cloud Tasks

What it is

Cloud Tasks is a managed task queue. You enqueue a job (a unit of work), and a worker picks it up and processes it. Think of it as a to-do list for background work.

Why we use it

Some operations are too slow or too heavy to run inside a web request: - Sending emails (can take seconds) - Generating PDFs or reports - Processing images - Calling external APIs that are slow

Instead of making the user wait, you enqueue the job and respond immediately. The worker processes it in the background.

Key concepts

  • Queue — a named queue that holds tasks. We'll create one called default.
  • Task — a unit of work (essentially a function call with arguments, serialized).
  • Worker — a separate Cloud Run Job that pulls tasks from the queue and processes them.

How it works

Web request → Django receives it → Enqueues task to Cloud Tasks →
Django responds immediately (user doesn't wait) →
Cloud Run Job worker picks up task → Does the work

Why not Cloud Run Jobs directly?

Cloud Run Jobs run once and exit. Cloud Tasks provides: - Reliability — if a worker fails, Cloud Tasks retries the task - Scheduling — Cloud Tasks tasks can be scheduled to run at a specific time (useful for reminders, batch jobs) - Rate limiting — prevents your workers from being overwhelmed - Queue management — pause, resume, purge queues

Cloud Run Jobs are what actually executes the tasks. Cloud Tasks is the queue that holds them.


Cloud Scheduler

What it is

Cloud Scheduler is a cron service — it triggers something on a time-based schedule.

Why we use it

We need to trigger the Cloud Tasks worker periodically. Cloud Scheduler can POST to an endpoint on a schedule, which triggers the worker to process any due tasks.

For example: "check every minute for scheduled tasks" or "send daily digest at 9am".

How it works

Cloud Scheduler (every minute)
POST to Cloud Run Job URL → Worker wakes up → Processes due tasks

Cloud Scheduler itself is a trigger mechanism — the actual work is done by Cloud Run Jobs.


Cloud Storage (GCS)

What it is

Cloud Storage is Google's object storage — like AWS S3. Files are stored as objects in buckets (flat namespaces, not directories). It's durable, globally accessible, and cheap.

Why we use it

Two reasons:

  1. Static files — CSS, JavaScript, icons. These are generated by collectstatic and uploaded to a GCS bucket. Browsers load them directly from GCS, not from Django.

  2. Media files — user-uploaded images (avatars, listing photos). Stored in GCS, not on the container filesystem (which would be lost if the container restarts).

Key concepts

  • Bucket — a container for objects. Bucket names are globally unique.
  • Object — a file stored in a bucket.
  • Public access — buckets can be public (anyone can read) or private.

Two buckets

Bucket Purpose Access
my-project-static CSS, JS, icons from collectstatic Public
my-project-media User uploads (images) Public

Public access means browsers can load static assets directly from GCS, bypassing Django entirely.


Artifact Registry

What it is

Artifact Registry is GCP's private container registry — a place to store Docker images. Think of it as Docker Hub, but inside your GCP project and private.

Why we use it

When GitHub Actions builds your Docker image, it needs somewhere to store it. Cloud Run pulls from here when deploying. The image never leaves GCP's network.

Key concepts

  • Repository — a collection of related images (we have one called my-project-repo)
  • Image — the actual Docker image with a tag (e.g., app:latest, app:<git-sha>)
  • Tags — labels like latest or abc123 that point to specific images

Image naming

Images in Artifact Registry look like:

southamerica-east1-docker.pkg.dev/my-project/my-project-repo/app:latest

Breaking it down: - southamerica-east1-docker.pkg.dev — the registry host - my-project — your GCP project - my-project-repo — the repository name - app — the image name - latest — the tag


Secret Manager

What it is

Secret Manager stores secrets — passwords, API keys, tokens, anything sensitive. Secrets are encrypted at rest and only decrypted when requested.

Why we use it

We can't hardcode credentials in the code or put them in environment files that get committed to git. Secret Manager provides: - Encryption at rest - Version history (roll back if needed) - Access control via IAM - Runtime injection into Cloud Run

How it works with Cloud Run

When deploying to Cloud Run, you reference secrets by name. At container startup, Cloud Run fetches the secret values and injects them as environment variables.

--set-secrets=DATABASE_URL=DATABASE_URL:latest,SECRET_KEY=SECRET_KEY:latest

This maps the Secret Manager secret DATABASE_URL to the environment variable DATABASE_URL inside the container.

Cost

Free tier: 6 secret versions and 10,000 accesses per month. After that: $0.06 per version per month.


IAM (Identity and Access Management)

What it is

IAM controls who can do what in your GCP project. It works with: - Members — users, service accounts, groups - Roles — sets of permissions (like "can read storage buckets") - Policies — attach roles to members

Why we use it

Instead of giving everyone admin access, we follow the principle of least privilege: give each identity only the permissions it needs.

Service Accounts

A service account is an identity for a program (not a person). Our Cloud Run containers run as a service account, which has permissions to: - Read secrets from Secret Manager - Read/write objects in Cloud Storage - Connect to PlanetScale (via connection string — not GCP-specific)

Example

# Give Cloud Run service account permission to read secrets
gcloud projects add-iam-policy-binding my-project \
  --member="serviceAccount:my-run-sa@my-project.iam.gserviceaccount.com" \
  --role="roles/secretmanager.secretAccessor"

In Terraform:

resource "google_project_iam_member" "run_secret_accessor" {
  project = var.project_id
  role    = "roles/secretmanager.secretAccessor"
  member  = "serviceAccount:${google_service_account.run.email}"
}

VPC (Virtual Private Cloud)

We don't use a VPC in this guide. Cloud Run, Cloud Storage, and Secret Manager are all inside GCP's network. Cloud Run connects to: - Secret Manager — via GCP's internal API (no firewall needed) - Cloud Storage — via internal GCP network - PlanetScale — over the public internet with SSL (no VPC needed)


What we're NOT using (and why)

Cloud SQL

We use PlanetScale instead of GCP's Cloud SQL because: - PlanetScale has a free development tier - Serverless (no server management) - Database branching is great for workflow (branch for feature, merge, deploy) - Simpler pricing

If you need full Postgres features (like foreign key constraints), you could use GCP's Cloud SQL instead — it requires managing a database server yourself, but offers complete Postgres compatibility.

Kubernetes (GKE)

Kubernetes is more powerful but significantly more complex. Cloud Run is simpler for our use case — it handles scaling, networking, and SSL automatically. We don't need the control that Kubernetes provides.

Load Balancer

Cloud Run handles HTTPS and routing automatically. We don't need a separate load balancer.