ShieldThemes Web Development
+1 (415) 555-0142 Get a quote →
← Journal/DevOps

Terraform state layout and guardrails for small teams

One giant state file works until it does not. How we split Terraform state, protect it, and review changes so a three-person team can manage infrastructure safely.

Leo Tanaka
Leo Tanaka
Head of DevOps · Sep 15, 2025 · 5 min read
Terraform state layout and guardrails for small teams

Terraform tends to arrive in a small team through one enthusiastic engineer. They write a single configuration that describes the VPC, the database, the application servers and the DNS records, run it from their laptop, and store the state file in a bucket. For six months it is wonderful. Then a second engineer runs a plan while the first is applying, or a refactor proposes to destroy the production database, and the team realizes that infrastructure as code needs the same care as application code. This is the layout and set of guardrails we use for teams of two to ten engineers.

Split state by blast radius

A single state file means every plan evaluates every resource. Plans get slow, a mistake anywhere can touch everything, and two people cannot work on unrelated changes at once. We split state along two axes: environment and component.

infra/
  modules/
    network/
    database/
    app/
  live/
    production/
      network/
      database/
      app/
      dns/
    staging/
      network/
      database/
      app/

Each folder under live has its own backend and state. The network rarely changes and is shared by everything, so it lives alone. The database changes occasionally and is the resource you least want to destroy by accident, so it lives alone too. The application layer changes most often and is safest to recreate.

Components share data through outputs read from remote state or, better, through lookups by tag or name, so that a change to one does not require applying another. The rule of thumb: if destroying it would cause data loss or a long outage, it gets its own state.

Remote state, locking and access

State must live in a remote backend with locking, never on a laptop. On AWS that means an S3 bucket with versioning and encryption plus a lock mechanism; other clouds and Terraform Cloud offer equivalents. Versioning matters more than people expect, because it is the only way to recover from a corrupted or accidentally overwritten state.

Access follows least privilege:

  • Engineers can read state and run plans for any environment.
  • Only the CI pipeline can apply to production, using a role with the permissions it needs and nothing else.
  • Break-glass credentials for manual production applies exist, are stored in a vault, and trigger an alert when used.

State files contain secrets, including database passwords and generated keys, in plain text. Treat the state bucket as sensitive as the database itself.

Modules with versions

Reusable modules keep staging and production consistent, but only if changes to a module do not silently roll into every environment at once. We version modules with git tags and pin each environment to a specific version.

module "app" {
  source = "git::ssh://git.example.internal/infra-modules.git//app?ref=v1.8.2"
  environment = "production"
}

A module change is released as a new tag, applied to staging, observed for a few days, and then promoted to production by bumping the reference. It is slower than editing in place, and that is the point.

Infrastructure changes should be boring. If an apply makes the team nervous, the process is missing a step.

Plans as pull requests

Every infrastructure change goes through a pull request, and the pipeline posts the plan output as a comment. Reviewers read the plan, not just the code, because the plan shows what will actually happen. We ask reviewers to look for four things in particular:

  1. Any resource marked for destruction or replacement, especially databases, volumes and load balancers.
  2. Changes to security groups, IAM policies or public access settings.
  3. Unexpected changes outside the component the author intended to touch, which usually signal drift.
  4. Resource counts that look wrong, for example a loop producing forty instances instead of four.

On merge, the pipeline applies the exact plan that was reviewed, not a fresh one. That removes the gap in which someone else's change could sneak in between review and apply.

For stateful resources we add prevent_destroy lifecycle rules. They are blunt, but a failed apply is far better than a deleted database. Removing the rule requires its own reviewed pull request.

Detect drift before it bites

Drift happens when someone changes infrastructure outside Terraform: a quick fix in the console during an incident, a support engineer resizing a disk. A scheduled pipeline job runs a plan against every environment nightly and alerts if anything differs from code. Most drift is harmless and gets codified the next morning. Occasionally it reveals a security group opened during debugging and never closed, which is exactly the kind of thing you want to know about.

Where small teams usually start

It is also worth deciding early how you will handle secrets that Terraform needs, such as database passwords and API keys for providers. Hard-coding them in variable files that end up in git is the most common mistake we find in audits. A better pattern is to have Terraform generate the secret, store it directly in the cloud provider's secret manager, and let the application read it at runtime. Humans never see the value, rotation becomes a code change, and nothing sensitive sits in a repository.

Most teams we work with do not need to adopt all of this in one go. A reasonable sequence over four to six weeks is: move state to a locked, versioned remote backend; split out the database; add plan comments on pull requests; move production applies to CI; then add module versioning and drift detection. Each step is useful on its own.

If you are still provisioning by hand, our infrastructure as code service can import existing resources into Terraform without rebuilding them, and teams on AWS often combine it with AWS setup and management to tidy accounts and permissions at the same time.

Put guardrails around your infrastructure

Tell us how your infrastructure is managed today, even if the honest answer is "mostly by hand", and we will propose a fixed-price plan to bring it under control. Quotes go out within 24 hours from our contact page.

Leo Tanaka
WRITTEN BY
Leo Tanaka
Leo runs our hosting and infrastructure practice — CI/CD, cloud cost, observability and the on-call rotation behind every care plan.
All articles by Leo Tanaka →
Want this on your project?
Get a fixed-price quote from a senior lead within 24 hours.
Request a quote →

Keep reading

How we shipped a support agent that resolves 62% of tickets
AI · 5 min
How we shipped a support agent that resolves 62% of tickets
What to learn in the two weeks before a website redesign
Design · 5 min
What to learn in the two weeks before a website redesign
Migrating to Shopify Plus without losing a single ranking
Shopify · 5 min
Migrating to Shopify Plus without losing a single ranking