> ## Documentation Index
> Fetch the complete documentation index at: https://docs.membrane.agency/llms.txt
> Use this file to discover all available pages before exploring further.

# Google Cloud Platform

# Self-hosting on Google Cloud Platform

To self-host Membrane on Google Cloud Platform, you need to set up Google Cloud Storage.

Everything else is the same as in the [main guide](/docs/managing-membrane/self-hosting).

## Setting up Google Cloud Storage

This guide uses terraform to manage the infrastructure.

Create a service account:

```hcl theme={null}
# Create a Google Service Account for the API service
resource "google_service_account" "api_service" {
  account_id   = "${var.project}-${var.environment}-api"
  display_name = "API Service Account for ${var.environment}"
  project      = var.gcp_project_id
}

# Grant necessary permissions to the service account
resource "google_project_iam_member" "api_storage_admin" {
  project = var.gcp_project_id
  role    = "roles/storage.admin"
  member  = "serviceAccount:${google_service_account.api_service.email}"
}

# Grant token creator permission for storage signed URLs
resource "google_project_iam_member" "api_token_creator" {
  project = var.gcp_project_id
  role    = "roles/iam.serviceAccountTokenCreator"
  member  = "serviceAccount:${google_service_account.api_service.email}"
}

# Allow the Kubernetes service account to impersonate the Google service account
resource "google_service_account_iam_binding" "api_workload_identity" {
  service_account_id = google_service_account.api_service.name
  role               = "roles/iam.workloadIdentityUser"

  members = [
    "serviceAccount:${var.gcp_project_id}.svc.id.goog[${var.kubernetes_namespace}/integration-app]"
  ]
}

```

2. Create your buckets

```hcl theme={null}
# Temporary files bucket
resource "google_storage_bucket" "tmp" {
  name     = "${var.project}-${var.environment}-tmp"
  location = var.gcp_region
  project  = var.gcp_project_id

  # Force destroy even if bucket contains objects
  force_destroy = true

  # Uniform bucket-level access
  uniform_bucket_level_access = true

  # Lifecycle rule to delete objects after 7 days
  lifecycle_rule {
    condition {
      age = 7
    }
    action {
      type = "Delete"
    }
  }

  # Versioning
  versioning {
    enabled = false
  }

  # Labels
  labels = local.common_labels
}

# Connectors bucket
resource "google_storage_bucket" "connectors" {
  name     = "${var.project}-${var.environment}-connectors"
  location = var.gcp_region
  project  = var.gcp_project_id

  # Prevent accidental deletion
  force_destroy = false

  # Uniform bucket-level access
  uniform_bucket_level_access = true

  # Versioning for code safety
  versioning {
    enabled = true
  }

  # Lifecycle rule to delete old versions after 30 days
  lifecycle_rule {
    condition {
      num_newer_versions = 3
      with_state         = "ARCHIVED"
    }
    action {
      type = "Delete"
    }
  }

  # Labels
  labels = local.common_labels
}

# Static content bucket
resource "google_storage_bucket" "static" {
  name     = "${var.project}-${var.environment}-static"
  location = var.gcp_region
  project  = var.gcp_project_id

  # Force destroy for development environments
  force_destroy = var.environment != "prod"

  # Uniform bucket-level access
  uniform_bucket_level_access = true

  # Website configuration
  website {
    main_page_suffix = "index.html"
    not_found_page   = "404.html"
  }

  # CORS configuration
  cors {
    origin          = ["*"]
    method          = ["GET", "HEAD", "OPTIONS"]
    response_header = ["*"]
    max_age_seconds = 3600
  }

  # Versioning
  versioning {
    enabled = false
  }

  # Labels
  labels = local.common_labels
}

# Make static bucket publicly accessible for CDN (Optional)
resource "google_storage_bucket_iam_member" "static_public_access" {
  bucket = google_storage_bucket.static.name
  role   = "roles/storage.objectViewer"
  member = "allUsers"
}

```

