Infrastructure as Code Patterns: Tools, State & Multi-Account Design
Infrastructure as Code (IaC) is the practice of managing infrastructure through declarative or imperative definitions stored in version control. In 2026, the question is no longer whether to use IaC but which tool, pattern, and organizational model fits your context.
Tool Comparison
| Dimension | Terraform / OpenTofu | Pulumi | AWS CDK | Azure Bicep | Crossplane |
|---|---|---|---|---|---|
| Language | HCL | TypeScript, Python, Go, C# | TypeScript, Python, Go | Bicep DSL | YAML (K8s CRDs) |
| Paradigm | Declarative | Imperative + Declarative | Imperative → CloudFormation | Declarative | Declarative (GitOps) |
| Multi-cloud | Excellent (1000+ providers) | Good (growing) | AWS only | Azure only | Good (providers) |
| State management | Remote backend (S3, GCS, TFC) | Pulumi Cloud or self-managed | CloudFormation stack | Azure Resource Manager | etcd (K8s cluster) |
| Learning curve | Medium (HCL is unique) | Low (familiar languages) | Medium | Low (Azure devs) | High (K8s required) |
| Testing | Terratest, tftest | Native unit tests | CDK Assertions | Bicep linter | K8s testing tools |
| License (2026) | BSL (TF) / MPL (OpenTofu) | Apache 2.0 | Apache 2.0 | MIT | Apache 2.0 |
| Best for | Multi-cloud, large teams | Dev-heavy teams | AWS-native shops | Azure-native shops | K8s-centric platforms |
| Community size | Very large | Growing | Large (AWS) | Medium (Azure) | Medium |
State Management Comparison
| Strategy | Pros | Cons | Best For |
|---|---|---|---|
| Local state | Simple, no setup | No collaboration, no locking | Learning, prototyping |
| Remote backend (S3+DynamoDB) | Locking, team access, versioned | Self-managed, no UI | AWS-centric teams |
| Terraform Cloud / HCP | UI, run history, RBAC, VCS integration | Cost, vendor lock-in | Enterprise teams |
| Pulumi Cloud | Integrated with Pulumi, secrets mgmt | Tied to Pulumi ecosystem | Pulumi users |
| GitOps (Crossplane) | K8s-native, drift detection | Requires K8s cluster | Platform engineering teams |
Critical rule: State files contain secrets. Encrypt at rest, restrict access, never commit to git.
Module Design Patterns
IaC Module Patterns
├── Composition Pattern
│ ├── Root module calls child modules
│ ├── Each module = one logical resource group
│ └── Example: network module + compute module + database module
│
├── Wrapper Pattern
│ ├── Thin wrapper around a provider resource
│ ├── Enforces org standards (tags, naming, encryption)
│ └── Example: "aws-s3-bucket" module that always enables versioning
│
├── Scaffold / Template Pattern
│ ├── Full environment template
│ ├── Parameterized for dev/staging/prod
│ └── Example: "environment" module creating VPC + EKS + RDS
│
└── Library Pattern
├── Shared modules in a central registry
├── Versioned, tested, documented
└── Example: Terraform private registry or Git tags
Testing Strategy
| Test Level | What It Validates | Tool | Speed | When to Run |
|---|---|---|---|---|
| Static analysis | Syntax, best practices, security | tflint, checkov, tfsec | Seconds | Every commit (pre-commit) |
| Unit tests | Module logic, variable validation | tftest, Pulumi unit tests | Seconds | Every PR |
| Contract tests | Outputs match expected schema | Custom assertions | Seconds | Every PR |
| Integration tests | Real infra deploys and works | Terratest, kitchen-terraform | Minutes | Nightly or per PR (staging) |
| E2E / Smoke tests | Full stack is functional | Application-level tests | Minutes | Post-deploy |
| Drift detection | Actual state matches desired | terraform plan (scheduled) | Minutes | Daily (cron) |
Multi-Account Architecture
┌─────────────────────────────────────────────────────────────┐
│ AWS Organization │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Management │ │ Security │ │ Shared │ │
│ │ Account │ │ Account │ │ Services │ │
│ │ │ │ │ │ │ │
│ │ - Billing │ │ - GuardDuty │ │ - Networking │ │
│ │ - Org mgmt │ │ - Config │ │ - DNS │ │
│ │ - SSO │ │ - CloudTrail │ │ - CI/CD │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Dev │ │ Staging │ │ Production │ │
│ │ Account │ │ Account │ │ Account │ │
│ │ │ │ │ │ │ │
│ │ - Workloads │ │ - Workloads │ │ - Workloads │ │
│ │ - Sandbox │ │ - Pre-prod │ │ - Live │ │
│ │ - Relaxed │ │ - Prod-like │ │ - Strict │ │
│ │ policies │ │ policies │ │ policies │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ IaC Strategy: │
│ - Shared modules in central repo (versioned) │
│ - Per-account state files (isolated) │
│ - CI/CD deploys to dev → staging → prod │
│ - Drift detection runs daily per account │
└─────────────────────────────────────────────────────────────┘
Key Principles
Treat modules like libraries. Version them, test them, document their interfaces. Breaking changes in a shared module should follow semantic versioning.
State isolation per environment. Never share a state file between dev and prod. A bad plan in dev should never risk production resources.
Plan before apply, always. Every change goes through plan in CI. apply only happens after human review or automated policy checks (OPA/Sentinel).