Skip to content

0009 - Manage GCP Infrastructure with Terraform in the Monorepo

Status: Accepted Date: 2026-08-07 Author: Manuel Nucci

Context

FPC needs repeatable development and production infrastructure, remote state, GitHub-based delivery, and minimal duplication. The repository currently has no GCP infrastructure code. A prior infrastructure study compared separate module and live-configuration repositories with a Terragrunt variant that generated backend configuration, inherited common inputs, and coordinated many independently deployed modules.

That design is useful for multiple infrastructure teams, consumers, accounts, and independently released modules. FPC currently has one product monorepo, one delivery team, two environments, and one backend platform. Introducing separate repositories or a second orchestration language would add release coordination and another upgrade surface before it removes meaningful maintenance cost.

Decision

Manage GCP with plain Terraform in a root-level infra/ area of the existing monorepo.

  • Keep infrequently changed bootstrap roots, reusable local modules, and separate development and production environment roots under infra/.
  • Model one complete FPC backend environment as a deep local module. Its interface exposes only real environment variation, including project, region, scaling, database lifecycle, retention, origin, and storage policy.
  • Keep development and production roots deliberately small and explicit.
  • Use a separate versioned GCS backend and default Terraform CLI workspace for every environment.
  • Do not use Terraform CLI workspaces for environment isolation.
  • Keep one application-platform state per environment while the resource count and ownership remain small. Split state only when lifecycle, permissions, ownership, or state size provides a demonstrated reason.
  • Bootstrap the pre-existing state buckets and GitHub federation through a one-time trusted operator flow, then migrate bootstrap state to GCS.
  • Authenticate GitHub Actions through Workload Identity Federation with separate least-privilege identities for planning, applying, runtime deployment, and database migration as required.
  • Pin Terraform and provider versions, commit dependency lock files, and use non-secret checked-in environment inputs.
  • Have Terraform create Secret Manager resources and IAM bindings, but not secret payload versions.
  • Treat development database start and stop as an operational command outside Terraform's desired-state interface.
  • Validate formatting, configuration, module behavior, security policy, and remote plans in GitHub Actions.
  • Plan both environments when the shared module changes and only the affected environment when an environment root changes.
  • Apply development automatically from dev. Apply production only from main through a protected GitHub Environment approval.
  • Do not adopt Terragrunt, HCP Terraform Stacks, Pulumi, OpenTofu, or CDK for Terraform for the MVP.

Consequences

Positive

  • Infrastructure and application changes can be reviewed atomically in one repository.
  • One local environment module removes most duplication without a separate registry or release process.
  • Separate roots, credentials, and state make environment blast radii visible and auditable.
  • Plain Terraform aligns with existing team knowledge and current Google guidance.
  • GitHub receives short-lived GCP credentials instead of stored service-account keys.
  • A small amount of root duplication makes production safeguards explicit at the deployment seam.

Negative

  • Backend and provider declarations are repeated for development and production.
  • Initial state and federation bootstrapping cannot be fully self-hosting.
  • A shared module change requires plans for both environments.
  • The monorepo change classifier and CI workflow must understand infrastructure impact.
  • Terraform configuration is intentionally GCP-specific and does not provide a cloud-neutral infrastructure abstraction.

Mitigations

  • Keep environment roots thin; do not use generation, symlinks, or inheritance to hide security-critical differences.
  • Document the bootstrap procedure and require a clean, reviewed operator identity for the first run.
  • Run environment plans in parallel and serialize applies only where dependencies require it.
  • Preserve portability at application seams with containers, PostgreSQL, provider-neutral object keys, and injected authentication or storage adapters.
  • Reconsider Terragrunt or a hosted orchestration layer only after independently deployed roots, environments, teams, or dependency graphs create measured operational pain.