3. Configure CDN for serving static files (Optional)

```hcl theme={null}
 # Reserve a global static IP for the load balancer
resource "google_compute_global_address" "cdn_ip" {
  count = var.enable_cdn ? 1 : 0

  name    = "${var.project}-${var.environment}-cdn-ip"
  project = var.gcp_project_id
}

# Backend bucket for serving static content
resource "google_compute_backend_bucket" "static_backend" {
  count = var.enable_cdn ? 1 : 0

  name        = "${var.project}-${var.environment}-static-backend"
  description = "Backend bucket for static content"
  bucket_name = google_storage_bucket.static.name
  project     = var.gcp_project_id

  # Enable CDN
  enable_cdn = true

  # CDN policy
  cdn_policy {
    cache_mode = "CACHE_ALL_STATIC"

    # Cache TTLs
    client_ttl  = 3600
    default_ttl = 3600
    max_ttl     = 86400

    # Negative caching
    negative_caching = true
    negative_caching_policy {
      code = 404
      ttl  = 120
    }

    # Cache key policy
    cache_key_policy {
      query_string_whitelist = []
    }
  }

  # Compression
  compression_mode = "AUTOMATIC"
}

# URL map for routing
resource "google_compute_url_map" "cdn_url_map" {
  count = var.enable_cdn ? 1 : 0

  name            = "${var.project}-${var.environment}-cdn-url-map"
  default_service = google_compute_backend_bucket.static_backend[0].id
  project         = var.gcp_project_id

  # Host rules for custom domains
  host_rule {
    hosts        = ["static.${var.domain_name}"]
    path_matcher = "static-paths"
  }

  # Path matchers
  path_matcher {
    name            = "static-paths"
    default_service = google_compute_backend_bucket.static_backend[0].id

    path_rule {
      paths   = ["/api/*"]
      service = google_compute_backend_bucket.static_backend[0].id
      route_action {
        url_rewrite {
          path_prefix_rewrite = "/"
        }
      }
    }
  }
}

# Managed SSL certificate
resource "google_compute_managed_ssl_certificate" "cdn_cert" {
  count = var.enable_cdn ? 1 : 0

  name    = "${var.project}-${var.environment}-cdn-cert"
  project = var.gcp_project_id

  managed {
    domains = ["static.${var.domain_name}"]
  }
}

# HTTPS proxy
resource "google_compute_target_https_proxy" "cdn_https_proxy" {
  count = var.enable_cdn ? 1 : 0

  name             = "${var.project}-${var.environment}-cdn-https-proxy"
  url_map          = google_compute_url_map.cdn_url_map[0].id
  ssl_certificates = [google_compute_managed_ssl_certificate.cdn_cert[0].id]
  project          = var.gcp_project_id
}

# HTTP proxy (for redirect to HTTPS)
resource "google_compute_target_http_proxy" "cdn_http_proxy" {
  count = var.enable_cdn ? 1 : 0

  name    = "${var.project}-${var.environment}-cdn-http-proxy"
  url_map = google_compute_url_map.cdn_http_redirect[0].id
  project = var.gcp_project_id
}

# URL map for HTTP to HTTPS redirect
resource "google_compute_url_map" "cdn_http_redirect" {
  count = var.enable_cdn ? 1 : 0

  name    = "${var.project}-${var.environment}-cdn-http-redirect"
  project = var.gcp_project_id

  default_url_redirect {
    https_redirect         = true
    redirect_response_code = "MOVED_PERMANENTLY_DEFAULT"
    strip_query            = false
  }
}

# Global forwarding rule for HTTPS
resource "google_compute_global_forwarding_rule" "cdn_https_forwarding" {
  count = var.enable_cdn ? 1 : 0

  name                  = "${var.project}-${var.environment}-cdn-https-forwarding"
  ip_protocol           = "TCP"
  load_balancing_scheme = "EXTERNAL_MANAGED"
  port_range            = "443"
  target                = google_compute_target_https_proxy.cdn_https_proxy[0].id
  ip_address            = google_compute_global_address.cdn_ip[0].id
  project               = var.gcp_project_id
}

# Global forwarding rule for HTTP
resource "google_compute_global_forwarding_rule" "cdn_http_forwarding" {
  count = var.enable_cdn ? 1 : 0

  name                  = "${var.project}-${var.environment}-cdn-http-forwarding"
  ip_protocol           = "TCP"
  load_balancing_scheme = "EXTERNAL_MANAGED"
  port_range            = "80"
  target                = google_compute_target_http_proxy.cdn_http_proxy[0].id
  ip_address            = google_compute_global_address.cdn_ip[0].id
  project               = var.gcp_project_id
}

# Cloud Armor security policy (optional, for DDoS protection)
resource "google_compute_security_policy" "cdn_security_policy" {
  count = var.enable_cdn && var.environment == "prod" ? 1 : 0

  name    = "${var.project}-${var.environment}-cdn-security-policy"
  project = var.gcp_project_id

  # Default rule
  rule {
    action   = "allow"
    priority = "2147483647"
    match {
      versioned_expr = "SRC_IPS_V1"
      config {
        src_ip_ranges = ["*"]
      }
    }
    description = "Default rule, allow all traffic"
  }

  # Rate limiting rule
  rule {
    action   = "rate_based_ban"
    priority = "1000"
    match {
      versioned_expr = "SRC_IPS_V1"
      config {
        src_ip_ranges = ["*"]
      }
    }
    rate_limit_options {
      conform_action = "allow"
      exceed_action  = "deny(429)"
      enforce_on_key = "IP"
      rate_limit_threshold {
        count        = 1000
        interval_sec = 60
      }
    }
    description = "Rate limit rule"
  }
}
```

