hashicorp/terraform
Lore
A timeline of how Terraform Core got to where it is today, derived from the public commit history and CHANGELOG.md. Dates are approximate where the historical record only reveals month or quarter granularity.
Eras
The bootstrap (May 2014 – mid-2015)
The first commit (649cf336e8) lands on 2014-05-21 under Mitchell Hashimoto's authorship, with a small package layout and the original command package as the front door. The early codebase predates HCL2, the for_each meta-argument, modules-as-first-class-citizens, and providers-as-separate-binaries. The first releases ship a monolithic binary that bundles the AWS provider directly.
Highlights:
- Original CLI dispatch shape —
commands.gomapping subcommand names tocli.Commandfactories — is set up here and survives basically intact to today. - The
terraform.Contextgod-object is introduced as the coordinator for plan/apply. - The DAG primitive lives in
internal/dag/and is reused unchanged through every later rewrite.
Provider extraction and the SDK split (Mar 2017 – Aug 2018)
The single biggest non-language change in Terraform's history. Providers, originally compiled into the Terraform binary, are extracted into separate repositories and loaded via hashicorp/go-plugin over gRPC. This required:
- Defining the wire protocol in Protocol Buffers (
docs/plugin-protocol/tfplugin5.proto, retained today). - Building the provider installer in
internal/getproviders/to download and verify provider releases. - Inventing the registry-style address scheme (
hashicorp/aws) that became the basis for Terraform Registry.
After this era, Terraform Core stopped growing AWS/Azure/GCP code and started growing infrastructure to talk to the idea of a provider.
HCL2 and the 0.12 rewrite (early 2018 – Q3 2019)
The configuration language was rewritten on top of HCL2. This introduced:
- First-class types via
cty.Value(replacing string-based config). - Type constraints in variable declarations.
dynamicblocks, splat expressions, conditional improvements.- The expression evaluator now in
internal/lang/.
The 0.12 rewrite touched almost every file. The git log around 0.12-beta shows hundreds of commits from Martin Atkins reshaping internal/configs/, internal/terraform/, and internal/states/. The internal/legacy/ package was carved out at this time to retain the old representations of state and provider data so that tests and migrations could still compile.
State-as-data and the JSON state format (mid-2019)
Terraform 0.13 reorganized state to support fully-qualified provider source addresses (so the dependency lock file in 0.14 had something to reference) and also exposed a formal JSON representation, codified in internal/command/jsonstate/ and internal/command/jsonplan/. From this point forward, automation could parse plans and states without screen-scraping the human output.
The dependency lock file (Q4 2020)
terraform 0.14 introduced .terraform.lock.hcl. The implementation is internal/depsfile/ and the supporting hash logic is in internal/getproviders/hash.go. This was the first user-visible break that forced operators to update their CI workflows; before it, any terraform init could silently upgrade providers.
for_each for modules and moved blocks (2021)
Terraform 1.0 was released in Jun 2021 — a stability commitment more than a feature jump. The interesting platform changes around this time were:
- Modules gained
countandfor_each(1.x). The graph engine learned to expand entire module trees dynamically; seeinternal/terraform/node_module_expand.go. - The
movedblock (1.1) let users refactor withoutterraform state mv. Lives ininternal/configs/moved.goandinternal/refactoring/.
terraform test and native testing (Q3 2022 – ongoing)
The terraform test command graduated from an experiment to a production feature over several releases. The runtime moved out of internal/command/test.go and into a dedicated internal/moduletest/ package with its own graph builder (internal/moduletest/graph/) and run/state machinery. The matching parser lives in internal/configs/test_file.go. Mock providers (internal/configs/mock_provider.go) were added to support deterministic test runs without real provider calls.
HCP Terraform / cloud {} block and remote operations (2023 – ongoing)
The cloud {} configuration block superseded the terraform { backend "remote" {} } form for opting into HCP Terraform (formerly Terraform Cloud). The implementation lives in internal/cloud/ and reuses the older internal/backend/remote/ for the operations channel. Renames from "Terraform Cloud" to "HCP Terraform" over 2024 are visible throughout the codebase as comments and string literals are updated incrementally.
Stacks (2024 – ongoing)
The largest new subsystem since provider extraction. internal/stacks/ introduces a new top-level configuration form (stack {} and component {}) that composes multiple Terraform configurations into one planned-and-applied unit. Stacks have their own graph builders, plan format, and runtime, and ship a new RPC-API surface (internal/rpcapi/stacks.go) so HCP Terraform can drive them remotely. The stacks CLI subcommand (internal/command/stacks.go) is the local entry point. As of 1.16-dev, stacks are still gated behind experiment flags in many code paths.
Actions, queries, ephemeral values, identity (2024 – ongoing)
A cluster of language-level features arrived in late 2024 and continues into 1.16:
- Actions — first-class, non-resource operations a provider exposes.
action_triggerblocks andaction {}configuration.internal/configs/action.go,internal/plans/action_invocation.go, and a family ofnode_action_*.gofiles ininternal/terraform/. - Queries — read-only operations whose results aren't persisted to state.
internal/command/query.goandeval_query.go. - Ephemeral values — values that live only for the duration of one operation, never written to state. Implemented via
ctyvalue marks ininternal/lang/marks/and supported throughout the engine (node_resource_ephemeral.go,context_apply_ephemeral_test.go). - Identity — a stable, server-assigned identifier for a managed resource, separate from its address.
internal/command/state_identities.goand tests underinternal/terraform/context_apply_identity_test.go.
These features extend the protocol; the v6 plugin protocol (tfplugin6.proto) is where they are wired in.
License change (Aug 2023)
Terraform's license changed from MPL 2.0 to the Business Source License 1.1 on 2023-08-10. This is the most visible non-code event in the project's history; it triggered the OpenTofu fork. The repository's copyright headers were updated en masse via the scripts/copyright.sh tooling.
Longest-standing features
| Feature | First appeared | Status |
|---|---|---|
The dag package |
2014 | Unchanged in shape; still the only DAG implementation. |
commands.go factory map |
2014 | Same pattern as v0.1; entries added/removed but structure is identical. |
terraform.Context |
2014 | Massively rewritten internally over the years, but the public surface (Plan, Apply, Refresh, Validate) is largely unchanged. |
internal/states/statefile JSON format |
2018 (0.12) | Backwards-compatible additions only. The version field is still 4. |
cli library (hashicorp/cli) |
2014 | Same dependency since the start. |
Deprecated or removed features
| Feature | Removed/Deprecated | Replacement |
|---|---|---|
terraform push |
Removed in spirit, retained as a stub that errors. See internal/command/push.go. |
HCP Terraform / VCS-driven runs. |
Tool-specific provisioners (chef, salt-masterless, puppet, habitat) |
Deprecated in 0.13.4 (Sep 2020). | Configuration management tools used outside Terraform. |
| Pre-0.12 state format | Migrated automatically; loader code still lives in internal/legacy/ for compatibility. |
The 0.12+ JSON state format. |
Pre-0.13 unqualified provider references (provider "aws" without source) |
Removed in 0.13. | Required source addresses (hashicorp/aws). |
env list / env new / env select / env delete |
Renamed to workspace … in 0.10. The originals are still wired up in commands.go as hidden aliases. |
terraform workspace …. |
| Partial-import without configuration | Replaced by import {} blocks in 1.5+. The CLI subcommand still works but is no longer the recommended path. |
import {} configuration blocks. |
| Several state-storage backends (Consul, etcdv2/v3, OSS, OCI, COS, Postgres-third-party-maintainer) | Retained as code but explicitly unmaintained per CODEOWNERS. The team is not accepting new state-storage backends. |
Existing maintained backends (S3, GCS, Azure, K8s, HTTP, HCP Terraform). |
Major rewrites and migrations
- 0.12 HCL2 rewrite (2019). Configuration parsing, expression evaluation, and type system rewritten around HCL2 and
cty. - 0.13 provider source addresses (2020). Every reference to a provider gained a fully qualified address; the registry namespace migration was driven by tooling in
internal/getproviders/. - 0.15 sensitive values (2021). Sensitive marks moved from a state-level flag to per-value
ctymarks, propagated through the entire evaluation pipeline. - 1.0 → 1.1 module expansion. Modules gained
count/for_each, requiring a parallel rewrite of the apply graph builder to handle dynamic module trees. - License migration to BUSL (Aug 2023). Mechanical change applied to ~12,000 source files; visible in headers via
.copywrite.hcl. - HCP Terraform rename (2024). A long tail of string updates across
internal/cloud/,internal/backend/remote/,internal/command/, and tests.
Growth trajectory
- 2014: One package, one author, one provider (AWS) baked in.
- 2017: First multi-provider release with binaries downloaded ad-hoc.
- 2019: HCL2 rewrite triples the size of
internal/configs/. - 2020–2021: 1.0 stabilization; growth slows in core, accelerates in
internal/cloud/. - 2024–2026: Stacks, actions, queries, ephemerals, identity — the language and engine grow another ~150 KLOC across
internal/stacks/,internal/terraform/, andinternal/configs/.
The codebase has roughly tripled in size since 1.0 (Jun 2021), almost entirely from new subsystems rather than new code in old ones.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.