# ── Cloud Run v2 Services ───────────────────────────────────────────────────── # Three prod regions + one stage region. # All services use INGRESS_TRAFFIC_ALL so the global LB can reach them. # Secrets are mounted as env vars via Secret Manager volume references. locals { run_labels = { "managed-by" = "terraform" "project" = "neuron-marketing" } } # ── Prod — us-central1 ──────────────────────────────────────────────────────── resource "google_cloud_run_v2_service" "prod_us" { name = "marketing-prod-us" location = "us-central1" project = var.project_id # Allow the global HTTP(S) load balancer to reach this service ingress = "INGRESS_TRAFFIC_ALL" labels = local.run_labels template { service_account = google_service_account.marketing.email scaling { min_instance_count = 1 max_instance_count = 100 } containers { image = local.image resources { limits = { cpu = "2" memory = "1Gi" } # cpu_idle = false keeps CPU allocated at all times (no throttling between requests) cpu_idle = false } # ── Static env vars ─────────────────────────────────────────────────── env { name = "NODE_ENV" value = "production" } env { name = "STRIPE_PUBLISHABLE_KEY" value = local.stripe_publishable_key } env { name = "GCS_SHARE_BUCKET" value = "neuron-shares-prod" } env { name = "SUPABASE_ANON_KEY" value = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9jb2pzZ2hhb25sdHVuaWRrenB3Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3Nzc2NDIxNjgsImV4cCI6MjA5MzIxODE2OH0.e0FVFw1aahnrBVvnkR5R8a-RxCx095U8o_gsk7Quq3E" } env { name = "NEURON_ORIGIN" value = "https://neurontechnologies.ai" } env { name = "NEURON_LLM_0_FORMAT" value = "anthropic" } env { name = "NEURON_LLM_0_MODEL" value = "claude-sonnet-4-5" } env { name = "NEURON_LLM_0_URL" value = "https://api.anthropic.com/v1/messages" } # ── Secret env vars from Secret Manager ─────────────────────────────── env { name = "SUPABASE_SERVICE_KEY" value_source { secret_key_ref { secret = "supabase-service-key" version = "latest" } } } env { name = "NEURON_LLM_0_KEY" value_source { secret_key_ref { secret = "anthropic-api-key" version = "latest" } } } env { name = "ANTHROPIC_API_KEY" value_source { secret_key_ref { secret = "anthropic-api-key" version = "latest" } } } env { name = "STRIPE_SECRET_KEY" value_source { secret_key_ref { secret = "stripe-secret-key" version = "latest" } } } env { name = "STRIPE_WEBHOOK_SECRET" value_source { secret_key_ref { secret = "stripe-webhook-secret" version = "latest" } } } env { name = "STRIPE_PRICE_PROFESSIONAL" value_source { secret_key_ref { secret = "stripe-price-professional" version = "latest" } } } env { name = "STRIPE_PRICE_FOUNDING" value_source { secret_key_ref { secret = "stripe-price-founding" version = "latest" } } } env { name = "STRIPE_PRICE_FAMILY_CHILD" value_source { secret_key_ref { secret = "stripe-price-family-child" version = "latest" } } } env { name = "RESEND_API_KEY" value_source { secret_key_ref { secret = "resend-api-key" version = "latest" } } } env { name = "DOCUSEAL_WEBHOOK_TOKEN" value_source { secret_key_ref { secret = "docuseal-webhook-token" version = "latest" } } } ports { container_port = 8080 name = "http1" } startup_probe { http_get { path = "/api/health" port = 8080 } initial_delay_seconds = 10 timeout_seconds = 5 period_seconds = 10 failure_threshold = 10 } liveness_probe { http_get { path = "/api/health" port = 8080 } initial_delay_seconds = 15 timeout_seconds = 5 period_seconds = 30 failure_threshold = 3 } } max_instance_request_concurrency = 1000 } traffic { type = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST" percent = 100 } depends_on = [google_project_iam_member.marketing_secret_accessor] } # ── Prod — europe-west1 ─────────────────────────────────────────────────────── resource "google_cloud_run_v2_service" "prod_eu" { name = "marketing-prod-eu" location = "europe-west1" project = var.project_id ingress = "INGRESS_TRAFFIC_ALL" labels = local.run_labels template { service_account = google_service_account.marketing.email scaling { min_instance_count = 1 max_instance_count = 100 } containers { image = local.image resources { limits = { cpu = "2" memory = "1Gi" } cpu_idle = false } env { name = "NODE_ENV" value = "production" } env { name = "STRIPE_PUBLISHABLE_KEY" value = local.stripe_publishable_key } env { name = "GCS_SHARE_BUCKET" value = "neuron-shares-prod" } env { name = "SUPABASE_ANON_KEY" value = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9jb2pzZ2hhb25sdHVuaWRrenB3Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3Nzc2NDIxNjgsImV4cCI6MjA5MzIxODE2OH0.e0FVFw1aahnrBVvnkR5R8a-RxCx095U8o_gsk7Quq3E" } env { name = "NEURON_ORIGIN" value = "https://neurontechnologies.ai" } env { name = "NEURON_LLM_0_FORMAT" value = "anthropic" } env { name = "NEURON_LLM_0_MODEL" value = "claude-sonnet-4-5" } env { name = "NEURON_LLM_0_URL" value = "https://api.anthropic.com/v1/messages" } env { name = "SUPABASE_SERVICE_KEY" value_source { secret_key_ref { secret = "supabase-service-key" version = "latest" } } } env { name = "NEURON_LLM_0_KEY" value_source { secret_key_ref { secret = "anthropic-api-key" version = "latest" } } } env { name = "ANTHROPIC_API_KEY" value_source { secret_key_ref { secret = "anthropic-api-key" version = "latest" } } } env { name = "STRIPE_SECRET_KEY" value_source { secret_key_ref { secret = "stripe-secret-key" version = "latest" } } } env { name = "STRIPE_WEBHOOK_SECRET" value_source { secret_key_ref { secret = "stripe-webhook-secret" version = "latest" } } } env { name = "STRIPE_PRICE_PROFESSIONAL" value_source { secret_key_ref { secret = "stripe-price-professional" version = "latest" } } } env { name = "STRIPE_PRICE_FOUNDING" value_source { secret_key_ref { secret = "stripe-price-founding" version = "latest" } } } env { name = "STRIPE_PRICE_FAMILY_CHILD" value_source { secret_key_ref { secret = "stripe-price-family-child" version = "latest" } } } env { name = "RESEND_API_KEY" value_source { secret_key_ref { secret = "resend-api-key" version = "latest" } } } env { name = "DOCUSEAL_WEBHOOK_TOKEN" value_source { secret_key_ref { secret = "docuseal-webhook-token" version = "latest" } } } ports { container_port = 8080 name = "http1" } startup_probe { http_get { path = "/api/health" port = 8080 } initial_delay_seconds = 10 timeout_seconds = 5 period_seconds = 10 failure_threshold = 10 } liveness_probe { http_get { path = "/api/health" port = 8080 } initial_delay_seconds = 15 timeout_seconds = 5 period_seconds = 30 failure_threshold = 3 } } max_instance_request_concurrency = 1000 } traffic { type = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST" percent = 100 } depends_on = [google_project_iam_member.marketing_secret_accessor] } # ── Prod — asia-northeast1 ──────────────────────────────────────────────────── resource "google_cloud_run_v2_service" "prod_apac" { name = "marketing-prod-apac" location = "asia-northeast1" project = var.project_id ingress = "INGRESS_TRAFFIC_ALL" labels = local.run_labels template { service_account = google_service_account.marketing.email scaling { min_instance_count = 1 max_instance_count = 100 } containers { image = local.image resources { limits = { cpu = "2" memory = "1Gi" } cpu_idle = false } env { name = "NODE_ENV" value = "production" } env { name = "STRIPE_PUBLISHABLE_KEY" value = local.stripe_publishable_key } env { name = "GCS_SHARE_BUCKET" value = "neuron-shares-prod" } env { name = "SUPABASE_ANON_KEY" value = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9jb2pzZ2hhb25sdHVuaWRrenB3Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3Nzc2NDIxNjgsImV4cCI6MjA5MzIxODE2OH0.e0FVFw1aahnrBVvnkR5R8a-RxCx095U8o_gsk7Quq3E" } env { name = "NEURON_ORIGIN" value = "https://neurontechnologies.ai" } env { name = "NEURON_LLM_0_FORMAT" value = "anthropic" } env { name = "NEURON_LLM_0_MODEL" value = "claude-sonnet-4-5" } env { name = "NEURON_LLM_0_URL" value = "https://api.anthropic.com/v1/messages" } env { name = "SUPABASE_SERVICE_KEY" value_source { secret_key_ref { secret = "supabase-service-key" version = "latest" } } } env { name = "NEURON_LLM_0_KEY" value_source { secret_key_ref { secret = "anthropic-api-key" version = "latest" } } } env { name = "ANTHROPIC_API_KEY" value_source { secret_key_ref { secret = "anthropic-api-key" version = "latest" } } } env { name = "STRIPE_SECRET_KEY" value_source { secret_key_ref { secret = "stripe-secret-key" version = "latest" } } } env { name = "STRIPE_WEBHOOK_SECRET" value_source { secret_key_ref { secret = "stripe-webhook-secret" version = "latest" } } } env { name = "STRIPE_PRICE_PROFESSIONAL" value_source { secret_key_ref { secret = "stripe-price-professional" version = "latest" } } } env { name = "STRIPE_PRICE_FOUNDING" value_source { secret_key_ref { secret = "stripe-price-founding" version = "latest" } } } env { name = "STRIPE_PRICE_FAMILY_CHILD" value_source { secret_key_ref { secret = "stripe-price-family-child" version = "latest" } } } env { name = "RESEND_API_KEY" value_source { secret_key_ref { secret = "resend-api-key" version = "latest" } } } env { name = "DOCUSEAL_WEBHOOK_TOKEN" value_source { secret_key_ref { secret = "docuseal-webhook-token" version = "latest" } } } ports { container_port = 8080 name = "http1" } startup_probe { http_get { path = "/api/health" port = 8080 } initial_delay_seconds = 10 timeout_seconds = 5 period_seconds = 10 failure_threshold = 10 } liveness_probe { http_get { path = "/api/health" port = 8080 } initial_delay_seconds = 15 timeout_seconds = 5 period_seconds = 30 failure_threshold = 3 } } max_instance_request_concurrency = 1000 } traffic { type = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST" percent = 100 } depends_on = [google_project_iam_member.marketing_secret_accessor] }