One GCP project. Three repositories. Six state files, and no seventh: the Cloudflare zone in front of everything stays console-managed. That is the Terraform architecture behind a personal site, a honeypot, an analytics platform, and a monitoring stack. None of these services share a deploy cycle. None of them should share a blast radius.

The default path with Terraform is a single directory, a single state file, and a single terraform apply that touches everything. That works until it does not. A bad apply to a Cloud SQL configuration should not delete your Pub/Sub topics. Rotating an API key should not require replanning your DNS records. Destroying a honeypot for maintenance should not risk your analytics database. Flat Terraform couples everything, and coupling is where infrastructure breaks.

Three specific problems drove this architecture. First, blast radius: a single state file means a single point of failure for every resource in the project. Second, secrets in state. Terraform's sensitive = true attribute hides values from terminal output but writes them to the state file in plaintext. Every third-party API key you manage through Terraform is one gsutil cat away from exposure. Third, IAM sprawl. The default GCP service account has Editor permissions on the entire project. A compromised container can do anything the project allows.

Layering solves each problem with a structural constraint. Independent state files limit blast radius to a single operational domain. A split secret strategy (CLI-populated containers for external credentials, Terraform-managed versions for system-generated passwords) keeps third-party keys out of state entirely. Per-service accounts with narrowly scoped role grants mean a compromised site container can publish to one Pub/Sub topic and nothing else.

The cross-layer wiring uses terraform_remote_state with a defaults block pattern that allows layers to deploy in any order after the foundation exists. Outputs from one layer become the read-only inputs to the next. The dependency direction is always downward: platform to services to site, never reversed. The Cloudflare layer sits outside the GCP dependency graph entirely; it only needs to know hostnames.

The practical result: each layer plans and applies in under 30 seconds. A secret rotation does not trigger a 15-minute Cloud SQL replanning. Tearing down the honeypot takes one terraform destroy in a single directory while the site keeps running. Every secret has a named accessor, so Cloud Audit Logs answer the question of who read what and when. I run six Terraform layers across two repos, with a third repo as the record for the console-managed Cloudflare zone, but the primitives are the same ones a team would use across fifty services and five projects. The boundaries scale. The architecture does not change.

This is what I run in production. It is not a reference design or a best-practices document. It is the outcome of hitting every failure mode flat Terraform creates and deciding each one was worth preventing.