hashicorp/terraform
Backends
Active contributors: terraform-core team, terraform-aws (S3), terraform-azure, tf-eco-hybrid-cloud (GCS, Kubernetes), tf-core-cloud.
Purpose
internal/backend/ is the pluggable state-storage layer. A backend tells Terraform where to keep the state file (and, for a few backends, where to run operations). The CLI selects a backend at terraform init time based on the terraform { backend "..." {} } or cloud {} block in the configuration; subsequent commands use that backend's state manager and (sometimes) operation runner.
Directory layout
internal/backend/
├── backend.go # Backend interface, basic helpers
├── testing.go # shared test scaffolding
├── backendbase/ # base type providing common behaviors
├── backendrun/ # interfaces for backends that execute operations
├── init/ # registry of backend constructors (init.go)
├── local/ # the default local backend (also wraps state-only backends)
├── remote/ # the legacy remote backend (HCP Terraform pre-cloud-block)
├── remote-state/ # state-only backends:
│ ├── azure/, gcs/, http/, s3/, kubernetes/, oss/, cos/, consul/,
│ │ inmem/, oci/, pg/
└── pluggable/ # provider-implemented state storage (newer)Key types
| Type | File | Description |
|---|---|---|
backend.Backend |
backend.go |
Bare-bones interface: ConfigSchema(), PrepareConfig(...), Configure(...), StateMgr(workspace string), Workspaces(), DeleteWorkspace(name string, force bool). |
backendrun.OperationsBackend |
backendrun/operations_backend.go |
Optional interface for backends that run operations: Operation(ctx, op) returns a RunningOperation. Implemented by local, remote, and cloud. |
backendrun.Operation |
backendrun/operation.go |
The operation request: type (plan/apply/refresh/…), workspace, config, prior plan, hooks, view. |
backendrun.RunningOperation |
backendrun/operation.go |
The handle returned: cancellation, completion channel, accumulated diagnostics. |
backend.Enhanced |
(see init.go) | A backend with both state storage and operation execution. |
init.Backend(name string) backend.Backend |
init/init.go |
The registry: maps "s3" to s3.New, "local" to local.New, etc. |
How it works
graph TD
Cfg[terraform { backend "..." {} } / cloud {}] --> Meta[command.Meta.Backend]
Meta --> Init[internal/backend/init.Backend]
Init --> Ctor[constructor for selected name]
Ctor --> Cfg2[Configure with merged settings]
Cfg2 --> SM[StateMgr for workspace]
Meta --> Wrap[wrap in local.Local if not OperationsBackend]
Meta --> Op[backendrun.Operation]
Op --> Local[local.Local.Operation]
Local --> Ctx[terraform.Context.Plan / Apply]
Local --> SM
SM --> Persist[durable storage]init/ — the registry
internal/backend/init/init.go is a single file mapping backend names to their constructors. Adding a backend means adding an entry here and writing the implementation under a sibling directory.
The cloud and remote entries are special: they're labeled "enhanced" and wired to also implement OperationsBackend.
State-only vs operations backends
Most backends (s3, gcs, azure, http, kubernetes, pg, oss, consul, cos, oci, inmem) implement only Backend — they store state. When the CLI is asked to run an operation against one of these, it wraps the backend in local.Local and runs the operation locally. The wrapping logic is in internal/command/meta_backend.go.
The local backend itself (internal/backend/local/) implements both Backend and OperationsBackend. As an operations backend it:
- Reads the prior state from its own state manager (which delegates to whatever inner state-only backend wraps it).
- Loads the configuration via
internal/command/meta_config.go. - Builds a
terraform.Context. - Calls the appropriate
Plan/Apply/Refresh/Validate/Evalmethod. - Renders results through the operation's view.
The remote backend (internal/backend/remote/) and the cloud integration (internal/cloud/) implement both interfaces by sending the operation to HCP Terraform for execution. See cloud.
Workspace semantics
Backend.Workspaces() ([]string, error) returns the list of workspaces this backend exposes. Backends that don't natively support multiple workspaces typically simulate them via key-prefixing or a marker file:
s3: workspacedefaultlives at<key>; other workspaces live at<workspace_key_prefix>/<name>/<key>.gcs: similar — a configurable prefix.kubernetes: each workspace is a Kubernetes Secret.local: each workspace is a separateterraform.tfstate.d/<name>/terraform.tfstatefile in the working directory.
StateMgr(name) returns a statemgr.Full for the named workspace.
Backend configuration
Backend config arrives from three sources, merged in the order:
- The
terraform { backend "..." {} }orcloud {}block in the configuration. -backend-config=KEY=VALUEflags or-backend-config=path/to/file.tfvarspointing at an extra HCL fragment.- Previously-stored values in
.terraform/terraform.tfstate(the local marker file used to remember whatinitconfigured).
The merge logic is in internal/command/meta_backend.go. Sensitive values (credentials) are only kept in memory; the on-disk marker stores their hashes.
Migration
When the user changes their backend block (or switches between cloud {} and backend ...), meta_backend_migrate.go prompts them through migrating state from the old backend to the new one. The flow accommodates several scenarios:
- Local→remote: copy the local state to the new backend, then delete the local file.
- Remote→local: copy the remote state to a local file.
- Remote→remote: workspace-by-workspace migration with confirmation.
- Local-with-workspaces→cloud: workspace mapping rules.
Pluggable state storage
internal/backend/pluggable/ is the newer extension point that lets providers implement state storage. Configurations declare state_store {} blocks pointing at a provider plus a "store type" — the engine talks to the provider over gRPC to read and write state instead of using a built-in backend. The schemas are exchanged through the regular plugin protocol (internal/configs/state_store.go + the provider's GetProviderSchema response). This keeps the project's surface stable (no new state-only backends merged into the CLI) while still letting third parties ship their own.
Maintenance status of individual backends
Per CODEOWNERS:
| Backend | Maintainer | Status |
|---|---|---|
local |
Core team | Maintained |
remote |
Core team + tf-core-cloud | Maintained |
s3 |
Core team + terraform-aws | Maintained, accepts contributions |
azure (azurerm) |
Core team + terraform-azure | Maintained |
gcs |
Core team + tf-eco-hybrid-cloud | Maintained |
kubernetes |
Core team + tf-eco-hybrid-cloud | Maintained |
http |
Core team | Maintained |
consul |
None | Unmaintained |
cos, oss, pg, oci |
Third parties | Reviewed only when their maintainers are active |
The team explicitly does not accept new state-storage backends; the plug-gable state storage subsystem is the recommended path forward.
Integration points
- CLI:
command.Meta.Backendselects, configures, and migrates backends. The local-wrap logic is inmeta_backend.go. - State: every backend exposes
statemgr.Fullinstances; see state. - Engine:
local.Local.Operationis the bridge between operation backends and the engine. See terraform-core. - HCP Terraform: the
remoteandcloudbackends call out to HCP Terraform; see cloud.
Entry points for modification
- Adding a feature to an existing backend: edit the corresponding directory under
internal/backend/remote-state/<name>/. Tests in the same directory typically gate onTF_ACC=1to avoid hitting the real cloud. - Changing backend selection or migration:
internal/command/meta_backend.goandmeta_backend_migrate.go. - Adding a new operation kind: extend
backendrun.OperationTypeand update bothlocalandremoteto handle it. - Adding state-store-style support to a provider: the host-side glue is in
internal/backend/pluggable/; provider authors implement the matching gRPC methods.
For provider-side state storage and the cloud {} block, see HCP Terraform. For everything inside local.Local, see terraform-core.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.