Multi-account AWS environments become easier to operate when account boundaries, reusable modules, and environment configuration are explicit. The structure below separates reusable infrastructure from account-specific composition.
Keep the repository predictable
infra/
|-- modules/
| |-- vpc/
| |-- ecs-cluster/
| `-- rds/
|-- environments/
| |-- dev/
| |-- staging/
| `-- prod/
|-- accounts/
| |-- landing-zone.hcl
| |-- security.hcl
| `-- shared-services.hcl
`-- terragrunt.hclSeparate policy from implementation
Terraform modules define what can be created. Terragrunt supplies account, region, and environment inputs while keeping shared configuration in one place. Changes then remain small, reviewable, and consistent across accounts.
Make account boundaries explicit
Each account should have a clear purpose, owner, logging destination, and access path. Organization policies protect the boundaries, while cross-account roles provide narrowly scoped automation. Avoid a shared administrator role that quietly becomes the real architecture.
locals {
account_name = "production"
region = "eu-west-1"
}
inputs = {
account_name = local.account_name
region = local.region
}Design state as a security boundary
Remote state needs encryption, locking, retention, and a narrowly scoped access policy. Separate state by account and environment so a development pipeline cannot casually read or modify production outputs.
Terraform state can contain sensitive values. Treat the state bucket and locking mechanism as production infrastructure, with logging and access review of their own.
Make plans reviewable
Pull requests should show formatting, validation, security checks, and a plan for each affected account. Reviewers need to see resource destruction, IAM changes, network changes, and data movement clearly instead of searching through raw pipeline logs.
Handle drift and exceptions
Scheduled plans identify drift, but not every difference should be overwritten automatically. Investigate why the resource changed, record an approved exception when needed, and bring the configuration back to the intended state deliberately.
Use a promotion path
Promote the same module version through development, staging, and production with environment-specific inputs. Pin provider versions, test upgrades in a disposable account, and keep rollback options visible before applying a change.
- One owner and purpose for every account
- Encrypted and locked remote state
- Scoped cross-account roles
- Plan output attached to every infrastructure change
- Automated drift detection and exception tracking
- Provider and module upgrades tested before production
Build safety into delivery
Use remote state with locking, explicit cross-account roles, plan approval, and automated validation. Test the module contract before applying changes to production accounts.
Make the promotion path visible
Keep account bootstrap separate from application stacks, pin module and provider versions, and pass the target account and region explicitly into each run. Promote the same reviewed module revision through environments; do not quietly re-render a different configuration for production. A plan should identify the account, state key, role, and expected blast radius before approval. DRY is useful when it removes copy-paste, not when it makes a reviewer solve a treasure hunt.
The goal is not to make every account identical. It is to make the differences intentional enough that a tired reviewer can still see them.
A reviewability test for infrastructure design
DRY should reduce repeated decisions, not hide important differences. Keep security and ownership boundaries visible in the configuration.