10 — Cloud Storage
← Previous: 09 — Secret Manager
Cloud Storage (GCS) stores static files and user-uploaded media. We'll create two buckets with Terraform: one for static files (CSS, JS) and one for media (user uploads).
Why two buckets?
| Bucket | Contents | Who writes | Who reads |
|---|---|---|---|
static |
CSS, JS, icons — from collectstatic |
Django (at deploy time) | Browsers directly |
media |
User-uploaded images, avatars | Django (at runtime) | Browsers directly |
Both are publicly readable so browsers can fetch files directly without going through Django.
Create the buckets with Terraform
Add to infrastructure/main.tf:
# Cloud Storage buckets
resource "google_storage_bucket" "static" {
name = "${var.project_id}-static"
location = var.region
force_destroy = true
# Uniform bucket-level access (simpler, recommended)
uniform_bucket_level_access = true
# Public access prevention (keep off so we can make public)
public_access_prevention = "inherited"
# CORS for font loading from web fonts (if needed)
# cors {
# origin = ["https://mycoolproject.com"]
# method = ["GET"]
# response_header = ["Content-Type"]
# max_age_seconds = 3600
# }
}
resource "google_storage_bucket" "media" {
name = "${var.project_id}-media"
location = var.region
force_destroy = true
uniform_bucket_level_access = true
public_access_prevention = "inherited"
}
# Make static bucket publicly readable
resource "google_storage_bucket_iam_member" "static_public" {
bucket = google_storage_bucket.static.name
role = "roles/storage.objectViewer"
member = "allUsers"
}
# Make media bucket publicly readable
resource "google_storage_bucket_iam_member" "media_public" {
bucket = google_storage_bucket.media.name
role = "roles/storage.objectViewer"
member = "allUsers"
}
# Allow Cloud Run service account to write to both buckets
resource "google_storage_bucket_iam_member" "run_static_admin" {
bucket = google_storage_bucket.static.name
role = "roles/storage.objectAdmin"
member = "serviceAccount:${google_service_account.run.email}"
}
resource "google_storage_bucket_iam_member" "run_media_admin" {
bucket = google_storage_bucket.media.name
role = "roles/storage.objectAdmin"
member = "serviceAccount:${google_service_account.run.email}"
}
output "static_bucket" {
value = google_storage_bucket.static.name
}
output "media_bucket" {
value = google_storage_bucket.media.name
}
Run terraform apply to create both buckets.
What gets created
| Bucket name | URL | Purpose |
|---|---|---|
mycoolproject-prod-static |
https://storage.googleapis.com/mycoolproject-prod-static/ |
CSS, JS, fonts |
mycoolproject-prod-media |
https://storage.googleapis.com/mycoolproject-prod-media/ |
User uploads |
Django configuration
In web/core/settings/prod.py:
STORAGES = {
# Default storage: user uploads go here
"default": {
"BACKEND": "storages.backends.gcloud.GoogleCloudStorage",
"OPTIONS": {
"bucket_name": "${var.project_id}-media",
},
},
# Static files: collectstatic uploads here
"staticfiles": {
"BACKEND": "storages.backends.gcloud.GoogleCloudStorage",
"OPTIONS": {
"bucket_name": "${var.project_id}-static",
"default_acl": None, # Removes public ACL, relies on bucket policy
"object_parameters": {
"cache_control": "public, max-age=31536000",
},
},
},
}
GS_PROJECT_ID = var.project_id
STATIC_URL = f"https://storage.googleapis.com/${var.project_id}-static/"
MEDIA_URL = f"https://storage.googleapis.com/${var.project_id}-media/"
Collectstatic: upload static files
Before deploying, run collectstatic to upload CSS, JS, and fonts to GCS:
This uploads all static files to the static bucket. Django's GCS backend handles this automatically via the storages library.
No media files to collect
Media files (user uploads) are uploaded at runtime when users submit forms. There's no collectstatic equivalent — Django writes directly to GCS via the storages backend.
Verify buckets exist
You should see:
Navigation
- 01 — Introduction: What We're Building
- 02 — Terraform Overview
- 03 — Cloud Services Explained
- 04 — PlanetScale Database Explained
- 05 — Project Setup & Terraform State
- 06 — GCP Project & APIs
- 07 — Artifact Registry
- 08 — Secrets Management
- 09 — Cloud Storage (Current chapter)
- 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