4. Configure custom domain DNS managed zone for BASE\_STATIC\_URI (Optional)

```hcl theme={null}
# Create DNS managed zone
resource "google_dns_managed_zone" "main" {
   name        = var.dns_zone_name
   dns_name    = "${var.domain_name}."
   description = "${var.project} ${var.environment} DNS zone"
   project     = var.gcp_project_id

   labels = local.common_labels

   dnssec_config {
      state = "on"
   }
}

# DNS record for static content CDN
resource "google_dns_record_set" "static_cdn" {
   count = var.enable_cdn ? 1 : 0

   name         = "static.${google_dns_managed_zone.main.dns_name}"
   managed_zone = google_dns_managed_zone.main.name
   type         = "A"
   ttl          = 300
   project      = var.gcp_project_id

   rrdatas = [google_compute_global_address.cdn_ip[0].address]
}

# TXT record for domain verification
resource "google_dns_record_set" "domain_verification" {
   name         = google_dns_managed_zone.main.dns_name
   managed_zone = google_dns_managed_zone.main.name
   type         = "TXT"
   ttl          = 300
   project      = var.gcp_project_id

   rrdatas = [
      "\"v=spf1 -all\"",
      "\"google-site-verification=${var.google_site_verification_code}\""
   ]
}

# A record for the root domain (optional)
resource "google_dns_record_set" "root" {
   count = var.enable_cdn ? 1 : 0

   name         = google_dns_managed_zone.main.dns_name
   managed_zone = google_dns_managed_zone.main.name
   type         = "A"
   ttl          = 300
   project      = var.gcp_project_id

   rrdatas = [google_compute_global_address.cdn_ip[0].address]
}

# CAA record for SSL certificate issuance
resource "google_dns_record_set" "caa" {
   name         = google_dns_managed_zone.main.dns_name
   managed_zone = google_dns_managed_zone.main.name
   type         = "CAA"
   ttl          = 300
   project      = var.gcp_project_id

   rrdatas = [
      "0 issue \"pki.goog\"",
      "0 issue \"letsencrypt.org\""
   ]
}
```
