Terraform Structure
Directory-per-environment layout, module organization, GCS backend, provider versions, and the apply sequence.
Trovella's infrastructure is managed by Terraform with a directory-per-environment layout. Each environment has its own state file, provider configuration, and module invocations.
Directory Layout
infra/
environments/
shared/ main.tf, variables.tf, versions.tf, outputs.tf
prod/ main.tf, variables.tf, versions.tf, outputs.tf
staging/ main.tf, variables.tf, versions.tf, outputs.tf
modules/
cloud-sql/ main.tf (instance, database, user, outputs)
compute-vm/ main.tf, variables.tf, outputs.tf, monitoring.tf, startup.sh
secret-manager/ main.tf (for_each secret creation)
wif/ main.tf (pool, provider, SA, cross-project IAM)
project-services/ (empty -- placeholder)
budget/ (empty -- placeholder)
GCS Remote Backend
All environments share a single GCS bucket with per-environment prefixes:
| Environment | Bucket | Prefix |
|---|---|---|
| shared | gs://trovella-terraform-state | shared/ |
| prod | gs://trovella-terraform-state | prod/ |
| staging | gs://trovella-terraform-state | staging/ |
The bucket has object versioning enabled, providing a safety net for state corruption. It lives in the trovella-shared project, separate from the resources it manages.
backend "gcs" {
bucket = "trovella-terraform-state"
prefix = "prod"
}
Provider Versions
All environments require Terraform >= 1.5 and use the Google provider ~> 6.0:
terraform {
required_version = ">= 1.5"
required_providers {
google = {
source = "hashicorp/google"
version = "~> 6.0"
}
google-beta = {
source = "hashicorp/google-beta"
version = "~> 6.0"
}
}
}
The google-beta provider is configured in prod and staging but not shared (shared resources do not need beta features). The random provider (~> 3.6) is used in prod for generating the Cloud SQL database password.
Apply Sequence
Environments must be applied in dependency order:
shared --> prod --> staging
shared must be applied first because it bootstraps:
- The Workload Identity Federation pool that CI uses to authenticate
- The Artifact Registry that the build job pushes Docker images to
- The service account with cross-project IAM grants
After shared is applied, prod and staging can be applied in either order (they are independent of each other).
Module Inventory
modules/cloud-sql
Creates a Cloud SQL PostgreSQL 18 instance with:
- Enterprise edition, ZONAL availability (no HA for cost savings)
- 10GB PD-SSD with autoresize
- SSL mode
ENCRYPTED_ONLY - Authorized networks restricted to the VM static IP
- Daily backups with 7-day retention and PITR
- Sunday 06:00 UTC maintenance window
- Deletion protection enabled
- Database user with random 32-character password
Used by: environments/prod
modules/compute-vm
Creates a Compute Engine VM with:
- Static external IP
- Firewall rules (HTTP/HTTPS + IAP SSH)
- Service account with scoped IAM roles
- Cloud Monitoring alerts and uptime check
- Startup script for Docker and Ops Agent installation
Used by: environments/prod (staging deferred)
modules/secret-manager
Creates empty Secret Manager secrets using for_each over a list of secret names. Values are never stored in Terraform -- they are set manually or via CI.
Used by: environments/prod, environments/staging
modules/wif
Creates the Workload Identity Federation pool, OIDC provider, service account, and cross-project IAM grants for GitHub Actions.
Used by: environments/shared
modules/project-services and modules/budget
Empty placeholder directories. Service enablement is handled inline by the modules that need each API. Budget alerts are configured manually in the GCP Console (not yet in Terraform).
Labels
All resources use a standard label set defined in each environment's locals block:
locals {
labels = {
app = "trovella"
env = var.environment # "prod", "staging", or "shared"
managed-by = "terraform"
}
}
These labels are passed to every module and applied to all resources that support labeling. They enable cost attribution and resource filtering in the GCP Console.
Common Operations
Plan Changes
cd infra/environments/prod
terraform plan
Apply Changes
cd infra/environments/prod
terraform apply
View Current State
cd infra/environments/prod
terraform show
Import Existing Resources
If a resource was created manually and needs to be brought under Terraform management:
terraform import google_project_service.sqladmin trovella-prod/sqladmin.googleapis.com
IPv6 Gotcha (Windows)
Terraform's Go runtime preferentially uses IPv6, which fails on networks with flaky IPv6 connectivity. If terraform init repeatedly times out, the workaround is to prefer IPv4 at the OS level. This is documented in the Environment Strategy Decision risks section.