HCP Terraform and Terraform Enterprise
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages. |
Terraform the binary plans and applies a configuration, and it leaves everything around that to you: where the state lives and who may read it, who is allowed to run an apply, where the cloud credentials come from, how a module is shared, how a policy blocks a bad change, how somebody notices that the infrastructure drifted. Teams usually answer these questions with a storage bucket, a CI system, a secret store and a set of conventions. HCP Terraform is HashiCorp’s hosted answer to all of them at once, and Terraform Enterprise is the same application that you run yourself.
This page describes what the platform contains and how to drive it from the command line, from a repository and from code.
It is the destination of several earlier pages: the cloud block was introduced in
Backends and Remote State, the workspace and project vocabulary in
Workspaces, Environments and Project Structure,
and the policy engines that run on the platform in Policy as Code. The same
jobs done with a general-purpose pipeline are in CI/CD and Automation,
and the platform’s multi-deployment orchestration feature has its own page,
Terraform Stacks.
The baseline is Terraform 1.17 (1.17.0 was released on October 21, 2026), the hashicorp/tfe provider 0.82.0 (released
on October 8, 2026), HCP Terraform as documented on developer.hashicorp.com in October 2026, and the Terraform Enterprise
2.0.x line. Prices, plan limits and the feature matrix of a hosted product change more often than the language does, so every
figure on this page carries the date of the reading; confirm it on the pricing and billing pages before deciding anything.
Source: HCP Terraform Docs — Overview.
What the platform is, and what it is called
HCP Terraform is a hosted service that stores state, runs Terraform for you on disposable virtual machines, connects to
version control, and adds governance (teams and permissions, policy enforcement, cost estimation, audit trails). The
service was called Terraform Cloud until April 22, 2024, when HashiCorp renamed it to HCP Terraform. Most books, blog
posts and error messages written before that date still say "Terraform Cloud", and a few identifiers kept the old names:
the cloud block, the tfe provider prefix (for Terraform Enterprise, a name that goes back to Private Terraform
Enterprise) and the hostname app.terraform.io. They are all the same product.
Terraform Enterprise is the self-managed distribution of the same application. The official overview describes it as a private instance of HCP Terraform with no resource limits and with additions such as audit logging and SAML single sign-on, and states that information about HCP Terraform generally applies to Terraform Enterprise unless it is marked otherwise. HashiCorp is now part of IBM, and the current pricing page lists the self-managed product as IBM Terraform Enterprise; the documentation on developer.hashicorp.com still uses "Terraform Enterprise".
| Edition | Who runs it | Typical reason to choose it |
|---|---|---|
Terraform CLI (open source engine under the BSL) |
You, on a laptop or in your own CI |
Everything in the previous pages; no platform features |
HCP Terraform |
HashiCorp, as a service (with an EU region, whose hostname is |
No infrastructure to operate, fast start, pay by managed resource |
Terraform Enterprise |
You, in your own network (Docker, Kubernetes, OpenShift, Podman or Nomad) |
Data residency, air-gapped networks, regulatory requirements |
The rest of the page uses "the platform" for statements that are true of both, and names HCP Terraform or Terraform Enterprise when they differ.
The object model: organizations, projects and workspaces
Everything on the platform lives in a three-level hierarchy, and most mistakes come from putting a setting at the wrong level.
| Object | What it is |
|---|---|
Organization |
The top-level collection of projects, workspaces and Stacks and the owner of teams, tokens, the private registry, policy sets, variable sets, VCS connections and agent pools. Plans and billing are set per organization, not per user, and one user can belong to several organizations on different plans. Names may contain letters, numbers, underscores and hyphens. |
Project |
A folder inside an organization that holds workspaces, Stacks or both. Teams can be granted access at the project level instead of organization-wide, which is the lever for least privilege. Project names are unique inside the organization, projects can carry up to 10 tags that their workspaces inherit, and a project can be deleted only when it is empty. Every organization has a default project. |
Workspace |
One self-contained root module with its own state, its own variables, its own run history and its own settings. It is the unit of execution: a run always belongs to one workspace, and a workspace processes its runs one at a time, in order. |
Stack |
A configuration that deploys the same set of components to many deployments, each with isolated state; see Terraform Stacks. |
Source: HCP Terraform Docs — Organizations, HCP Terraform Docs — Manage projects, HCP Terraform Docs — Workspaces
An HCP Terraform workspace is not a CLI workspace. A CLI workspace is another instance of state inside the same working directory; an HCP Terraform workspace is a separate working directory with its own configuration source and settings, as the comparison table in Workspaces, Environments and Project Structure spells out. The official documentation also has a page, "Compare Stacks and workspaces", for the choice between a workspace per environment and one Stack with several deployments.
Access is granted to teams, never to individual users. A user who is in the organization but in no team can sign in and
use nothing. Organization-level permissions (manage workspaces, manage policies, manage the private registry, manage
agent pools, manage run tasks and so on) are separate from the project and workspace permissions that a team receives,
and the project permission levels are read, write, maintain, admin and custom. Free-plan organizations have
most features but not custom team permissions; see Pricing and the free tier.
A sensible layout for the Bookshelf scenario follows the blast-radius reasoning of the workspaces page: one organization
for the company, one project per product (bookshelf), and inside it one workspace per layer and environment.
organization: example-org
└── project: bookshelf
├── workspace: bookshelf-network-dev (tags: layer=network, env=dev)
├── workspace: bookshelf-network-prod (tags: layer=network, env=prod)
├── workspace: bookshelf-data-dev (tags: layer=data, env=dev)
├── workspace: bookshelf-data-prod (tags: layer=data, env=prod)
├── workspace: bookshelf-apps-dev (catalog-api, reviews-api, web)
└── workspace: bookshelf-apps-prod
Layers that change at different speeds get separate workspaces, and they exchange values through remote-state sharing,
run triggers or tfe_outputs (see below), not through shared files.
Workspace settings that matter
The workspace settings page is long; these are the settings that change how a workspace behaves.
-
Execution mode:
remote,local,agent, or the project’s default. See Execution modes: remote, local and agent. -
Apply method: manual (a person confirms every plan) or auto-apply. Runs created by run triggers have a separate auto-apply setting.
-
Terraform version: an exact version or a constraint such as
~> 1.17; the default is the release that was current when the workspace was created. A "plan only" run can use a different version without changing the setting, which is the way to test an upgrade. -
Working directory: the path inside the repository where Terraform runs. For VCS-backed workspaces it also controls which file changes trigger a run.
-
Remote state sharing: which other workspaces may read this workspace’s outputs through
terraform_remote_stateortfe_outputs. New workspaces default to "share with specific workspaces" with none selected, and the documentation recommends keeping it that way. -
Run triggers: a successful apply in a source workspace queues a run in this one.
-
Auto-destroy: a destroy run scheduled at a date or after a period of inactivity, set per workspace or as a project default (Standard and Premium editions); this is the HCP Terraform answer to short-lived review environments.
-
Health assessments: drift detection and continuous validation, see Health assessments: drift detection and continuous validation.
Connecting a configuration: the cloud block
A configuration uses the platform by adding a cloud block to the terraform block. It replaces a backend block (the
two cannot coexist), it needs Terraform 1.1 or later, and like a backend it cannot refer to variables, locals or data
source attributes.
terraform {
required_version = ">= 1.11"
cloud {
organization = "example-org"
workspaces {
name = "bookshelf-apps-dev"
}
}
}
The arguments are:
-
organization: required for HCP Terraform. -
hostname: defaults toapp.terraform.io; required for a Terraform Enterprise installation, which has its own hostname. -
token: the API token. The documentation recommends omitting it and usingterraform login, thecredentialsblock of the CLI configuration file, or an environment variable instead. -
workspaces: required for HCP Terraform, withnamefor exactly one workspace ortagsfor several, plus an optionalproject.nameandtagsare mutually exclusive.tagsaccepts a map of key-value tags or a list of key-only tags (the older style), and Terraform associates the configuration with the workspaces that match all the tags; workspaces created through this configuration inherit them.
A configuration that is shared by several workspaces uses tags and lets the operator pick a workspace:
terraform {
cloud {
organization = "example-org"
workspaces {
project = "bookshelf"
tags = {
layer = "network"
}
}
}
}
With tags, Terraform links the working directory to the existing workspaces that match, and terraform init prompts to
create a workspace with those tags if none exists. The thing to remember is that these are HCP Terraform workspaces, not
state files in a local directory.
Environment variables
Four variables supply the same values from the environment, so a single directory can be reused by different pipelines without edits. Terraform reads them only for an attribute that the block omits, and if both exist, the block wins.
| Variable | Fills in | Notes |
|---|---|---|
|
|
The configuration takes precedence if both are set. |
|
|
For Terraform Enterprise installations. |
|
|
The configuration takes precedence if both are set. |
|
|
Also selects the workspace. With tags, its value must be one of the tagged workspaces. The platform does not create a workspace from this variable; the workspace must already exist. |
An all-environment setup still needs an empty cloud block in the configuration file:
terraform {
cloud {}
}
export TF_CLOUD_ORGANIZATION="example-org"
export TF_CLOUD_PROJECT="bookshelf"
export TF_WORKSPACE="bookshelf-apps-dev"
terraform init
terraform plan
Authentication from the command line
terraform login opens a browser, creates a token and stores it in the CLI configuration. Without a browser (CI), set the
variable named after the hostname with each period replaced by an underscore, TF_TOKEN_app_terraform_io for
app.terraform.io; it has priority over a token saved by terraform login. The token must be a user or team token:
organization tokens cannot be used for command-line actions. HCP Terraform organizations in the EU region log in with the
hostname app.terraform.io/eu. The details of the credentials and the CLI configuration file are in
CLI Essentials.
Migrating existing state
Adding a cloud block to a configuration that already has state is a backend change, and terraform init offers to copy
the state to the new workspace (the -migrate-state flag makes the intent explicit). The general procedure, the backup
advice and the S3-to-remote walkthrough are in
Backends and Remote State; the one platform-specific step is to
create the workspace’s variables and credentials before the first remote run, because a remote run does not see the
variables of your shell.
Run workflows
A run is one plan, optionally followed by an apply, executed in a workspace. The platform has three ways to start one, and a workspace is bound to one of them by how its configuration arrives.
| Workflow | How a run starts | Typical use |
|---|---|---|
UI/VCS-driven |
A webhook from the linked repository (a merge to the tracked branch), the New run button, or a pull request for a speculative plan |
GitOps: the repository is the source of truth |
CLI-driven |
|
Developers iterating, scripts, existing CI |
API-driven |
A call to the Runs API, or uploading a configuration version |
Your own automation or another platform |
The stages of a run
Every run passes through the same stages; the optional ones appear only when the workspace has the feature configured.
In the order the documentation gives them:
-
Pending and Fetching: the run waits in the workspace queue, then the configuration is pulled from the repository.
-
Pre-plan: run tasks (see Run tasks) report pass or fail before planning.
-
Plan:
terraform planruns. A plan with no changes and no cost or policy checks ends as "Planned and finished". -
Post-plan: run tasks receive the JSON plan.
-
OPA policy check, then cost estimation, then Sentinel policy check: the governance gates described in Policy as Code. A failed mandatory policy pauses the run in "Policy Override" until an authorised person overrides it or discards the run, and a hard-mandatory Sentinel failure ends it.
-
Confirmation: with manual apply, someone with the right permission confirms or discards. No new run can start until the pending plan is applied or discarded.
-
Pre-apply, Apply, Post-apply: post-apply tasks are advisory only, since the infrastructure already changed.
A run locks the configuration version and the variable values at the moment it starts, so later edits affect only later runs. The run ends as Applied, Planned and Finished, Apply Errored, Plan Errored, Discarded or Canceled.
UI/VCS-driven runs
When a workspace is linked to a repository and one branch, the platform registers webhooks with the VCS provider, and a merge or commit to that branch queues a run. Opening or updating a pull request against the branch starts a speculative plan, a plan-only run that never applies and does not wait for the queue; the platform posts a link and a status to the pull request. Details worth knowing:
-
Triggers can be limited to files and directories, or to Git tags; a monorepo uses the working directory and trigger patterns so only the affected workspaces plan.
-
A workspace with no runs yet will not accept webhook-triggered runs until somebody queues one manually.
-
Speculative plans are not run for pull requests from forks by default.
-
A speculative plan compares the pull request’s head branch against the current state, so a stale branch gives a misleading preview; rebase before trusting it.
-
A manual run uses the workspace’s current configuration version and not the latest commit, which suits a change of variables only.
CLI-driven runs
With the cloud block configured and a token available, the usual commands run on the platform:
$ terraform login
$ terraform init
$ terraform plan # a remote speculative plan, using local files and the workspace's variables
$ terraform apply # a remote plan, then a prompt to approve it
The documented behaviour:
-
terraform planis a speculative plan and works in any workspace where you can queue plans, VCS-linked ones included. -
terraform applyis for workspaces that are not linked to a repository; it plans remotely and asks for approval. -
Remote saved plans (
plan -outfollowed byapply FILE) need Terraform CLI 1.6.0 or later, and a saved plan can be applied only in the workspace that created it. Stale plans are discarded automatically. -
Variables for a run can come from
-var,-var-fileorTF_VAR_variables in your shell, but provider credentials and other environment variables must be set in the workspace, not just locally. -
terraform importstill runs on your machine, so the workspace’s environment variables are not available to it. The documentation recommendsimportblocks (see Import and Query). -
Non-interactive systems cannot answer the approval prompt; the documentation recommends the API-driven workflow for them, or
-auto-approveimmediately after approving a speculative plan.
API-driven runs
The API workflow is for software that talks to the platform directly. In the documented sequence the caller packs the
configuration into a tar.gz with the .tf files at the root of the archive, looks up the workspace ID, creates a
configuration version, and uploads the archive to the returned upload URL; the upload itself starts the plan. Applying a
non-auto-apply workspace uses the Run apply endpoint. Authentication is a bearer token, and the token needs the permission
to queue plans.
# Illustrative sequence; $TOKEN is a team or user token supplied by the CI secret store.
ORG="example-org"
WS="bookshelf-apps-dev"
tar -czf bundle.tar.gz -C ./bookshelf-apps .
WS_ID=$(curl -s -H "Authorization: Bearer $TOKEN" \
"https://app.terraform.io/api/v2/organizations/$ORG/workspaces/$WS" | jq -r '.data.id')
UPLOAD_URL=$(curl -s -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/vnd.api+json" \
-d '{"data":{"type":"configuration-versions"}}' \
"https://app.terraform.io/api/v2/workspaces/$WS_ID/configuration-versions" \
| jq -r '.data.attributes."upload-url"')
curl -s -H "Content-Type: application/octet-stream" --data-binary @bundle.tar.gz "$UPLOAD_URL"
The official page marks its script as not meant for production; real automation uses an SDK instead (the changelog lists
an official Python SDK, python-tfe, since October 28, 2025) or the tfe provider described below.
Execution modes: remote, local and agent
Where Terraform itself runs is a workspace setting, independent of how the run was started.
| Mode | What happens | Consequences |
|---|---|---|
Remote (default) |
The platform runs Terraform on its own disposable virtual machines |
Policy enforcement, cost estimation, notifications and run tasks are available; credentials live in the workspace |
Local |
The platform only stores state; the plan and apply run on your workstation or CI worker |
Many platform features are unavailable; you must supply credentials yourself |
Agent |
An HCP Terraform agent in your network polls the platform and runs the job |
Remote-mode features with private network reach; a paid feature whose number of agents depends on the plan |
Changing the mode while a run is planned can make that run error on apply; the documented procedure is to disable
auto-apply, lock the workspace, change the mode, then unlock and re-enable auto-apply. A workspace in local mode is also
the migration step for a team that wants central state first and remote runs later.
Agents
An agent is a small program deployed inside a network segment so that the platform can manage infrastructure that it cannot reach: a vSphere cluster, a database behind a firewall, an on-premises appliance, a protected enclave. The agent needs only outbound connectivity to the platform, so no inbound firewall rule is opened.
-
Agents belong to an agent pool; each pool has its own tokens, which are not shared across pools. A workspace (or a project default) is pointed at a pool, and a pool can be limited to specific workspaces or projects.
-
The agent is distributed as the container image
hashicorp/tfc-agentand as a standalone binary. -
It is configured with environment variables:
TFC_AGENT_TOKEN(required),TFC_AGENT_NAME(a display name),TFC_ADDRESS(the API address, defaulthttps://app.terraform.io) andTFC_AGENT_AUTO_UPDATE(minorby default,patchordisabled), among others. -
Dynamic provider credentials need agents v1.7.0 or newer, and some features need newer ones.
export TFC_AGENT_TOKEN="${AGENT_POOL_TOKEN}" # taken from the pool, never committed
export TFC_AGENT_NAME="bookshelf-agent-1"
docker run --platform=linux/amd64 -e TFC_AGENT_TOKEN -e TFC_AGENT_NAME hashicorp/tfc-agent:latest
The image tag latest is what the official page shows; production deployments should pin a version and let an update
policy (TFC_AGENT_AUTO_UPDATE) decide how the agent changes, as for any container.
Variables and variable sets
Each workspace has variables of two categories: Terraform variables, which set the root module’s input variables (and,
unlike environment variables, can be lists and maps written in HCL), and environment variables, which exist in the run’s
process environment and carry provider credentials such as AWS_REGION. Both can be marked sensitive: the value becomes
write-only in the interface and the API. It can still appear in logs and state in some cases, which is why the
sensitive data page is the companion of this section.
A variable set is a named group of variables that can be applied to many workspaces, which is how one set of credentials or tags reaches many places without copy-paste.
-
An organization-owned set can be global, or applied to chosen projects, Stacks or workspaces. A project-owned set can be applied to the whole project or to selected workspaces and Stacks inside it. A global set cannot declare a key that another global set declares.
-
Precedence, from weakest to strongest: variable sets, then workspace variables, then values given for a run on the command line (
-var,-var-file,TF_VAR_). A workspace variable overwrites a set value with the same key, and the interface flags the overwritten one. Between two sets applied to one workspace, a set can overwrite the other’s key. -
A priority variable set inverts this: its values overwrite same-key variables at more specific scopes, including command-line values and
terraform.tfvarsfiles. The documentation notes that code can still bypass it by hard-coding a value, so a policy or a run task should back it up when enforcement matters. -
The platform loads
*.auto.tfvarsandterraform.tfvarsfrom the configuration on every run; other.tfvarsfiles must be passed explicitly. A workspace variable with the same key overrides the file’s value.
The language-level precedence rules (defaults, -var-file, TF_VAR_) are in
Variables, Outputs and Locals; the platform adds its own layers
below the command line.
Dynamic provider credentials
The best credential is one that does not exist until it is needed. Dynamic provider credentials replace long-lived keys stored in workspace variables with short-lived credentials that the platform obtains for each run through workload identity (OIDC), the platform-side counterpart of the CI pattern described in CI/CD and Automation.
How it works, as documented:
-
The platform generates an OIDC-compliant workload identity token that names the organization, the project, the workspace (or Stack) and the run phase.
-
At the start of a plan or an apply, the platform sends the token to the cloud provider.
-
The provider verifies the signature against the platform’s public key (published at the JWKS endpoint
https://app.terraform.io/.well-known/jwks) and returns temporary credentials. -
The platform puts them in the run environment, and they are discarded when the run ends.
The token’s sub claim has the form organization:<org>:project:<project>:workspace:<workspace>:run_phase:<plan|apply>,
and aud is the audience for the target platform (aws.workload.identity for AWS, for instance, which can be changed).
Further claims name the organization, project, workspace and run (terraform_organization_name,
terraform_project_name, terraform_workspace_name, terraform_run_phase and others). A trust policy must check at
least the audience and the organization name in sub; without that check, any HCP Terraform organization could assume the
role.
Source: HCP Terraform Docs — Dynamic provider credentials, HCP Terraform Docs — Workload identity tokens
Every integration has the same three steps: establish trust between the platform and the cloud, create a role or identity
with the permissions the workspace needs, and add environment variables to the workspace (or to a variable set that many
workspaces share). The supported targets are Vault, AWS, Google Cloud, Azure, Kubernetes, HCP and Tencent Cloud, and every
variable also exists with a TFC_DEFAULT_ prefix (shared defaults) and a _TAG suffix (named provider aliases).
| Target | Variables set in the workspace or variable set (from the official pages) |
|---|---|
AWS |
|
Azure |
|
Google Cloud |
|
Vault |
|
Source: HCP Terraform Docs — AWS configuration, HCP Terraform Docs — Azure configuration, HCP Terraform Docs — Google Cloud configuration, HCP Terraform Docs — Vault configuration
The AWS trust policy is the part people get wrong, so here is its documented shape, with placeholders:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Federated": "<OIDC_PROVIDER_ARN>" },
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"app.terraform.io:aud": "aws.workload.identity",
"app.terraform.io:sub": "organization:example-org:project:bookshelf:workspace:bookshelf-apps-dev:run_phase:apply"
}
}
}
]
}
The condition keys use the hostname without https://. To use one role for both phases, the documentation switches the
sub comparison to StringLike and ends the value with run_phase:*; the safer layout is separate plan and apply roles,
with a read-only plan role. The cloud side of this trust (the identity provider, the roles, the least-privilege reasoning)
is described in AWS Identity and Access Management,
Azure Identity and Access and
Google Cloud Identity and Access.
The provider block of a configuration that uses dynamic credentials contains no secrets at all:
provider "aws" {
region = var.aws_region # no access keys: the platform injects short-lived credentials
}
The same OIDC idea appears on the other side of the pipeline: a CI job outside the platform presents its own token to the cloud, and the platform presents its token for the runs it executes. Both remove the static key that the earlier generation of tutorials stored in a CI secret.
The private registry and no-code modules
The private registry works like the public Terraform Registry but is visible only to the organization’s members. It can host private modules and private providers, and it can synchronise selected public providers and modules so that the organization marks which ones it recommends.
-
Publishing a module needs a VCS connection and a repository whose release tags are semantic versions (optionally prefixed with
v); tags that do not match are ignored. A repository with several modules can use a tag prefix. The alternative is branch-based publishing, where you name a branch and a version and publish new versions by choosing a commit; only branch-based modules support the registry’s module tests. The repository should follow the standard module structure, as described in Module Development and Publishing. -
Consuming a module uses the registry address
app.terraform.io/<ORG>/<NAME>/<PROVIDER>(or the Terraform Enterprise hostname) with aversionconstraint, and the consumer authenticates with a platform token, so people who may use the module do not need access to its source repository. -
Governance: a Sentinel policy can require that non-root modules come from the private registry, or that they use recent versions.
module "network" {
source = "app.terraform.io/example-org/network/aws"
version = "~> 2.1"
name = "bookshelf-dev"
cidr = "10.20.0.0/16"
}
No-code provisioning lets people deploy a module’s resources without writing Terraform: a platform team marks a registry module "no-code ready", and a consumer picks it from the registry, answers a form and gets a new workspace. It requires the Standard or Premium edition, and a few rules shape the module:
-
Declare the required providers inside the module (the opposite of the advice for ordinary reusable modules), and follow the standard structure with resources at the repository root.
-
A no-code workspace must authenticate to every provider the module uses. The documentation suggests a project-scoped variable set (recommended), a global set, sensitive outputs of another workspace read through remote state, or letting the first run fail so the user adds credentials.
-
Scope each module to one use case, give variables sensible defaults and, for variables without a default, define the allowed values (dropdown options) in the interface, in the API or with the
tfeprovider. -
The no-code designation is pinned to a module version; moving to a new version means reviewing the variable options.
A Bookshelf platform team might publish one no-code module called bookshelf-service that wraps the container, load
balancer and database pattern, so that a new service team gets a workspace with the right network, tags and policies
without learning HCL. When that team later needs more control, the module’s source is still the ordinary module that they
can read and call from code.
Run tasks
A run task calls an external service at a defined point of the run, passes it context, and lets the answer pass or stop the run. Typical tasks are security scanners, cost tools, ticket checks and internal approval systems.
-
A task is defined once in the organization (a name, a URL, an optional HMAC key) and then attached to workspaces. When it is created, the platform sends a test POST, and the endpoint must answer with HTTP 200 for the task to be created.
-
It is attached at a stage:
pre_plan,post_plan,pre_applyorpost_apply. -
It has an enforcement level:
advisory(a failure shows a warning) ormandatory(a failure stops the run). Post-apply tasks are advisory only. -
The platform sends a JSON payload with the stage, an access token, the configuration download URL, a plan JSON URL, the run, workspace and project identifiers, a callback URL and the enforcement level. If an HMAC key is configured, the request carries a SHA-512 signature in the
X-Tfc-Task-Signatureheader so the service can check where it came from. -
The service answers with 200 and later reports
running,passedorfailedplus a message to the callback URL. The request errors if no progress arrives within 10 minutes or the task runs longer than 60 minutes. -
The Free edition includes one run-task integration that can apply to up to ten workspaces. The changelog also lists project-level run tasks since June 8, 2026.
Run tasks and policies overlap in purpose. Use a policy (Sentinel, OPA or Terraform Policy) for a rule that can be written over the plan; use a run task for a decision that needs an outside system or a third-party product.
Health assessments: drift detection and continuous validation
Infrastructure changes behind Terraform’s back, and a Terraform that is run only on pull requests will not notice. Health assessments are scheduled checks that run without a pull request or a person.
-
Drift detection finds configuration drift, an out-of-band change that conflicts with the configuration. It does not detect state drift. The fix is a plan and apply that reverts the change, or a configuration update that keeps it.
-
Continuous validation re-evaluates the custom conditions of the configuration (preconditions, postconditions and
checkblocks, see Lifecycle, Validation and Checks) after provisioning, for example a website’s status code or a certificate’s renewal status. Results appear under Health > Continuous validation. -
Assessments run periodically, never interrupt a run (a new run cancels an assessment in progress), and pause when the latest run errored or was discarded. A workspace admin can start one on demand.
-
They need Terraform 0.15.4 or later for drift detection, 1.3.0 or later for both, the remote or agent execution mode, a successful latest run and at least one successful apply. They are available in the Standard and Premium editions.
-
An organization owner can enforce them for the whole organization, which overrides workspace settings.
Source: HCP Terraform Docs — Health
The explorer and change requests
The explorer answers questions about the whole organization from a single query interface. It has four views (workspaces, modules, providers and Terraform versions), pre-built queries (top module versions, workspaces without a VCS connection, workspaces with failed checks, drifted workspaces, the most recently updated), a query builder whose conditions combine with AND, and saved views. It needs the organization owner role, or the "View all workspaces" permission or higher.
A change request turns an explorer finding into a task on the affected workspaces: an administrator selects the workspaces
from a query result and writes a message, for instance "upgrade the network module to 2.x" because an old version was
revoked. The request stays on each workspace’s backlog until the team archives it as done, and a team notification can
alert the workspace’s owners. Change requests are available in the Standard and Premium editions.
Together with the private registry, these features are the platform’s answer to the module-upgrade problem described in Module Development and Publishing: the registry publishes the new version, the explorer lists the workspaces that still use the old one, and change requests ask their owners to move.
Policies, cost estimation and notifications
The run stages above include the governance gates; this page only locates them. Sentinel, OPA and Terraform Policy are
configured as policy sets attached to workspaces, projects or Stacks, with the enforcement levels and examples in
Policy as Code. The tfe_policy_set documentation names the same three
frameworks (Sentinel, Open Policy Agent and Terraform policy, tfpolicy) and notes that on Terraform Enterprise, Terraform
policy requires v2.1.0 or later. According to the changelog, HCP Terraform started evaluating Terraform policy (beta) on
July 16, 2026 and policies against Stacks on September 10, 2026. Free organizations have one policy set of up to five
policies.
Cost estimation shows the estimated monthly cost change before the apply and can feed Sentinel policies; it is part of the Standard and Premium editions. Notifications send run events per workspace to Slack or any webhook-capable service, and the Premium edition adds audit logging.
Pricing and the free tier
The platform is billed by managed resources (the documentation also says "resources under management"), not by user.
-
A managed resource is a resource in state whose mode is
managed. It counts from the first plan or apply until it is destroyed. Resources created withcount,for_eachand modules count; data sources, locals, variables, and thenull_resourceandterraform_dataresources do not. -
Billing is hourly: the peak resource count of an hour sets the cost of that hour, and a partial hour is billed as a full one. The usage page of the HCP portal refreshes hourly. Since 2026 the usage insights for paid plans break consumption down by project, workspace and Stack.
-
There are three billing models: pay-as-you-go (monthly, by usage), HashiCorp Flex (committed usage with volume discounts) and contract plans negotiated with sales (the EU region uses contract plans).
The plans, as the overview page and the pricing page showed them on October 23, 2026:
| Plan | Managed resources | Concurrent remote / agent runs | What it adds |
|---|---|---|---|
Free |
Up to 500 |
1 / 1 |
Remote execution, VCS integration, private registry, run tasks, SSO, one policy set; no custom team permissions |
Essentials |
Unlimited |
3 / 1 |
Teams and custom roles; listed on the pricing page from $0.10 per resource per month |
Standard |
Unlimited |
10 / 10 |
Policy enforcement, cost estimation, health assessments, change requests, no-code modules, auto-destroy; from $0.47 per resource per month |
Premium |
Unlimited |
200 / 300 |
Audit logging and priority support; from $0.99 per resource per month |
Workspaces, Stacks and projects are unlimited on every plan, and the API rate limit is 30 requests per minute on every plan. Additional concurrency can be bought on Standard and Premium. The self-managed edition has no resource limit and is priced by contract.
The free-tier change
Older articles and both reference books describe the free tier of Terraform Cloud as a plan limited by the number of members. That is the legacy Free plan, and it no longer exists. HashiCorp’s announcement of December 17, 2025 says that the legacy user-based Free plan reached end of life on March 31, 2026 and that the remaining organizations were moved automatically to the enhanced Free tier, which had been introduced in 2023. The enhanced tier supports unlimited users and up to 500 managed resources at no charge, and includes SSO, policy as code (Sentinel and OPA), run tasks and agents. The migration cannot be reversed to a legacy plan, and organizations on the enhanced tier needed to do nothing.
The practical reading: the question is no longer "how many people are in the organization" but "how many resources are
under management". Teams close to the 500 limit should count them (the resource_count attribute of a workspace shows the
number) and consider that the free tier has one concurrent run, so a busy pipeline queues.
Source: HashiCorp — Continuing HCP Terraform’s enhanced Free tier experience, HCP Terraform Docs — Overview
The changelog of October 15, 2026 adds a detail for organizations on the older Flex 1.0 billing accounts: the Premium plan appears as a locked card that cannot be selected, while organizations that are not on Flex are unchanged. The numbers above come from reading the pages on that date, and the pricing page showed a "$500 HCP trial credit" for new sign-ups next to the per-resource prices; confirm the current offer before relying on it.
Terraform Enterprise
Terraform Enterprise (TFE) is the same application packaged for your own infrastructure. It exists for organizations whose rules prevent a hosted service: air-gapped or regulated networks, strict data residency, or an integration with internal identity and storage that the hosted service cannot reach. In exchange you operate it.
Versions
Terraform Enterprise changed its release naming. Until July 2025 it shipped monthly releases named vYYYYMM-N (the last
one listed in the release notes is v202507-1). The current documentation lists semantic versions, with the 2.0.x line as
the newest, followed by the 1.2.x, 1.1.x and 1.0.x lines. The 2.0.x release notes link Terraform CLI 1.14.4 and Sentinel
0.40.0 and recommend Docker Compose V5 for Docker deployments. The project plan for this guide records 2.0.8 as the latest
patch release (September 23, 2026), which the release pages fetched while writing did not confirm. Documentation older than
v202408-1 is no longer published.
Deployment options
The documentation lists the runtimes Docker, Kubernetes, OpenShift, Podman and Nomad, with host preparation (instance,
runtime, network access, DNS and a TLS certificate) described on a separate page, and the compatibility matrix in an IBM
compatibility report. The application can manage its own data storage or connect to externally managed storage.
HashiCorp Validated Designs ship Terraform modules that deploy it, as the registry shows: AWS EC2 and EKS, Azure VM and
AKS, Google Cloud GCE and GKE (hashicorp/terraform-enterprise-hvd/aws and its siblings). The earlier Replicated-based
installation was limited to customers who bought before January 2024, and the documentation gives April 1, 2026 as the end
of its support.
Because it is the same application, the cloud block, the run workflow, the tfe provider and the API work unchanged; the
differences are the hostname of the cloud block (and TF_CLOUD_HOSTNAME), the limits that an administrator sets on the
instance, and a few features that are marked as hosted-only, such as the EU region or HYOK settings in the provider’s
documentation. A configuration that must run on both uses the environment variables for the organization and hostname
instead of constants.
Managing the platform as code: the tfe provider
Almost everything above is available through the API, and the hashicorp/tfe provider exposes it as resources, which is
the way to avoid a platform configured by clicking. One provider covers HCP Terraform and Terraform Enterprise. It is at
version zero, so the documentation recommends constraining the minor version (~> 0.82.0 at the time of writing), and
since provider v0.70.0 it requires Terraform 1.0 or later.
The provider takes a hostname (default app.terraform.io, or TFE_HOSTNAME), a token (or TFE_TOKEN, or credentials
from terraform login) and a default organization (or TFE_ORGANIZATION). To manage the full set of resources the token
should be a user token of a member of the owners team; an organization or team token limits what can be managed and cannot
manage resources across organizations. Mark the token variable sensitive, and never write it in the configuration.
The Bookshelf platform from the layout above, as code:
terraform {
required_version = ">= 1.11"
required_providers {
tfe = {
source = "hashicorp/tfe"
version = "~> 0.82.0"
}
}
}
variable "tfe_token" {
type = string
sensitive = true
}
variable "aws_run_role_arn" {
type = string
description = "ARN of the IAM role that the OIDC trust policy allows; a placeholder in this guide."
}
provider "tfe" {
organization = "example-org"
token = var.tfe_token
}
resource "tfe_project" "bookshelf" {
name = "bookshelf"
description = "Catalog, reviews and web front end"
tags = {
team = "platform"
}
}
resource "tfe_workspace" "apps" {
for_each = toset(["dev", "prod"])
name = "bookshelf-apps-${each.key}"
project_id = tfe_project.bookshelf.id
terraform_version = "~> 1.17"
auto_apply = each.key == "dev"
tags = {
layer = "application"
env = each.key
}
}
resource "tfe_workspace_settings" "apps" {
for_each = tfe_workspace.apps
workspace_id = each.value.id
execution_mode = "remote"
assessments_enabled = each.key == "prod"
}
Source: Terraform Registry — tfe provider, Terraform Registry — tfe_project, Terraform Registry — tfe_workspace, Terraform Registry — tfe_workspace_settings
Two details of that example come from the provider documentation. The execution settings (execution_mode, agent_pool_id)
belong to tfe_workspace_settings; the same arguments on tfe_workspace are deprecated, and the documentation warns
against managing the same property on both resources because it causes permanent drift. And tfe_workspace defaults
queue_all_runs to true, which differs from the API’s default of false, so a freshly created VCS workspace starts
planning immediately.
The credentials, shared across the workspaces through a variable set:
resource "tfe_variable_set" "aws_dynamic_credentials" {
name = "bookshelf-aws-dynamic-credentials"
description = "OIDC role for runs of the Bookshelf workspaces"
global = false
}
resource "tfe_project_variable_set" "bookshelf" {
project_id = tfe_project.bookshelf.id
variable_set_id = tfe_variable_set.aws_dynamic_credentials.id
}
resource "tfe_variable" "aws_provider_auth" {
variable_set_id = tfe_variable_set.aws_dynamic_credentials.id
category = "env"
key = "TFC_AWS_PROVIDER_AUTH"
value = "true"
}
resource "tfe_variable" "aws_run_role" {
variable_set_id = tfe_variable_set.aws_dynamic_credentials.id
category = "env"
key = "TFC_AWS_RUN_ROLE_ARN"
value = var.aws_run_role_arn
}
resource "tfe_variable" "aws_region" {
variable_set_id = tfe_variable_set.aws_dynamic_credentials.id
category = "env"
key = "AWS_REGION"
value = "eu-west-1"
}
Source: Terraform Registry — tfe_variable_set, Terraform Registry — tfe_project_variable_set, Terraform Registry — tfe_variable
tfe_variable_set has global, priority, parent_project_id and description; attach it with
tfe_workspace_variable_set or tfe_project_variable_set (the workspace_ids argument of the set itself is deprecated in
favour of the attachment resources). tfe_variable takes category (terraform or env), key, value, hcl,
sensitive and exactly one of workspace_id or variable_set_id. For a value that must not enter the provider’s own state,
the resource offers the write-only pair value_wo and value_wo_version (Terraform 1.11 or later), which connects this
page to Sensitive Data, Ephemeral Values and Write-Only Arguments.
Teams, agents, run tasks, the registry and the no-code module follow the same pattern:
resource "tfe_team" "bookshelf_developers" {
name = "bookshelf-developers"
}
resource "tfe_team_project_access" "developers" {
access = "write"
team_id = tfe_team.bookshelf_developers.id
project_id = tfe_project.bookshelf.id
}
resource "tfe_agent_pool" "private_network" {
name = "bookshelf-private-network"
organization_scoped = false
}
resource "tfe_agent_token" "private_network" {
agent_pool_id = tfe_agent_pool.private_network.id
description = "token for the agents in the data-centre network"
}
resource "tfe_organization_run_task" "scanner" {
name = "iac-scanner"
url = "https://scanner.example.com/hooks/terraform"
enabled = true
description = "Security scan of the plan"
hmac_key_wo = var.scanner_hmac_key
hmac_key_wo_version = 1
}
resource "tfe_workspace_run_task" "scanner_prod" {
workspace_id = tfe_workspace.apps["prod"].id
task_id = tfe_organization_run_task.scanner.id
enforcement_level = "mandatory"
stages = ["post_plan"]
}
resource "tfe_run_trigger" "apps_after_data" {
workspace_id = tfe_workspace.apps["dev"].id
sourceable_id = tfe_workspace.data_dev.id
}
Source: Terraform Registry — tfe_team_project_access, Terraform Registry — tfe_agent_pool, Terraform Registry — tfe_organization_run_task, Terraform Registry — tfe_workspace_run_task, Terraform Registry — tfe_run_trigger
The fragment assumes a scanner_hmac_key variable and a tfe_workspace.data_dev resource declared elsewhere in the same
configuration; it is a sketch of the argument shapes and not a deployable root module. tfe_agent_token exposes the
generated token as a sensitive attribute, which ends up in state: use a secret store to hand it to the agents, and treat the
state of this configuration as a secret.
The rest of the resource catalogue, in groups:
| Area | Resources (names from the provider documentation) |
|---|---|
Organization |
|
Teams and access |
|
Workspaces |
|
Variables |
|
Policies |
|
Registry |
|
Agents and run tasks |
|
Stacks and OIDC |
|
Data sources complement the resources. tfe_outputs reads another workspace’s outputs, which is the platform-native
alternative to terraform_remote_state (see the data-sharing section of
Backends and Remote State), and the producing workspace must allow the consumer to read its state (remote state sharing).
Chicken and egg: who creates the platform?
The configuration above needs a token and an organization before it can run. The usual order is: create the organization and
the first owner by hand, create a workspace for the platform configuration with the local execution mode or a bootstrap
token, apply it once, then move the platform configuration into a workspace that manages itself. Keep that bootstrap
workspace small, restrict it to the owners team, and give the tfe token the minimum the platform configuration needs.
Other delivery platforms
HCP Terraform is one of several products that wrap Terraform (and, in some cases, OpenTofu) with remote state, runs, policies and drift detection. The commonly named ones are Spacelift, env0, Scalr, Atlantis (open source, driven by pull request comments and self-hosted), Digger and Terrateam (which run in your existing CI), Harness, Octopus Deploy and Terrakube (open source). They differ in how much they own: some replace the CI system and the state backend, some only orchestrate a CI runner you already have, and some are a thin pull-request bot. All of them need the same decisions as this page: where state lives, who approves, how credentials are issued, how a policy stops a change. Evaluate them with the checklist in Choosing between the platform and your own pipeline and with your own constraints (OpenTofu support, self-hosting, licensing, price per run or per resource), and read each product’s own documentation, because pricing and feature sets change and this guide has not compared them. The general CI pipeline alternative, with GitHub Actions, GitLab CI and Atlantis, is in CI/CD and Automation.
Choosing between the platform and your own pipeline
| Question | HCP Terraform or Terraform Enterprise | Your own pipeline and backend |
|---|---|---|
State and locking |
Included, versioned, access-controlled per workspace |
A bucket and a lock mechanism that you secure (see Backends and Remote State) |
Credentials |
Dynamic provider credentials from the workspace, or variable sets |
OIDC federation from the CI runner |
Approval and policy |
Built into the run; policy sets with enforcement levels |
Branch protection, environment approvals, Conftest or similar |
Private network reach |
Agents |
Self-hosted runners |
Cost |
Per managed resource (hosted) or by contract (self-managed) |
Runner minutes and your time |
Lock-in |
The |
Low; the backend is a configuration choice |
A reasonable path for the Bookshelf is to start with the CLI and a remote backend, move to a pipeline when more than one
person applies, and adopt the platform when the governance features (policy, drift detection, the registry, dynamic
credentials, per-team permissions) are worth more than the per-resource price. Because the cloud block can be replaced
by a backend block by migrating state, the decision is reversible.
What changed recently
| When | Change |
|---|---|
2023 |
The enhanced Free tier, with SSO, policy as code, run tasks and agents, was introduced. |
April 22, 2024 |
Terraform Cloud was renamed HCP Terraform. |
Terraform 1.1 |
The |
July 2025 |
The last monthly Terraform Enterprise release, |
September 25, 2025 |
The changelog lists Stacks. |
October 1, 2025 |
EU region launch. |
October 20, 2025 |
Terraform Search and Terraform Actions appear in the changelog (see Import and Query and Provisioners, Actions and Post-Apply Operations). |
October 28, 2025 |
Official Python SDK, |
December 17, 2025 |
Announcement of the end of the legacy Free plan. |
March 31, 2026 |
The legacy Free plan ended; organizations moved to the enhanced Free tier (500 managed resources). |
February 2 and April 1, 2026 |
Project-scoped remote-state sharing; IP allowlists for user and agent access. |
June 8 and June 10, 2026 |
Project-level run tasks; an Actions page that shows declared actions and invocation history. |
July 16 and September 10, 2026 |
Policies written for Terraform policy (beta) are evaluated by HCP Terraform; Terraform policy can evaluate policies against Stacks. |
October 1, 2026 |
SCIM provisioning for user lifecycle management through identity providers. |
October 8, 2026 |
|
Source: HCP Terraform Docs — Changelog, Terraform Enterprise Docs — Releases, hashicorp/terraform-provider-tfe — Releases
The provider version row records the release date and the documentation state of that version; the individual resource changes are listed in the provider’s changelog.
Where the books are outdated or wrong
Both reference books call the product Terraform Cloud, which was correct when they were written and has been wrong since
April 22, 2024. The pricing and free-tier figures in the continuous-delivery chapter of the more recent book are outdated
for the reasons in Pricing and the free tier: the limit is now on managed resources (500 on the enhanced Free tier), the legacy
member-based Free plan ended on March 31, 2026, and the paid plans are Essentials, Standard and Premium with per-resource
pricing. Where a book shows static cloud keys stored in workspace variables, the current approach is dynamic provider
credentials. And the monthly vYYYYMM-N naming of Terraform Enterprise releases has been replaced by semantic versions.
Pitfalls
-
Setting provider credentials only in your shell and wondering why a remote run cannot authenticate: remote runs use workspace variables.
-
Using
TF_WORKSPACEin a shell profile and applying to the wrong workspace; the CLI docs recommend it for non-interactive use only. -
Expecting
TF_WORKSPACEto create a workspace: it must already exist. -
Writing
nameandtagstogether in thecloudblock. -
Trusting a speculative plan from a stale pull-request branch.
-
Running
terraform importin a remote workspace and expecting the workspace’s environment variables; useimportblocks. -
Managing
execution_modeon bothtfe_workspaceandtfe_workspace_settings, which causes permanent drift. -
An OIDC trust policy that checks neither
audnor the organization insub, so another organization can assume the role. -
Combining dynamic credentials with static authentication variables (
ARM_CLIENT_ID,GOOGLE_CREDENTIALS, access keys). -
Sharing a workspace’s state with the whole organization when two consumers need it.
-
Marking a variable sensitive and assuming it never appears anywhere: it can still appear in logs and state.
-
Forgetting that a run locks the variable values when it starts, so a fix to a variable does not rescue a queued run.
-
Counting on the free tier without watching the 500-resource limit and the single concurrent run.
-
Treating an agent token or an organization token as harmless in state.