---
title: Terraform on AWS
description: Terraform + AWS in 2026: Stacks GA, ephemeral values, provider-defined functions, Test Framework, OpenTofu 1.8 encryption — vs CDK and CloudFormation.
url: https://www.factualminds.com/integrations/terraform-aws/
category: iac
updated: 2026-04-29
---

# Terraform on AWS

> Deploy AWS infrastructure reliably with Terraform — Stacks, ephemeral values, provider-defined functions, Test Framework, and OpenTofu state encryption for teams that need an OSI-licensed alternative.

## Terraform on AWS in 2026

Terraform is still the default IaC tool on AWS for enterprise teams. What changed over the last two years is (a) Terraform Stacks going GA on HCP Terraform, (b) ephemeral values and ephemeral resources finally removing plaintext secrets from state files, (c) provider-defined functions cleaning up a lot of HCL gymnastics, (d) a production-ready Test Framework, and (e) OpenTofu maturing as a credible OSI-licensed alternative with client-side state encryption.

This page is a working guide to the 2026 configuration we ship.

> **Licensing in one paragraph**: Terraform moved from MPL 2.0 to the Business Source License (BUSL 1.1) in August 2023. **IBM completed its acquisition of HashiCorp in early 2025**; Terraform and HCP Terraform continue under the HashiCorp brand inside IBM Software. **OpenTofu** is the Linux Foundation / CNCF fork on MPL 2.0 — functionally compatible with Terraform for most AWS workflows, increasingly diverging on advanced features (Stacks, Sentinel). Pick based on license policy, HCP usage, and roadmap needs.

## What's new for Terraform on AWS in 2026

- **Terraform Stacks (GA on HCP Terraform, 2025)** — coordinated multi-configuration deployments across accounts and regions.
- **Ephemeral values (1.10+) and ephemeral resources (1.11+)** — never land in state.
- **Provider-defined functions (1.8+)** — cleaner HCL ergonomics.
- **Test Framework maturity (1.6 → 1.10+)** — production-ready module testing.
- **S3-native state locking (1.10+)** — DynamoDB lock table now optional.
- **Provider-defined validators** and richer CLI diagnostics.
- **HCP Terraform** — Stacks, Sentinel, OIDC to AWS, private registry, audit.
- **OpenTofu 1.8+** — client-side state encryption, early provider innovations, community governance.
- **AWS provider v6** — continued coverage for new AWS services (Bedrock, S3 Tables, EKS Auto Mode, VPC Lattice, Verified Access).

## Why Terraform on AWS

- **Reproducibility** — the same module deploys identical infra in every account and region.
- **Governance** — Sentinel / OPA / HCP Terraform policy enforcement on every plan.
- **Scalability** — modules and Stacks let one platform team serve dozens of product teams.
- **Drift detection** — nightly plans catch console drift before auditors do.
- **Ecosystem** — richest provider ecosystem of any IaC tool; community modules cover most AWS services.

## Account and state architecture (landing-zone first)

- **AWS Organizations + Control Tower** owns account creation and baseline guardrails.
- **IAM Identity Center** replaces long-lived IAM users; Terraform runs via OIDC with short-lived credentials.
- **One AWS account per environment × workload** (or per business unit × environment).
- **One state key per (account, workload)** in an S3 backend inside that account.
- **S3 state bucket**: versioning on, KMS CMK encryption, Block Public Access, TLS-only bucket policy, Object Lock where compliance mandates it.
- **Locking**: S3-native (Terraform 1.10+) or DynamoDB lock table.
- **Cross-workspace references**: Terraform Stacks, not shared state files.

## Core Terraform concepts (refresher)

- **Providers** — `hashicorp/aws` for AWS APIs; pin with `~> 6.0` (or whatever current major) and upgrade deliberately.
- **Resources / data sources** — managed vs read-only views of infra.
- **Ephemeral values and resources** — new in 1.10+, never persisted.
- **Modules** — small, focused, versioned via the registry or a private registry on HCP Terraform.
- **Stacks** — HCP Terraform, coordinate multiple configs.
- **State** — remote, encrypted, locked, never in Git.

## Example: remote state with S3-native locking

```hcl
terraform {
  required_version = ">= 1.10"

  backend "s3" {
    bucket       = "acme-prod-tfstate"
    key          = "platform/eks/terraform.tfstate"
    region       = "eu-west-1"
    encrypt      = true
    kms_key_id   = "arn:aws:kms:eu-west-1:111122223333:key/…"
    use_lockfile = true
  }

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }
}
```

