Trovella Wiki

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:

EnvironmentBucketPrefix
sharedgs://trovella-terraform-stateshared/
prodgs://trovella-terraform-stateprod/
staginggs://trovella-terraform-statestaging/

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:

  1. The Workload Identity Federation pool that CI uses to authenticate
  2. The Artifact Registry that the build job pushes Docker images to
  3. 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.

On this page