diff --git a/infra/env/prod/main.tf b/infra/env/prod/main.tf index ac7fc1b..9b99ad5 100644 --- a/infra/env/prod/main.tf +++ b/infra/env/prod/main.tf @@ -70,6 +70,23 @@ module "sluice" { extra_providers = var.extra_providers enable_router_shim = var.enable_router_shim router_shim_tokenutil_package = var.router_shim_tokenutil_package + + custom_domains = var.custom_domains +} + +# Adopt the out-of-band custom-domain binding for sluice.phoenixvc.tech, +# created 2026-07-03 via `az containerapp hostname add` + `az containerapp +# hostname bind --validation-method CNAME` (Azure-managed certificate +# mc-pvc-prod-sluic-sluice-phoenixvc-1673). The first apply after this lands +# should report "1 to import, 0 to add/change/destroy" for this resource; +# once the binding is in state the block is a no-op and may be removed. +# +# Note: the static segment is `containerApps` (capital A) — the azurerm v4 +# ID parser is case-sensitive and rejects the all-lowercase form that +# `az containerapp show --query id` returns. +import { + to = module.sluice.azurerm_container_app_custom_domain.custom["sluice.phoenixvc.tech"] + id = "/subscriptions/bb4e3882-2079-4bab-8974-611bc0b8bb58/resourceGroups/pvc-prod-sluice-rg/providers/Microsoft.App/containerApps/pvc-prod-sluice-ca/customDomainName/sluice.phoenixvc.tech" } module "state_service" { diff --git a/infra/env/prod/terraform.tfvars b/infra/env/prod/terraform.tfvars index ba8cff5..3d9922c 100644 --- a/infra/env/prod/terraform.tfvars +++ b/infra/env/prod/terraform.tfvars @@ -65,3 +65,9 @@ enable_litellm_db = true # # Phase 3 — Kimi K2 direct Moonshot path (optional; Groq path uses GROQ_API_KEY above) # moonshot = { api_key = "sk-...", env_name = "MOONSHOT_API_KEY" } # } + +# Custom domain for the gateway. DNS (CNAME + asuid TXT) lives in the +# org-owned phoenixvc.tech zone in `mys-global-shared-rg` (a different +# subscription than sluice) and is intentionally not managed by this stack. +# The Azure-managed certificate was bound out-of-band on 2026-07-03. +custom_domains = ["sluice.phoenixvc.tech"] diff --git a/infra/env/prod/variables.tf b/infra/env/prod/variables.tf index 5791dea..9b63f39 100644 --- a/infra/env/prod/variables.tf +++ b/infra/env/prod/variables.tf @@ -329,3 +329,9 @@ variable "extra_providers" { default = {} sensitive = true } + +variable "custom_domains" { + type = list(string) + description = "Pre-verified custom hostnames to bind to the gateway Container App. Each hostname must already have DNS in place (CNAME + asuid TXT in the owning zone) and an Azure-managed certificate provisioned out-of-band via `az containerapp hostname add` + `az containerapp hostname bind` before being listed here." + default = [] +} diff --git a/infra/modules/sluice_aca/main.tf b/infra/modules/sluice_aca/main.tf index b45f301..57fe5d8 100644 --- a/infra/modules/sluice_aca/main.tf +++ b/infra/modules/sluice_aca/main.tf @@ -1017,3 +1017,26 @@ resource "azurerm_container_app" "ca" { } } } + +# Custom domain bindings (e.g. sluice.phoenixvc.tech). +# +# DNS is intentionally NOT managed here: the CNAME + `asuid.` TXT +# records for phoenixvc.tech live in the org-owned DNS zone in +# `mys-global-shared-rg` (a different subscription / ownership boundary than +# sluice). Records must exist — and the Azure-managed certificate must be +# provisioned via `az containerapp hostname add` + `az containerapp hostname +# bind --validation-method CNAME` — before a hostname is added to +# var.custom_domains. +resource "azurerm_container_app_custom_domain" "custom" { + for_each = toset(var.custom_domains) + name = each.value + container_app_id = azurerm_container_app.ca.id + + # Azure-managed certificate: provisioned out-of-band by + # `az containerapp hostname bind`. azurerm cannot manage managed + # certs directly; per provider docs these attributes must be + # ignored or every plan tries to unbind the certificate. + lifecycle { + ignore_changes = [certificate_binding_type, container_app_environment_certificate_id] + } +} diff --git a/infra/modules/sluice_aca/outputs.tf b/infra/modules/sluice_aca/outputs.tf index ada712e..d300da1 100644 --- a/infra/modules/sluice_aca/outputs.tf +++ b/infra/modules/sluice_aca/outputs.tf @@ -44,3 +44,8 @@ output "application_insights_name" { description = "Application Insights resource name. Retrieve connection string from Key Vault secret 'appinsights-connection-string'." value = azurerm_application_insights.ai.name } + +output "custom_domain_fqdns" { + description = "Custom hostnames bound to the gateway Container App (empty when none configured)." + value = [for d in azurerm_container_app_custom_domain.custom : d.name] +} diff --git a/infra/modules/sluice_aca/variables.tf b/infra/modules/sluice_aca/variables.tf index 32d1b91..8f8cd29 100644 --- a/infra/modules/sluice_aca/variables.tf +++ b/infra/modules/sluice_aca/variables.tf @@ -316,3 +316,11 @@ variable "otel_service_name" { description = "OpenTelemetry service name for tracing." default = "ai-gateway" } + +# Custom domain bindings — see the azurerm_container_app_custom_domain +# resource in main.tf for the out-of-band provisioning contract. +variable "custom_domains" { + type = list(string) + description = "Pre-verified custom hostnames to bind to the gateway Container App. Each hostname must already have DNS in place (CNAME + asuid TXT in the owning zone) and an Azure-managed certificate provisioned out-of-band via `az containerapp hostname add` + `az containerapp hostname bind` before being listed here." + default = [] +}