## Example: ephemeral value for a bootstrap password

```hcl
ephemeral "random_password" "bootstrap" {
  length  = 32
  special = true
}

resource "aws_secretsmanager_secret_version" "initial" {
  secret_id     = aws_secretsmanager_secret.db.id
  secret_string = ephemeral.random_password.bootstrap.result
  lifecycle {
    ignore_changes = [secret_string]
  }
}
```

The generated password exists during the apply, writes into Secrets Manager, and is then gone — not in state, not in the plan, not in logs.

## Example: provider-defined function

```hcl
locals {
  arn_parts = provider::aws::arn_parse(aws_s3_bucket.this.arn)
  account_id = local.arn_parts.account_id
}
```

No more regexes over ARNs.

## Terraform Stacks (HCP Terraform)

- Define components (configurations) and deployments (targets).
- Reference components from each other with typed outputs.
- Deploy an entire environment — VPC → EKS → workloads → observability — in one orchestrated apply.
- Roll out changes across environments with deployment strategies (percentage, wave, approval gates).
- Sentinel policies gate every step.

Use when you have three or more coupled configurations deploying together. For single-state workloads, plain workspaces are still fine.

## Test Framework

```hcl
# tests/s3.tftest.hcl
run "s3_bucket_is_private" {
  command = plan

  assert {
    condition     = aws_s3_bucket_public_access_block.this.block_public_acls == true
    error_message = "S3 bucket must block public ACLs"
  }

  assert {
    condition     = aws_s3_bucket_server_side_encryption_configuration.this.rule[0].apply_server_side_encryption_by_default.sse_algorithm == "aws:kms"
    error_message = "S3 bucket must be encrypted with KMS"
  }
}
```

Run in CI on every PR. Pair with `tflint`, `tfsec` / `checkov`, and Sentinel (HCP) or OPA.

## OpenTofu 1.8+

- **Client-side state encryption** — encrypt state at rest with keys OpenTofu never sends to its backend.
- **Early provider work** and its own registry.
- **Governance**: Linux Foundation / CNCF.
- **Compatibility**: tracks Terraform core features where licensing and design allow; Stacks and Sentinel are Terraform-only.

Adopt OpenTofu when OSI licensing is non-negotiable or you want client-side state encryption today.

## Terraform vs AWS CDK vs CloudFormation (quick matrix)

| Dimension       | Terraform                              | AWS CDK                                  | CloudFormation / SAM      | Pulumi                                   |
| --------------- | -------------------------------------- | ---------------------------------------- | ------------------------- | ---------------------------------------- |
| Language        | HCL                                    | TS / Python / Java / .NET / Go           | YAML / JSON               | TS / Python / Go / .NET                  |
| Scope           | Multi-cloud                            | AWS-only (CloudFormation under the hood) | AWS-only                  | Multi-cloud                              |
| State           | External (S3 / HCP)                    | CloudFormation                           | CloudFormation            | External                                 |
| Policy engine   | Sentinel / OPA                         | Custom Aspects                           | CloudFormation Guard      | CrossGuard / OPA                         |
| Drift detection | `terraform plan`                       | CloudFormation Drift                     | CloudFormation Drift      | `pulumi preview`                         |
| Ecosystem       | Largest                                | Growing                                  | Native AWS coverage       | Growing                                  |
| Good fit        | Enterprise multi-cloud, HCP governance | AWS-only dev teams wanting TypeScript    | Minimal-tooling AWS shops | Teams wanting CDK-style with multi-cloud |

## Failure modes & resilience

**1. AWS provider rate limits / throttling.** `hashicorp/aws` calls the AWS API like any other client; large `apply` runs hit `Throttling`, `RequestLimitExceeded`, or `TooManyRequestsException` on Organizations, IAM, and Lambda APIs. Configure exponential backoff via `max_retries` and split monolithic state files:

```hcl
provider "aws" {
  region      = var.aws_region
  max_retries = 25 # default 25; bump only for known burst-heavy workloads
  default_tags {
    tags = local.common_tags
  }
}
```

For Organizations / Control Tower management, throttle parallelism: `terraform apply -parallelism=5` (default 10). Apply runs against AWS Organizations APIs are particularly sensitive.

