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:
-
Static files — CSS, JavaScript, icons. These are generated by
collectstaticand uploaded to a GCS bucket. Browsers load them directly from GCS, not from Django. -
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
latestorabc123that point to specific images
Image naming
Images in Artifact Registry look like:
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.
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.
Navigation
- 01 — Introduction: What We're Building
- 02 — Terraform Overview
- 03 — Cloud Services Explained (Current chapter)
- 04 — PlanetScale Database Explained
- 05 — Project Setup & Terraform State
- 06 — GCP Project & APIs
- 07 — Artifact Registry
- 08 — Secrets Management
- 09 — Cloud Storage
- 10 — Service Accounts & IAM
- 11 — Cloud Run
- 12 — Cloud Tasks & Scheduler
- 13 — Dockerfile
- 14 — First Deploy
- 15 — Custom Domain & SSL
- 16 — Workload Identity Federation
- 17 — GitHub Actions CI/CD
- 18 — Quick Reference