Saltar a contenido

09 — Cloud Storage

Anterior: 08 — Gestión de Secretos

Cloud Storage (GCS) almacena archivos estáticos y medios subidos por usuarios. Crearemos dos buckets con Terraform: uno para archivos estáticos (CSS, JS) y uno para medios (subidas de usuarios).


¿Por qué dos buckets?

Bucket Contenido Quién escribe Quién lee
static CSS, JS, íconos — de collectstatic Django (en tiempo de despliegue) Navegadores directamente
media Imágenes subidas por usuarios, avatares Django (en tiempo de ejecución) Navegadores directamente

Ambos son públicamente legibles para que los navegadores puedan obtener archivos directamente sin pasar por Django.


Crear los buckets con Terraform

Agrega a 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"
}

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
}

Ejecuta terraform apply para crear ambos buckets.


Qué se crea

Bucket nombre URL Propósito
mycoolproject-prod-static https://storage.googleapis.com/mycoolproject-prod-static/ CSS, JS, fuentes
mycoolproject-prod-media https://storage.googleapis.com/mycoolproject-prod-media/ Subidas de usuarios

Configuración de Django

En 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: subir archivos estáticos

Antes de desplegar, ejecuta collectstatic para subir CSS, JS y fuentes a GCS:

cd web
DJANGO_SETTINGS_MODULE=core.settings.prod uv run manage.py collectstatic --noinput

Esto sube todos los archivos estáticos al bucket estático. El backend de GCS de Django maneja esto automáticamente vía la librería storages.


No hay archivos media para colectar

Los archivos media (subidas de usuarios) se suben en tiempo de ejecución cuando los usuarios envían formularios. No hay equivalente de collectstatic — Django escribe directamente a GCS vía el backend de storages.


Verificar que los buckets existen

gsutil ls

Deberías ver:

gs://mycoolproject-prod-static/
gs://mycoolproject-prod-media/