**2. Partial-apply rollback.** Terraform does not roll back on partial failure — the failed resource is left as-is, and state reflects what succeeded. Recovery: read the error, fix the cause, re-run `apply`. For the high-stakes case where you cannot tolerate forward recovery, gate apply behind a `taint`/`untaint` workflow and add `prevent_destroy` lifecycle blocks on critical infra (RDS, KMS keys, S3 buckets with Object Lock).

**3. State corruption recovery.** S3 versioning is your safety net. Recovery flow: `aws s3api list-object-versions --bucket <state-bucket> --prefix <key>` → identify last-good version → `aws s3api copy-object` → re-acquire lock → `terraform refresh`. KMS-CMK rotation does NOT impact existing state reads (KMS retains old material for decrypt) but rotating to a new key alias requires re-encrypting state via `terraform init -reconfigure`.

**4. Drift between console and state.** Inevitable in regulated multi-team accounts. Run nightly `terraform plan -detailed-exitcode` in CI; non-zero exit means drift. Auto-import via `terraform plan -generate-config-out=` (1.5+) for known-safe drift; ticket the rest.

**5. Provider major-version upgrades.** `hashicorp/aws` v5→v6 changed default behavior on several resources (`aws_s3_bucket_versioning`, `aws_lb` defaults). Always test in a staging account; use `~> 6.0` to allow patch upgrades only.

**6. Cross-region operations.** `aws_route53_record`, ACM certs in `us-east-1` for CloudFront, and `aws_s3_bucket_replication_configuration` need provider aliases. Forgetting an alias silently creates resources in the default region.

## Multi-region rollout with Stacks

```hcl
# stack.tfdeploy.hcl
deployment "us_east_1" {
  inputs = { region = "us-east-1", weight = 100 }
}

deployment "eu_west_1" {
  inputs = { region = "eu-west-1", weight = 100 }
  depends_on = [deployment.us_east_1]
}

orchestrate "rolling" {
  check {
    condition = context.plan.changes.add < 50
    reason    = "Plan changed too many resources; manual review required."
  }
}
```

Stacks deployments run sequentially with explicit `depends_on`; orchestration checks gate progression. For wave-based rollouts across many regions, group deployments into waves with manual approval gates between them.

## Observability runbook

**CI signals:**

| Signal                                           | Action                                                                               |
| ------------------------------------------------ | ------------------------------------------------------------------------------------ |
| Nightly `terraform plan` exit code 2 (drift)     | Auto-create ticket; review before next deploy; `terraform import` if benign          |
| `apply` duration `> 30 min`                      | Split state; check for `aws_route53_*` deletes (slow), ASG drains, RDS modifications |
| `Throttling` / `RateExceeded` in plan/apply logs | Reduce `-parallelism`, bump `max_retries`, split state, request limit increase       |
| HCP Terraform run failure rate `> 5%`            | Investigate Sentinel policy regressions, provider version skew                       |
| State lock held `> 1 hr`                         | `terraform force-unlock <lock-id>` only after confirming no active apply in CI logs  |

**Drift alarm wiring (CloudWatch Logs → Metric Filter → Alarm):**

```bash
aws logs put-metric-filter \
  --log-group-name "/aws/codebuild/terraform-drift" \
  --filter-name terraform-drift-detected \
  --filter-pattern '"Plan: " "to add" "to change" "to destroy"' \
  --metric-transformations metricName=DriftCount,metricNamespace=Terraform,metricValue=1
```

Then alarm on `Terraform/DriftCount > 0` and route to PagerDuty / Slack.

## Common pitfalls (field-tested)

- **Manual console changes alongside Terraform** — run nightly `terraform plan` as drift detection; use `terraform import` to reconcile.
- **Committing state or `*.tfvars`** — `.gitignore` them; store secrets in Secrets Manager, pull via ephemeral values.
- **Monolithic configurations** — break into modules; giant single-state plans are slow and risky.
- **Skipping plan review** — require PR-level plan review; gate apply behind approval.
- **Unpinned providers** — pin `hashicorp/aws` with a major-version constraint; upgrade deliberately.
- **Long-lived IAM access keys in CI** — use OIDC federation instead.

## When Terraform is NOT the best fit

- Pure AWS-only team already deep in CDK with CDK Pipelines and application-language colocation.
- Very small team, one environment, one workload — CloudFormation/SAM templates are lighter.
- Workloads that are 95% managed AWS services with no Kubernetes, no DNS-external, no SaaS providers — CDK or CloudFormation may be enough.
- Strict air-gapped environment with no provider registry access — evaluate Terraform Enterprise / OpenTofu + mirrored registry.

