Architecture¶
See also
index | iac/terraform/explanation | iac/terraform/how-to-guides | | index#Questions
Terraform is HashiCorp's infrastructure-as-code tool that uses HCL (HashiCorp Configuration Language) to declaratively define cloud resources. The core engine parses HCL, builds a dependency graph, and orchestrates provider plugins via gRPC to reconcile desired state with real infrastructure.
Component Overview¶
graph TB
subgraph CLI["CLI Layer"]
TF["terraform CLI"]
end
subgraph CORE["Terraform Core"]
HCL["HCL Parser<br/>(hcl lang library)"]
CFG["Config Loader<br/>(module resolver)"]
GRAPH["Graph Builder<br/>(DAG construction)"]
EVAL["Evaluator<br/>(expression evaluation)"]
PLAN["Plan Engine"]
APPLY["Apply Engine"]
end
subgraph PLUGINS["Plugin System"]
PV["Provider Plugins<br/>(gRPC / protocol v5-v6)"]
PROV["Provisioner Plugins"]
end
subgraph STATE["State Layer"]
SF["State File<br/>(JSON)"]
BE["Backend<br/>(S3, GCS, Azure, consul, local)"]
WS["Workspaces"]
end
subgraph REG["Registry"]
TREG["Terraform Registry<br/>(registry.terraform.io)"]
PRIV["Private Registry<br/>(HCP Terraform)"]
end
subgraph HCPTF["HCP Terraform (optional)"]
API["Terraform Cloud API"]
SENT["Sentinel Policy"]
VCS["VCS Integration"]
end
TF --> HCL
HCL --> CFG
CFG --> GRAPH
GRAPH --> EVAL
EVAL --> PLAN
PLAN --> APPLY
APPLY --> PV
APPLY --> PROV
PLAN --> SF
APPLY --> SF
SF --> BE
WS --> SF
CFG -->|resolve modules| TREG
CFG -->|resolve modules| PRIV
TF -->|remote operations| HCPTF
Core Components¶
HCL Parser¶
The HCL parser (implemented in the hcl Go library, github.com/hashicorp/hcl/v2) handles:
- Parsing
.tffiles into an abstract syntax tree (AST). - Evaluating HCL expressions including variables, locals, conditionals,
forexpressions, and template strings. - Validating block types and attribute schemas against provider-defined schemas.
- Producing diagnostic messages with source location information.
The parser is language-agnostic -- the same library is used by other HashiCorp tools (Vault, Consul, Nomad).
Config Loader and Module System¶
The config loader resolves the full configuration from multiple sources:
- Root module: The
.tffiles in the working directory. - Child modules: Referenced via
moduleblocks, sourced from: - Local filesystem paths.
- Terraform Registry (
registry.terraform.io). - Git repositories (GitHub, GitLab, generic).
- S3 buckets, GCS buckets, and other storage backends.
- HCP Terraform private module registry.
Module versioning is handled through a lock file (.terraform.lock.hcl) that pins provider versions and checksums.
Graph Builder¶
The graph builder constructs a directed acyclic graph (DAG) that determines the order of operations:
- Nodes: Root variables, module variables, providers, resources, data sources, outputs, locals.
- Edges: Implicit dependencies detected by scanning HCL expression references, plus explicit
depends_ondeclarations. - Transformers: A series of graph transformers process the DAG:
ReferenceTransformer-- connects references to their targets.ProviderTransformer-- associates resources with their provider instances.CountBoundaryTransformer-- handlescountandfor_eachexpansion.OrphanResourceTransformer-- detects resources in state but not in config.TransitiveReductionTransformer-- removes redundant edges.
Separate graph builders for each operation phase:
- PlanGraphBuilder -- produces the plan.
- ApplyGraphBuilder -- executes planned changes.
- DestroyGraphBuilder -- reverse-order destruction.
- RefreshGraphBuilder -- updates state from real infrastructure.
- ValidateGraphBuilder -- validates configuration without planning.
Evaluator¶
The evaluator walks the graph and resolves all expressions:
- Evaluates variable values (from
.tfvarsfiles, environment variables, CLI flags). - Resolves
localsblocks in dependency order. - Expands
countandfor_eachinto individual resource instances. - Handles
lifecyclemeta-arguments (create_before_destroy,prevent_destroy,ignore_changes). - Produces the complete "desired state" that represents what the configuration declares.
Plugin Protocol¶
Terraform communicates with providers via a gRPC-based plugin protocol (protocol version 5 and 6).
Protocol Evolution¶
| Version | Transport | Introduced | Notes |
|---|---|---|---|
| v1-v4 | net/rpc (Go-specific) |
Pre-0.12 | Legacy, deprecated |
| v5 | gRPC (tfplugin5) |
Terraform 0.12 | Current standard |
| v6 | gRPC (tfplugin6) |
Terraform 1.0+ | Adds log-level control, moved types |
Plugin Handshake¶
sequenceDiagram
participant Core as Terraform Core
participant Plugin as Provider Plugin
Core->>Plugin: Launch process
Plugin-->>Core: Handshake (stdout)<br/>protocol version + gRPC address + TLS cert
Core->>Plugin: gRPC connection (mTLS)
Core->>Plugin: GetSchema
Plugin-->>Core: Resource & data source schemas
Core->>Plugin: ConfigureProvider
Plugin-->>Core: Config validated
Note over Core,Plugin: Plan phase
Core->>Plugin: PlanResourceChange (per resource)
Plugin-->>Core: Planned state + requires replacement?
Note over Core,Plugin: Apply phase
Core->>Plugin: ApplyResourceChange (per resource)
Plugin-->>Core: New state
Core->>Plugin: Close (graceful shutdown)
Plugin SDKs¶
| SDK | Status | Usage |
|---|---|---|
terraform-plugin-sdk/v2 (SDKv2) |
Mature | Most existing providers |
terraform-plugin-framework |
Recommended | New providers, protocol v6 features |
terraform-plugin-mux |
Active | Combine SDKv2 and framework in one provider |
Plan / Apply Flow¶
flowchart TD
A["terraform init"] --> B["Install providers<br/>Download modules<br/>Initialize backend"]
B --> C["terraform plan"]
C --> D["Parse HCL config"]
D --> E["Load prior state from backend"]
E --> F["Refresh: read real-world state via providers"]
F --> G["Evaluate config against refreshed state"]
G --> H["Build plan graph"]
H --> I["Compute diff per resource instance"]
I --> J["Save plan file (optional)"]
J --> K["terraform apply"]
K --> L["Load saved plan OR re-plan"]
L --> M["Build apply graph"]
M --> N["Walk graph: create / update / delete resources"]
N --> O["Update state incrementally"]
O --> P["Persist final state to backend"]
P --> Q["terraform output"]
Deterministic plans
The plan file is a binary artifact that captures the exact set of changes. When applied, Terraform guarantees that only those planned changes are executed. The plan file includes a cryptographic hash of the configuration to detect configuration drift between plan and apply.
State Management¶
State File Structure¶
The state file (terraform.tfstate) is a JSON document containing:
- Version: State format version (currently 4).
- Terraform version: Version that produced the state.
- Serial: Monotonically increasing counter for optimistic locking.
- Lineage: Unique UUID identifying the state lineage (prevents corruption from swapping state files).
- Outputs: Root module output values.
- Resources: Array of resource instances with:
type,name,provideridentifier.instances: Array of individual instances (fromcount/for_each).- Each instance has
attributes(current state),dependencies,schema_version.
Backend Options¶
| Backend | State Locking | Encryption | Notes |
|---|---|---|---|
local |
No | None | Default, terraform.tfstate on disk |
s3 |
Yes (DynamoDB) | SSE-S3, SSE-KMS | Most common AWS choice |
gcs |
Yes (native) | Google-managed or CMEK | Google Cloud |
azurerm |
Yes (blob lease) | SSE | Azure |
consul |
Yes (session) | Optional (base64) | HashiCorp Consul |
pg |
Yes (advisory lock) | Optional | PostgreSQL |
remote (HCP TF) |
Yes | Encryption at rest | Terraform Cloud managed |
oss |
Yes | SSE | Alibaba Cloud |
Workspaces¶
Workspaces provide isolated state instances within the same configuration:
- Each workspace maintains its own state file.
terraform.workspacevariable enables workspace-conditional logic.- Default workspace is always present (
default). - In HCP Terraform, workspaces map to separate workspaces with independent VCS triggers, variables, and run settings.
HCP Terraform (Terraform Cloud)¶
HCP Terraform extends the open-source CLI with managed infrastructure:
| Feature | Description |
|---|---|
| Remote operations | Plans and applies run on HashiCorp-managed infrastructure |
| VCS integration | Auto-trigger runs on GitHub, GitLab, Bitbucket pushes |
| Sentinel policies | Policy-as-code framework for governance |
| Private registry | Organizational modules and providers |
| Cost estimation | Estimated cloud spend per plan (Business tier) |
| RBAC | Team-based access control per workspace |
| SSO | SAML/SSO with major identity providers |
| API-driven workflow | Full CRUD API for workspace, run, variable management |
Comparison with Alternatives¶
| Aspect | Terraform | OpenTofu | Pulumi |
|---|---|---|---|
| Language | HCL | HCL | TS, Python, Go, .NET, Java |
| License | BSL 1.1 | MPL 2.0 | Apache 2.0 |
| Provider protocol | gRPC v5/v6 | gRPC v5/v6 (compatible) | gRPC (own protocol) |
| State encryption | Not native | Built-in (AES-GCM + KMS) | Per-secret encryption |
| Policy-as-code | Sentinel (paid) | OPA (community) | CrossGuard |
| Managed platform | HCP Terraform | Community/self-hosted | Pulumi Cloud |
References¶
- Terraform documentation
- Terraform GitHub repository
- Plugin protocol
- HCL specification
- HCP Terraform docs
How It Works¶
Provider plugin architecture, state management, plan/apply lifecycle, and dependency graph.
Core Lifecycle¶
sequenceDiagram
participant User as User
participant CLI as Terraform CLI
participant State as State File
participant Provider as Provider Plugin (gRPC)
participant Cloud as Cloud API
User->>CLI: terraform plan
CLI->>State: Read current state
CLI->>Provider: Refresh resource statuses
Provider->>Cloud: API calls to check real state
Provider-->>CLI: Current state
CLI->>CLI: Diff: desired (HCL) vs current
CLI-->>User: Execution plan (+ / ~ / -)
User->>CLI: terraform apply
CLI->>CLI: Build dependency graph (DAG)
CLI->>Provider: Create/Update/Delete resources
Provider->>Cloud: API calls
Provider-->>CLI: Resource attributes
CLI->>State: Write updated state
Dependency Graph (DAG)¶
Terraform builds a Directed Acyclic Graph of all resources. This ensures correct ordering:
flowchart TB
VPC["aws_vpc"] --> Subnet["aws_subnet"]
Subnet --> SG["aws_security_group"]
Subnet --> Instance["aws_instance"]
SG --> Instance
Instance --> EIP["aws_eip"]
style VPC fill:#ff6f00,color:#fff
Resources without dependencies are created in parallel.
Provider Plugin Architecture¶
flowchart LR
CLI_T["Terraform CLI\n(core engine)"] <-->|"gRPC"| AWS["AWS Provider\n(plugin binary)"]
CLI_T <-->|"gRPC"| GCP["GCP Provider"]
CLI_T <-->|"gRPC"| K8s_P["K8s Provider"]
CLI_T <-->|"gRPC"| Custom["Custom Provider"]
style CLI_T fill:#7b42bc,color:#fff
Each provider is a separate binary communicating via gRPC. Providers are downloaded during terraform init from the Terraform Registry.
State File¶
| Aspect | Detail |
|---|---|
| Purpose | Maps HCL resources to real-world objects |
| Format | JSON (human-readable but not for editing) |
| Locking | DynamoDB (AWS), GCS, Consul for remote |
| Sensitive data | ⚠️ Stored in plaintext (use remote backend + encryption) |
| Remote backends | S3, GCS, Azure Blob, Terraform Cloud, Consul |
Sources¶
Benchmarks¶
Scope
Performance characteristics, scaling limits, and resource consumption for Terraform.
Plan/Apply Performance¶
| State Size | Plan Time | Apply Time | Memory |
|---|---|---|---|
| 50 resources | < 5s | 1-3m | 100MB |
| 500 resources | 15-60s | 5-15m | 500MB |
| 2,000 resources | 2-10m | 15-45m | 2GB |
| 10,000 resources | 10-30m | 1-3h | 8GB+ |
Provider API Limits¶
| Provider | Rate Limit | Impact on Large Plans |
|---|---|---|
| AWS | 20-100 req/s (varies by API) | Parallelism > 10 can hit limits |
| Azure | 12,000 req/h per subscription | Large plans need throttling |
| GCP | 10-100 req/s | Moderate limitation |
Module Performance¶
| Module Count | Init Time | Plan Overhead |
|---|---|---|
| 5 | 5-10s | Negligible |
| 20 | 10-30s | 10-20% slower |
| 50+ | 30-120s | Consider splitting |
State File Benchmarks¶
| Metric | Small (< 1MB) | Medium (1-10MB) | Large (10-100MB) |
|---|---|---|---|
| Read | < 1s | 1-5s | 5-30s |
| Write | < 1s | 1-5s | 5-30s |
| Plan (full) | < 30s | 30s-5m | 5-30m |
Sourcing Status¶
Unsourced Performance Data
Do not plan capacity from these numbers. We estimated them from vendor documentation, community benchmarks, and engineering judgment. They do not represent controlled benchmarks with documented test conditions. Specific hardware configurations, software versions, and test methodologies were not recorded.
Use these figures as rough guidance only. For production capacity planning, run your own benchmarks against your specific workload and infrastructure.
Sources¶
Security¶
Authentication Model¶
Terraform itself does not authenticate users — the state backend and the cloud providers it manages handle authentication:
| Component | Authentication Method |
|---|---|
| Terraform Cloud/Enterprise | User accounts, SSO (SAML/OIDC), API tokens, Team tokens |
| AWS Provider | Static keys, IAM roles, AssumeRoleWithWebIdentity (OIDC) |
| Azure Provider | Service principals, managed identity, OIDC federated credentials |
| GCP Provider | Service accounts, Workload Identity Federation |
| State backends (S3, GCS, Azure) | Backend-specific credentials |
graph TB
subgraph Operators
Dev["Developer\n(terraform CLI)"]
VCS["VCS Webhook\n(GitHub/GitLab)"]
API["API Token\n(CI/CD)"]
end
subgraph TFCloud["Terraform Cloud / Enterprise"]
AuthN["Identity\n(SAML/OIDC/Tokens)"]
Sentinel["Sentinel\n(Policy Engine)"]
StateStore["State Storage\n(Encrypted)"]
end
subgraph Providers
AWS["AWS\n(IAM/OIDC)"]
Azure["Azure\n(MSI/OIDC)"]
GCP["GCP\n(WIF/OIDC)"]
end
Dev --> AuthN
VCS --> AuthN
API --> AuthN
AuthN --> Sentinel
Sentinel --> StateStore
Sentinel --> Providers
State File Security¶
The Terraform state file is the most critical security asset — it contains all resource IDs, computed attributes, and potentially plaintext secrets.
State File Risks¶
| Risk | Impact |
|---|---|
| Plaintext secrets | Database passwords, API keys stored in cleartext |
| Resource manipulation | Modified state can destroy or modify infrastructure |
| Sensitive outputs | IP addresses, internal DNS names, instance IDs |
| Drift concealment | Altered state masks unauthorized infrastructure changes |
Terraform Cloud State Security¶
- State encrypted at rest (AES-256) and in transit (TLS 1.2+)
- State access URLs are time-limited (25 hours) with embedded secrets
- State never written to disk during Terraform Cloud operations
- Full state versioning with rollback capability
Self-Managed Backend Hardening¶
For S3, GCS, Azure Blob backends:
terraform {
backend "s3" {
bucket = "terraform-state-prod"
key = "infra/terraform.tfstate"
region = "us-east-1"
encrypt = true
kms_key_id = "arn:aws:kms:..."
dynamodb_table = "terraform-locks"
}
}
- Enable server-side encryption with customer-managed keys
- Enable state locking (DynamoDB for S3, native for GCS/Azure)
- Restrict bucket access with IAM policies (least privilege)
- Enable versioning to recover from accidental deletion
- Enable access logging for audit trail
Sentinel Policy as Code¶
Terraform Enterprise and HCP Terraform enforce organizational policies via Sentinel:
Common Sentinel Policies¶
| Policy Category | Example |
|---|---|
| Resource restrictions | Only allow specific instance types |
| Encryption enforcement | Require KMS encryption on S3, EBS, RDS |
| Network security | Deny public S3 buckets, require security groups |
| Tagging compliance | Mandate cost-center and environment tags |
| Provider version pinning | Enforce minimum provider versions |
Sentinel enforcement levels: - Advisory: Log violations, allow apply - Soft-mandatory: Allow override with justification - Hard-mandatory: Block apply entirely
Provider Credential Management¶
Dynamic Credentials (Recommended)¶
Use OIDC federation to generate short-lived credentials instead of static keys:
| Provider | OIDC Method |
|---|---|
| AWS | AssumeRoleWithWebIdentity with Terraform Cloud OIDC |
| Azure | Workload Identity Federation |
| GCP | Workload Identity Federation |
Static Credentials (Legacy)¶
When OIDC is unavailable:
- Store credentials in environment variables, never in
.tffiles - Use HashiCorp Vault to generate short-lived credentials
- Rotate access keys on a defined schedule
- Use IAM roles with conditional trust policies (IP restrictions, MFA)
Credential Exposure Prevention¶
- Use
sensitive = trueon all outputs containing secrets - Enable
.terraform.lock.hclto pin provider versions - Verify provider checksums with
terraform providers lock
Module Registry Security¶
Module Verification¶
- Pin module versions to specific tags or commit SHAs — never use
latest - Verify module sources: prefer Terraform Registry or internal Git repos
- Use
terraform providers lockto create a dependency lock file with SHA256 checksums
Module Security Checklist¶
- Module source is from trusted registry or internal repository
- Pin the module version to a specific tag
- Provider versions are constrained with
>=and<bounds - No default credentials in variable definitions
- All sensitive variables declared with
sensitive = true
Terraform Cloud RBAC¶
| Role | Scope | Permissions |
|---|---|---|
| Organization Owner | Org | Manage users, teams, policies, billing |
| Organization Member | Org | Read org settings, create workspaces |
| Team Lead | Team | Manage team membership |
| Write | Workspace | Plan and apply |
| Read | Workspace | View state and outputs only |
| Plan | Workspace | Run plans but not applies |
| Admin | Workspace | Manage workspace settings and permissions |
Workspace Isolation¶
Each workspace in Terraform Cloud has isolated state storage, variable sets, run history, and access controls. Use separate workspaces for each environment (dev, staging, prod) with escalating RBAC restrictions.
Hardening Checklist¶
- Use Terraform Cloud/Enterprise as state backend (encrypted, audited)
- Enable state encryption on self-managed backends
- Use dynamic provider credentials (OIDC) — eliminate static keys
- Configure Sentinel policies with
hard-mandatoryenforcement - Pin all module and provider versions with lock file
- Mark all sensitive outputs with
sensitive = true - Enable SSO (SAML/OIDC) for Terraform Cloud access
- Use variable sets with
sensitiveflag for secrets - Restrict workspace access with team-level RBAC
- Enable audit logging in Terraform Enterprise
- Rotate API tokens quarterly
Known Pitfalls¶
| Pitfall | Risk | Mitigation |
|---|---|---|
| Plaintext secrets in state | Credential exposure | Use sensitive = true, encrypt state |
terraform state pull on local disk |
Unencrypted state file | Use remote backends exclusively |
| Unpinned module versions | Supply chain attacks | Pin versions, use lock file |
| Broad workspace write access | Unauthorized infrastructure changes | Least-privilege RBAC per workspace |
| Missing state locking | Concurrent apply corruption | Enable DynamoDB/native locking |
| Stale provider credentials | Compromised long-lived keys | Use OIDC dynamic credentials |