## Related reading

- [`Terraform AWS provider upgrade strategy`](/blog/terraform-aws-provider-upgrade-strategy/)
- [`Terraform state management on AWS: import, move, repair`](/blog/terraform-state-management-aws-import-move-repair/)
- [`Safe Terraform apply workflows with approval gates on AWS`](/blog/safe-terraform-apply-workflows-approval-gates-aws/)
- [`Migrating from Terraform to OpenTofu on AWS`](/blog/migrate-terraform-opentofu-aws/)
- [`Terraform vs AWS CDK: IaC decision guide`](/blog/terraform-vs-aws-cdk-infrastructure-as-code-decision-guide/)
- [`AWS infrastructure drift detection with Terraform`](/blog/aws-infrastructure-drift-detection-terraform/)

## Related services

- [AWS Architecture Review](/services/aws-architecture-review/)
- [DevOps Pipeline Setup](/services/devops-pipeline-setup/)
- [AWS Application Modernization](/services/aws-application-modernization/)

## Set up Terraform on AWS cleanly

1. **Install and authenticate** — Install Terraform (or OpenTofu) via tfenv or your package manager. For CI, configure GitHub Actions / GitLab / Buildkite with AWS OIDC federation — no long-lived access keys. Local developers use AWS IAM Identity Center SSO with short-lived credentials.
2. **Provision a remote state backend with locking** — Create a dedicated `tfstate` S3 bucket per account with versioning, KMS CMK encryption, TLS-only bucket policy, Block Public Access, and Object Lock where compliance requires it. Add an S3-native lockfile (Terraform 1.10+) or a DynamoDB lock table for older versions. One backend bucket per AWS account; one state key per environment/workload.
3. **Bootstrap an account scaffolding repo** — Use AWS Organizations + Control Tower as the landing-zone source of truth; your Terraform repo manages workload accounts, IAM Identity Center assignments, VPCs, baseline SCPs, Security Hub, and Config. Separate state per account.
4. **Write modules with pinned versions and tests** — Pin `hashicorp/aws` (e.g., `~> 5.0` → `~> 6.0` when upgrading), pin module versions, and add `terraform test` coverage for security-critical invariants (no public S3, encryption everywhere, least-privilege IAM). Use ephemeral values for secrets and tokens so nothing lands in state.
5. **Wire CI with plan-on-PR, apply-on-merge, and drift detection** — GitHub Actions via OIDC → Terraform plan on PR (comment on PR), apply on merge to main, and a nightly `terraform plan` to detect drift. For regulated workloads, gate apply behind Sentinel (HCP Terraform) or OPA policies and a manual approval.

## FAQ

### What is Terraform Stacks and when should I adopt it?
Terraform Stacks (GA on HCP Terraform in 2025) is a deployment model for coordinating multiple Terraform configurations as one unit — think "VPC + EKS + app + data tier across four environments and three regions" deployed through one orchestration instead of seven independently wired workspaces. It replaces a lot of the glue code teams write around `terraform_remote_state`, wrapper scripts, and CI fan-outs. Adopt it when (a) you manage more than two or three logical layers that deploy together, (b) you fan out across accounts/regions, and (c) you are already on HCP Terraform. For small teams on one workspace, plain workspaces + modules remain simpler.

### What are ephemeral values and ephemeral resources?
Ephemeral values (Terraform 1.10+) are references that exist only during a plan or apply and never land in state — ideal for short-lived secrets (a rotating DB password fetched from Vault or Secrets Manager, a temporary token, a bootstrap password). Ephemeral resources (1.11+) extend the same idea to provider-defined data sources — they are materialized, used, and discarded in one run. Use them everywhere you were previously using `sensitive = true` + state encryption as a workaround; it is the first real fix for "my plaintext password is in `terraform.tfstate`".

### What are provider-defined functions and what do they replace?
Provider-defined functions (Terraform 1.8+) let providers ship HCL functions alongside resources and data sources — e.g., `provider::aws::arn_parse(...)`, or parsing / formatting helpers inside the AWS / GCP / Vault providers. They replace brittle patterns like `regex(...)` gymnastics, stringly-typed helpers, and external `data` sources written just to do string manipulation. Terraform 1.9+ added `templatestring` and other native improvements; 1.10+ brought ephemeral values; 1.11+ continues the trend. Keep Terraform on a recent minor — the ergonomics wins are real.

### Is the Terraform Test Framework production-ready in 2026?
Yes, for module-level unit and integration tests. `terraform test` (Terraform 1.6+, matured through 1.10+) is how we verify module invariants in CI: plan-only tests run fast and cover security rules (no public S3, encryption on, least privilege); apply-tests spin up ephemeral resources in a sandbox account and verify end-to-end. Combine with `tflint`, `checkov` / `tfsec`, and Sentinel or OPA for policy. The weak spots are long-running infra (databases, Kubernetes clusters) — those still benefit from dedicated Terratest/Go-based harnesses. For most everyday modules, the native framework is enough.

### Should I stay on Terraform or move to OpenTofu in 2026?
Depends on three questions. (1) **Are you bound by an OSI-only or CNCF-only policy?** Then OpenTofu (MPL 2.0, Linux Foundation) is the pragmatic choice. OpenTofu 1.8 added client-side state encryption, 1.9 and 1.10 continued to track Terraform features where feasible. (2) **Do you rely on HCP Terraform / Sentinel / Stacks?** Stay on Terraform — Stacks in particular is not on OpenTofu's roadmap in the same form. (3) **Do you want a single provider ecosystem and minimal supply-chain friction?** Both share the same providers (OpenTofu has its own registry + ability to consume the Terraform registry) and both work with `hashicorp/aws`. Many enterprises run both: HCP Terraform for production golden-path, OpenTofu for greenfield or open-source-restricted contexts.

### What changed with the IBM + HashiCorp acquisition?
IBM completed its acquisition of HashiCorp in early 2025. Practically, nothing in the CLI or AWS provider has meaningfully changed for users — Terraform and HCP Terraform remain under HashiCorp branding inside IBM Software. The BUSL 1.1 license on Terraform core remains in effect; the OpenTofu fork continues as the OSI-licensed alternative. IBM's Cloud Paks and Red Hat Ansible ecosystem have closer integration stories (ansible-terraform operators, HCP Terraform on Red Hat OpenShift), but the core AWS experience is unchanged. Watch the roadmap for deeper Watson / GenAI features in HCP Terraform over the next year.

### How should I organize Terraform state and AWS accounts in 2026?
Landing zone first: AWS Control Tower + Organizations + IAM Identity Center. One AWS account per environment (dev/test/stage/prod) per workload, or per business unit + environment — not "one AWS account for everything". One Terraform state key per (account, workload); remote state in an S3 bucket inside that account with KMS CMK encryption, Block Public Access, Object Lock where required, and S3-native locking (Terraform 1.10+) or a DynamoDB table on older versions. Never share a state file across accounts; use `terraform_remote_state` data sources or — better — Terraform Stacks for cross-workspace references.

### Terraform vs AWS CDK vs CloudFormation in 2026 — which should I pick?
Short version. **Terraform**: pick for multi-cloud, large existing module ecosystem, HCP Terraform policy/governance, and teams comfortable with HCL. **AWS CDK**: pick for AWS-only shops that want to write infra in TypeScript/Python, for tight integration with AWS-managed constructs, and where you want to share libraries with application code. CloudFormation under the hood. **CloudFormation / SAM**: pick for AWS-only shops that want the lightest-weight tooling, native drift detection, and ChangeSets. **Pulumi** is a fourth option if you want a CDK-like programming model with multi-cloud. Most of our enterprise customers converge on Terraform for landing-zone and cross-workload infra, with selective CDK for highly AWS-native services (Lambda packaging, CDK Pipelines, Amplify).

### How do I handle secrets in Terraform on AWS?
Three rules. (1) Never hard-code secrets in HCL. (2) Source short-lived secrets with ephemeral values — pull from AWS Secrets Manager, HashiCorp Vault, or Parameter Store at plan/apply time, and let them expire after the run. (3) For long-lived generated secrets (initial DB password, KMS-wrapped values), generate inside AWS (`aws_secretsmanager_secret_version` with `random_password`) and store only an ARN reference in state. Combine with S3 state encryption via KMS CMK and OpenTofu 1.8+ client-side state encryption if your threat model requires zero-plaintext-secrets-at-rest.

---

*Source: https://www.factualminds.com/integrations/terraform-aws/*
