Terraform Lesson 13 of 57

HCP Terraform (Terraform Cloud), In Depth: Workspaces, VCS-Driven Runs, Remote State & the Private Registry

Run Terraform on your laptop and four problems appear the moment a second person joins: where does the state live so you don’t both edit it at once, where do the AWS credentials live so they aren’t on everyone’s disk, who reviewed the plan before it touched production, and how do you stop two people applying at the same time? You can solve each one piecemeal — a remote backend for state, a secrets manager for credentials, a CI pipeline for review, a lock table for concurrency — and many teams do. HCP Terraform (the cloud platform formerly called Terraform Cloud, and still widely called that) bundles all four solutions into one managed product, then adds the things a piecemeal stack rarely gets right: a private registry for your modules, policy-as-code gates between plan and apply, cost estimates on every change, and a permission model that lets a platform team hand self-service infrastructure to developers without handing over the cloud keys.

This lesson is the complete tour of that platform from the ground up. It is written to be beginner-accessible — every term is defined the first time it appears — but it is deliberately exhaustive, because the whole reason to learn a managed platform is to know which of its many knobs to turn. We will build the mental model (organisations → projects → workspaces), work through the three workspace workflows (VCS-driven, CLI-driven and API-driven) and exactly when each fits, dig into remote operations and the three execution modes, see how state is hosted, versioned, locked and shared, master variables and variable sets, walk the full run lifecycle from plan through cost estimation and policy checks to apply (with run triggers and run tasks), publish to the private module and provider registry, ship no-code modules, and lock the whole thing down with teams and RBAC. Throughout I flag where the free tier stops and a paid tier begins, because that boundary shapes a lot of real decisions. Where OpenTofu differs (it has no equivalent SaaS — the model is the open-source backend plus your own tooling), I say so.

Learning objectives

After working through this lesson you will be able to:

Prerequisites & where this fits

You should already be comfortable with the core Terraform loop — init, plan, apply, destroy — and with the idea of state (Terraform’s record of what it has built) and a backend (where that state is stored). If those are fuzzy, read Terraform Fundamentals: HCL, Providers, State & the Core Workflow first, and Terraform Backends, In Depth: Local vs Remote, Every Backend Type, Locking & Migration for how state storage works in general — this lesson is the managed-platform counterpart to that one. This is an Intermediate lesson in the Terraform Zero-to-Hero ladder: it sits after you can drive the CLI and write modules, and it underpins the later governance lessons (Sentinel/OPA policy, multi-environment delivery). A free HCP Terraform account is all you need for the hands-on lab; no credit card and no cloud spend are required.

A quick note on names. HashiCorp renamed Terraform Cloud to HCP Terraform in 2024 (HCP = HashiCorp Cloud Platform). They are the same product; documentation, blog posts and the CLI still use both names interchangeably, and the self-hosted, customer-installed edition is called Terraform Enterprise. I use HCP Terraform throughout and treat “Terraform Cloud” as a synonym.

Core concepts: the platform mental model

Five ideas carry the whole platform. Fix them and everything else slots into place.

A workspace is a unit of everything, not a folder. This is the single biggest source of confusion for newcomers, especially anyone who has used CLI workspaces (the terraform workspace command). An HCP Terraform workspace is a completely different thing: it is a managed container that bundles one state file, its own set of variables, a run history, a configuration source (a VCS branch, or uploaded local config) and its own permissions. One workspace typically maps to one environment of one component — for example “production networking” or “staging payments-api”. Where a local project keeps environments apart with directories or CLI workspaces, HCP Terraform keeps them apart with separate workspaces. (The CLI-workspaces-versus-HCP-workspaces distinction is so important it gets its own lesson next; see Next steps.)

Runs are remote and serialised. When you trigger a change, HCP Terraform performs the plan and apply on its own infrastructure (or on an agent you host), streams the logs to the UI, and — crucially — serialises runs per workspace: a workspace processes one run at a time and queues the rest. That single fact removes the entire class of “two applies corrupted the state” incidents, with no lock table to manage. This is called a remote operation.

The hierarchy is organisation → project → workspace. An organisation is your top-level tenant (your company, or a team within it) — it owns billing, membership, teams, the VCS connections, the private registry and policies. A project is a folder that groups related workspaces and is the main unit for permissions and organisation at scale. A workspace lives inside exactly one project. New organisations get a “Default Project” so you needn’t think about projects until you have enough workspaces to want grouping.

Variables are data and credentials, stored centrally. Instead of .tfvars files on disk and cloud keys in your shell, HCP Terraform stores Terraform variables (inputs to your config) and environment variables (like AWS_ACCESS_KEY_ID) in the workspace, with the option to mark any of them sensitive (write-only, never shown again). Reusable bundles called variable sets let you define a credential or a tag once and attach it to many workspaces or whole projects.

Governance lives between plan and apply. The platform’s reason for being, beyond convenience, is the gate it puts in the middle of every run: after plan and before apply it can run a cost estimate, evaluate policies (Sentinel or OPA) that can warn or block, and fire run tasks that call external systems (security scanners, CMDBs). A change isn’t just “does it apply cleanly?” but “does it comply, and what will it cost?”.

Key terms used throughout: organisation (top-level tenant), project (workspace group + permission boundary), workspace (state + vars + runs + source + permissions), run (one plan/apply cycle), remote operation (a run executed by HCP, not your laptop), VCS (version control system — GitHub, GitLab, etc.), variable set (reusable bundle of variables), run task (external integration hook), policy set (a bundle of Sentinel/OPA policies), and agent (a self-hosted worker that runs operations inside your network).

The hierarchy: organisations, projects and workspaces

Everything you create lives inside an organisation. You sign up at app.terraform.io, create or join an organisation, and from then on that org is the tenant boundary: members, teams, billing, VCS connections, the private registry, run tasks and policies all belong to it. You can belong to several organisations (e.g. a personal one and your employer’s) and switch between them in the UI.

Inside an org, projects group workspaces. A project does three jobs:

  1. Organisation — it’s a folder, so “all the payments-team workspaces” or “all of prod” are easy to find.
  2. Permissions — you grant a team access to a project, and that access applies to every workspace in it. This is how you scale RBAC without setting permissions workspace-by-workspace.
  3. Project-scoped settings — variable sets and some policy attachments can be scoped to a whole project, so every workspace in it inherits them.

A workspace is the working unit. The table below is the mental anchor — what each level owns:

Level Owns Typical mapping Permissions granted at this level
Organisation Members, teams, billing, VCS connections, private registry, run tasks, policy sets, org-wide variable sets Your company or a business unit Org owners; org-level permissions (manage policies, manage VCS, etc.)
Project A group of workspaces; project-scoped variable sets/policies A team, an application, or an environment tier Team access to all workspaces in the project (read/plan/write/admin/custom)
Workspace One state file, its variables, run history, configuration source, run triggers, notifications One environment of one component (e.g. prod-network) Team access to this workspace (when finer than project-level)

A widely used naming convention is <component>-<environment> (e.g. networking-prod, api-staging) so workspaces sort sensibly, but HCP Terraform doesn’t enforce one. The free tier allows a generous number of workspaces; the limits that bite earlier are concurrency (how many runs execute at once) and the paid-only features, both covered below.

Workspace settings you will actually touch

Each workspace has a settings area. The fields you set most often:

Setting What it controls Notes / default
Execution mode Where runs execute: Remote, Local, or Agent Default Remote. Covered in detail below.
Apply method Manual apply (a human confirms each apply) or Auto-apply Default manual. Auto-apply suits low-risk/dev workspaces and fully-gated pipelines.
Terraform version Which Terraform (or OpenTofu) version runs here Pin it; “latest” can surprise you when a new release lands.
Working directory Subdirectory of the repo that is the root module For monorepos where one repo holds many roots.
VCS branch / tags Which branch triggers runs, or trigger on tags instead Default the repo’s default branch.
Trigger patterns / paths Only run when files under these paths change (monorepo) Avoids running every workspace on every commit.
Run triggers Start a run here when another workspace applies Cross-workspace pipelines (see Run lifecycle).
Notifications Slack/email/webhook on run events Per-workspace.
Auto-destroy Schedule or inactivity-based automatic destroy Great for ephemeral/dev workspaces to control cost.
Global remote state sharing Whether other workspaces in the org may read this state Default off (you allowlist specific workspaces).

The three workspace workflows

A workspace gets its configuration and starts runs through one of three workflows. This choice is the most important decision when you create a workspace, because it shapes the whole day-to-day experience.

VCS-driven CLI-driven API-driven
Config source A connected VCS repo + branch Uploaded from your machine by terraform Uploaded as a “configuration version” via the API
What triggers a run A push/merge to the tracked branch (apply); a pull request (speculative plan) You run terraform plan/apply locally; execution happens remotely Your script/system POSTs a config and starts a run
Where plan/apply execute On HCP Terraform (remote) On HCP Terraform (remote) — your CLI is just the trigger and log viewer On HCP Terraform (remote)
Best for The default for teams — GitOps, PR review, audit trail Individual dev loops, debugging, machines without VCS hooks Custom platforms, CI systems, building your own automation
How you set it up Connect a VCS provider, point the workspace at a repo Add a cloud {} block (or remote backend) to your config, terraform login Use the API / tfe Terraform provider / a tool like the TFE provider or tfci
Speculative plans on PRs Yes — automatic Yes — terraform plan is speculative by default here Yes — create a “speculative” configuration version

VCS-driven (the team default)

You connect HCP Terraform to your VCS provider once (GitHub, GitHub Enterprise, GitLab, Bitbucket, Azure DevOps — see “Connecting a VCS provider”), then point a workspace at a specific repository and branch. From then on the workflow is GitOps:

This is the recommended pattern because the repository is the single source of truth, every change is reviewed and audited, and nobody applies from a laptop. You never run terraform apply by hand; you merge a PR. Trigger paths/patterns keep monorepos sane — a workspace only runs when files under its directory change.

CLI-driven

Here your local terraform is the trigger, but the heavy lifting still happens remotely. You add a cloud {} block to your configuration naming the org and workspace(s), run terraform login once to authenticate, and then terraform plan / terraform apply behave almost exactly as locally — except the operation runs on HCP Terraform, uses the workspace’s stored variables and state, and streams logs back to your terminal and the UI. terraform plan in this mode is speculative (plan-only) by default; terraform apply performs a real, confirmable apply. This is ideal for the inner-loop dev experience, for debugging a workspace, and for environments where wiring up VCS webhooks is awkward.

# main.tf — the cloud block makes this a CLI-driven HCP Terraform workspace
terraform {
  cloud {
    organization = "kloudvin"

    workspaces {
      name = "playground"   # a single named workspace
      # or, to map many CLI workspaces to a set of HCP workspaces:
      # tags = ["app:web", "team:platform"]
      # project = "web-platform"
    }
  }
}

terraform login stores an API token in ~/.terraform.d/credentials.tfrc.json; thereafter the CLI talks to HCP Terraform on your behalf.

API-driven

The most flexible and the most work. You drive everything over the HCP Terraform API: create a configuration version, upload a tarball of your config to it, then create a run against a workspace. This is how you build your own platform on top of HCP Terraform, or integrate it into a CI system that isn’t a supported VCS. Most people don’t call the raw API by hand — they use the tfe Terraform provider (to manage orgs/workspaces/variables/teams as code), the official Go client, or helper tooling. The API-driven workflow still gives you remote execution, stored state and the full run pipeline; you simply control when and what programmatically.

A subtle but important point: the three workflows describe how config arrives and how runs start, not where execution happens. In all three, plan/apply run remotely by default (that’s execution mode, covered next). You can mix them across workspaces in one org.

Remote operations and execution modes

A remote operation is a plan or apply that HCP Terraform performs on a worker rather than on your machine. The benefits are why people adopt the platform: credentials and state never leave the platform, runs are serialised so they can’t collide, logs are centralised and audited, and your laptop can be closed mid-apply without consequence.

Each workspace has an execution mode that decides where the operation actually runs:

Execution mode Where plan/apply run State stored in HCP? When to use Notes
Remote (default) On HCP Terraform’s managed compute Yes Almost always — the standard, fully-managed experience Streams logs to UI + CLI; your machine just triggers/watches.
Local On your machine (or your CI runner) Yes (HCP still hosts and locks state) When you need full local control of the runtime, or special tooling not on the remote runners You get HCP’s state + locking + variables, but execution and any required network access are yours.
Agent On a self-hosted agent inside your network Yes Private/air-gapped infrastructure HCP can’t reach (on-prem, private VPC, VMware) Requires the agents feature (paid tiers). The agent dials out to HCP, so no inbound firewall holes.

Local execution mode deserves a clarification because the name is confusing: it does not mean “use a local backend”. State is still hosted and locked in HCP Terraform; only the execution happens locally. You’d choose it when the remote runners lack something your run needs (a custom provider binary, a license daemon, a specific OS), while still wanting HCP’s state, variables and locking.

Agents (self-hosted runners)

When the resources you manage live somewhere HCP Terraform’s cloud cannot reach — an on-prem vCenter, a private database with no public endpoint, a VPC with no inbound access — you deploy a HCP Terraform agent. An agent is a lightweight process you run inside that network; it makes an outbound connection to HCP Terraform and pulls jobs to execute locally, then streams results back. Because the connection is outbound-only, you open no inbound firewall ports. Agents are organised into agent pools, and a workspace in Agent execution mode is assigned a pool. This feature is part of the paid tiers (it was historically a Business/Plus-tier capability), so on the free tier you’ll use Remote (or Local) execution.

State management in HCP Terraform

When a workspace uses HCP Terraform, state is hosted by the platform — you do not configure an S3 bucket or an Azure storage account. This is the managed equivalent of a remote backend, and it brings several things for free:

Sharing state between workspaces

Real systems span workspaces — the network workspace produces a VPC ID the application workspace needs. HCP Terraform supports two patterns:

1. terraform_remote_state (read another workspace’s outputs). A consumer workspace declares a data source pointing at the producer workspace; it reads that workspace’s published outputs (only outputs — never arbitrary resources). The producer must allow the consumer to read its state.

# In the consumer (e.g. the app workspace), read the network workspace's outputs
data "terraform_remote_state" "network" {
  backend = "remote"
  config = {
    organization = "kloudvin"
    workspaces = {
      name = "networking-prod"
    }
  }
}

resource "aws_instance" "app" {
  subnet_id = data.terraform_remote_state.network.outputs.private_subnet_id
  # ...
}

Remote state sharing must be authorised. In the producer workspace’s settings you either turn on global remote state sharing (any workspace in the org may read it — convenient but broad) or, better, allowlist specific workspaces. Without this, the consumer’s read is denied. This allowlist is itself a governance control: it makes cross-workspace dependencies explicit and auditable.

2. The tfe_outputs data source. The tfe provider offers a tfe_outputs data source that reads a workspace’s outputs directly via the API. It’s an alternative to terraform_remote_state that doesn’t require the consumer to use the remote backend, and it can read non-sensitive outputs from workspaces you have API access to. Use terraform_remote_state for the classic in-config dependency; reach for tfe_outputs when you’re orchestrating from the tfe provider side.

Cross-stack composition at scale — when to split state, how to structure producers and consumers, and the trade-offs — is covered in depth in Terraform Remote State at Scale. Here the point is simply how HCP Terraform exposes the mechanism.

Variables and variable sets

HCP Terraform replaces on-disk .tfvars and shell-exported credentials with stored variables on the workspace. There are two kinds, and the distinction matters:

Variable category What it is Example How your config sees it
Terraform variable An input to your configuration — exactly a variable "x" {} instance_count = 3, region = "ap-south-1" As var.x in HCL
Environment variable An OS environment variable set in the run’s shell AWS_ACCESS_KEY_ID, TF_LOG=DEBUG As an env var to the provider/CLI (e.g. the AWS provider reads AWS_*)

Either kind can be marked:

So the standard pattern is: Terraform variables carry your inputs (sizes, names, counts, toggles), and environment variables carry your cloud credentials (AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY, ARM_CLIENT_ID/ARM_CLIENT_SECRET, GOOGLE_CREDENTIALS), marked sensitive. This keeps secrets off every laptop. Better still, use dynamic provider credentials (workload identity / OIDC) so HCP Terraform exchanges a short-lived token with AWS/Azure/GCP per run and you store no long-lived cloud secret at all.

Variable sets — define once, attach widely

A variable set is a named, reusable bundle of variables you attach to many workspaces. This is how you avoid copy-pasting the same credential or the same standard tags into fifty workspaces. A variable set can be:

Typical uses: a “prod-aws-creds” set attached to all prod workspaces; a “standard-tags” set applied globally; a “datadog-api-key” set on the workspaces that need it.

Variable precedence (the gotcha)

When the same variable is defined in more than one place, HCP Terraform resolves it by precedence. From lowest to highest (later wins):

  1. Environment variables on the host (for local execution) / defaults.
  2. Variable sets applied to the workspace (and among overlapping sets, more specific scope — workspace-specific — wins over global).
  3. Workspace-level variables (set directly on the workspace) — these override variable sets.
  4. Run-specific variables supplied via the CLI (-var/-var-file) or the API for that run — highest.

The practical rule to memorise: a value set directly on the workspace beats the same key coming from a variable set, and a -var on a CLI-driven run beats both. This lets a variable set provide a sane default that any single workspace can override locally. Auto-loaded *.auto.tfvars files committed to the repo are also read in VCS/remote runs and behave like config-provided defaults — keep secrets out of them.

The run lifecycle, in full

A run is one plan/apply cycle for a workspace, and understanding its stages is the heart of operating the platform. The default path is plan → cost estimation → policy check → apply, with optional run tasks hooked in at defined points. Runs are queued and serialised per workspace — one at a time.

The stages, in order:

Stage What happens Can it block the run? Tier
Pending / Queued The run waits its turn (workspace processes one run at a time) All
Plan Terraform computes the diff remotely; logs stream live Yes — a plan error stops the run All
Cost estimation HCP estimates the monthly cost delta of the planned change (supported AWS/Azure/GCP resources) No — informational; policies can act on it Paid (and free for some accounts; informational)
Run tasks (post-plan) External integrations are called with the plan data; they return pass/fail/advisory Yes if a task is mandatory and fails Paid
Policy check Sentinel or OPA policies evaluate the plan Yes — depends on enforcement level (see below) Paid (governance)
Apply (awaiting confirmation) With manual apply, a human reviews and clicks Confirm & Apply Human gate All
Run tasks (pre-apply) Optional tasks run just before apply Yes if mandatory and failing Paid
Apply Terraform executes the diff remotely; state is written and a new state version stored All
Run tasks (post-apply) Optional notifications/integrations after apply No (post-hoc) Paid
Completed / Errored / Discarded Terminal states All

You can also discard a planned run instead of applying it, and cancel a running operation. A run can be a speculative plan (plan-only, never applies — what PRs produce) or a destroy run (plans the destruction of everything in the workspace, gated like any other).

Cost estimation

For supported resources across the big clouds, HCP Terraform shows the estimated monthly cost of the resources in the plan and the delta versus the current state (what this change adds or removes per month). It’s an estimate based on public pricing, not a billing guarantee, but it turns “this PR changes some infra” into “this PR adds ≈ ₹X/month” — and a policy can read the cost estimate and block a run that would, say, breach a budget. Cost estimation is an informational feature; acting on it with policy is a governance (paid) capability.

Policy checks: Sentinel and OPA

Between plan and apply, HCP Terraform can evaluate policy as code against the plan, the prior state and the run metadata. Two engines are supported: Sentinel (HashiCorp’s own language) and OPA/Rego (the Open Policy Agent standard). Policies are grouped into policy sets and attached to workspaces or projects. Each policy carries an enforcement level:

Enforcement level Effect when the policy fails
advisory Logs a warning; the run continues.
soft-mandatory Blocks the run, but an org owner / authorised user can override and proceed.
hard-mandatory Blocks the run; no override — the change cannot apply until the config complies.

This is how you enforce “no public S3 buckets”, “every resource carries a cost-centre tag”, “only approved regions”, or “monthly cost delta under ₹Y” — automatically, on every run, before anything is created. Policy as code is a paid/governance capability.

Authoring, testing offline with mocks, and shipping Sentinel policy sets is covered hands-on in Enforcing Governance with HashiCorp Sentinel Policy Sets and Mocks. Here we only place policy in the run pipeline.

Run tasks

A run task is an integration point that calls an external HTTP service at a defined stage of the run (post-plan, pre-apply, or post-apply), passing run/plan data; the service responds and can pass or fail the run. Run tasks are how third parties plug in: infra security scanners (Snyk, Wiz, Bridgecrew/Prisma), drift/FinOps tools, ServiceNow change tickets, custom webhooks. A run task can be advisory (informational) or mandatory (a failure blocks the run). Run tasks are a paid feature.

Run triggers (chaining workspaces)

A run trigger wires workspaces into a pipeline: configure workspace B to be triggered by workspace A, and every time A completes a successful apply, B automatically starts a run. The canonical use is layered infrastructure — when networking-prod applies, kick off app-prod so it picks up the new outputs. A workspace can be triggered by several source workspaces. This is HCP Terraform’s built-in way to model dependencies between state stacks without an external orchestrator.

Notifications

Each workspace can send notifications on run events (needs approval, applied, errored, etc.) to Slack, Microsoft Teams, email, or a generic webhook — so the team learns a prod apply needs a click without watching the UI.

The private registry

A core reason to adopt HCP Terraform is the private registry — your organisation’s own version of registry.terraform.io, holding modules and providers only your org can see. It has three parts:

Private modules. Publish a module from a connected VCS repository (the repo must follow the terraform-<PROVIDER>-<NAME> naming convention and use semver Git tags like v1.2.0). The registry tracks versions automatically as you push new tags, renders the module’s docs/inputs/outputs, and gives consumers a clean source address:

module "vpc" {
  source  = "app.terraform.io/kloudvin/vpc/aws"
  version = "~> 2.1"
  # ...inputs...
}

Versioning is real semver, so version = "~> 2.1" pins consumers safely and terraform init resolves from your registry. This gives you a governed, discoverable, versioned module catalogue instead of git:: URLs scattered through your code. (How module sources and versioning work in general — registry vs Git vs local — is its own topic; here the point is the private registry.)

Private providers. The registry can also host private/internal providers (and mirror public ones), so teams that write custom providers distribute them internally with version constraints, just like modules.

Public module/provider curation. Org admins can mark which public registry modules/providers are approved, presenting a curated catalogue to developers.

No-code modules (self-service without writing HCL)

A standout feature for platform teams is no-code provisioning. You take a private-registry module, mark a specific version as no-code ready, and define which of its variables are user-editable (with defaults for the rest). Developers can then, from the UI, click “Provision workspace”, fill in a small form, and HCP Terraform creates a new workspace and applies that module for them — without writing or even seeing any Terraform. This is how a platform team offers “give me a standard S3 bucket” or “spin me a sandbox VPC” as a self-service button while retaining full control of the underlying module, its guardrails and its version. No-code modules turn the registry from a code catalogue into a self-service portal. (No-code provisioning is a paid-tier capability.)

Teams and RBAC

HCP Terraform’s permission model is what lets a platform team safely delegate. The unit of permission is the team (a named group of users), and you grant teams access at the organisation, project and workspace levels.

Organisation-level permissions are coarse, org-wide capabilities granted to a team — for example “manage policies”, “manage VCS settings”, “manage the private registry”, “manage run tasks”, or full owners (who can do everything, including billing and membership). The built-in owners team is created with the org.

Project-level access grants a team a permission over a project (and thus all workspaces in it). Workspace-level access does the same for a single workspace when you need it finer than the project. Both use the same set of access levels:

Access level What the team can do Typical recipient
Read View runs, state, variables (non-sensitive), outputs Auditors, read-only stakeholders
Plan Read + queue speculative plans (but not apply) Developers proposing changes for review
Write Plan + apply runs, lock/unlock, edit variables Engineers who operate the workspace
Admin Write + manage workspace/project settings, permissions, delete Workspace/project owners
Custom Pick exactly which permissions (e.g. apply but not manage state, manage variables but not runs) Fine-grained least-privilege needs

The pattern that makes self-service safe: developers get Write on their own project’s workspaces (so they apply their app) but the cloud credentials live in variable sets they cannot read (sensitive) and the guardrails live in policy they cannot bypass. They ship infrastructure; they never hold the AWS keys and they can’t breach the rules. That separation — self-service for developers, control for the platform team — is the entire value proposition.

Teams and SSO. On paid tiers you can drive team membership from your identity provider via SSO (SAML/OIDC), so joining a team in your IdP grants the matching HCP Terraform access. Multiple teams and SSO are paid-tier features; the free tier includes the owners team and a limited number of additional teams/users.

Connecting a VCS provider

To use the VCS-driven workflow or to publish modules from repos, you connect HCP Terraform to your VCS provider once, at the organisation level. Supported providers include GitHub (and GitHub Enterprise), GitLab (SaaS and self-managed), Bitbucket (Cloud and Data Center) and Azure DevOps. The connection is an OAuth app / GitHub App authorisation that lets HCP Terraform:

You authorise it in Settings → Version Control / Providers, complete the OAuth handshake, and thereafter workspaces can pick any accessible repo. For GitHub, the GitHub App integration is preferred over the older OAuth app (finer-grained repo access, easier management). Once connected, the VCS-driven loop described earlier “just works”: PR → speculative plan as a status check; merge → real run.

cloud {} block vs remote backend vs HCP vs Enterprise

A point of frequent confusion: how does your configuration opt in to HCP Terraform, and how do the product editions relate?

Two ways to connect config to HCP Terraform:

Mechanism Syntax Use it when
cloud {} block (modern) A cloud {} block inside terraform {} with organization + workspaces (by name or tags) The current, recommended way. Supports mapping CLI workspaces to many HCP workspaces by tag, and is what HashiCorp documents going forward.
remote backend (legacy) backend "remote" { ... } with organization + workspaces Older configs; still works. The cloud {} block superseded it — prefer cloud {} for new work.

Both make the workspace use HCP Terraform for state and remote runs; the cloud {} block is simply the newer, more capable surface (notably the tags-based mapping). You cannot use both at once.

The product editions:

Edition What it is Where it runs Who operates it
HCP Terraform (SaaS) The hosted multi-tenant platform at app.terraform.io HashiCorp’s cloud HashiCorp
Terraform Enterprise The same product, self-installed Your infrastructure (on-prem / your cloud) You — for data-residency, air-gap, or compliance needs
OpenTofu The open-source CLI/engine fork Anywhere You — there is no OpenTofu SaaS equivalent

This last row matters for OpenTofu users: OpenTofu is a drop-in for the CLI and language, but it has no managed platform. If you adopt OpenTofu, you assemble the equivalent of HCP Terraform yourself — a remote backend (S3/GCS/Azure/Postgres) for state, your own CI for runs and PR plans, OPA/Conftest for policy, and your own module sources. HCP Terraform’s value is precisely that it pre-integrates all of that. (OpenTofu can use HCP Terraform as a state backend in some configurations, but it does not get HCP’s remote run/governance pipeline.)

Pricing tiers

The tier boundary shapes real decisions, so know roughly where it falls. HashiCorp’s plans have evolved; the durable shape is:

Tier Cost model What you get What you don’t
Free Free, capped on resources under management (RUM) and limited concurrency Orgs, projects, unlimited workspaces (within RUM), VCS/CLI/API workflows, remote runs, hosted state with versions/locking, the private module/provider registry, a limited number of users/teams Self-hosted agents, policy as code (Sentinel/OPA), run tasks, no-code provisioning, SSO, audit logging, concurrency add-ons
Standard Paid per resource under management Everything in Free plus team management & SSO, more concurrency The full governance suite of higher tiers
Plus / Premium (and Enterprise self-hosted) Paid, contract The governance suite: policy as code, run tasks, agents, no-code modules, audit logging, drift detection, advanced concurrency

The practical reading for a learner or a small team: the free tier is genuinely useful — you get remote runs, hosted/locked state, all three workflows and the private registry at no cost, enough to run real projects and do everything in this lesson’s lab. The features you “graduate” into paying for are the governance ones — policy, run tasks, agents, no-code, SSO — exactly the controls a larger org needs. (Plan names and the precise RUM thresholds change over time; check current pricing before committing.)

How HCP Terraform organises work and runs a change: an organisation contains projects, each holding workspaces that each bundle their own state, variables and run history; a VCS pull request triggers a speculative plan while a merge triggers a full run that flows plan to cost estimation to policy check to apply, with variable sets feeding credentials in, run triggers chaining one workspace to the next, and the private registry serving modules to all of them

The diagram traces a change from a Git push through the organisation → project → workspace hierarchy and along the full run pipeline, showing where variable sets, policy checks, run triggers and the private registry plug in.

Hands-on lab: a CLI-driven workspace on the free tier

This lab uses the free tier and the local/random providers, so it touches no cloud and costs nothing — yet it exercises real HCP Terraform: a remote workspace, remote runs, hosted/locked state, a stored variable, and the state UI. (You can swap in a real cloud provider with sensitive credential variables later; the mechanics are identical.)

Prerequisites: the Terraform (or OpenTofu) CLI installed, and a free account at app.terraform.io.

1. Create an organisation and authenticate

Sign in at app.terraform.io, create an organisation (e.g. kloudvin-lab). Then log the CLI in:

terraform login
# Opens a browser to generate a token; paste it back. Stored in
# ~/.terraform.d/credentials.tfrc.json

(OpenTofu: tofu login works the same way against HCP Terraform.)

2. Write a tiny config wired to HCP Terraform

# main.tf
terraform {
  cloud {
    organization = "kloudvin-lab"
    workspaces {
      name = "hello-hcp"   # created on first init if it doesn't exist
    }
  }
  required_providers {
    random = { source = "hashicorp/random", version = "~> 3.6" }
  }
}

variable "greeting" {
  type    = string
  default = "hello"
}

resource "random_pet" "name" {
  prefix = var.greeting
  length = 2
}

output "pet" {
  value = random_pet.name.id
}

3. Init — this creates the remote workspace

terraform init
# Initialises the cloud backend and creates the "hello-hcp" workspace
# in your org if it doesn't already exist.

Open the HCP Terraform UI — you’ll see the new workspace under your default project, in Remote execution mode.

4. Set a variable in the UI, then plan and apply remotely

In the workspace, go to Variables and add a Terraform variable greeting = "kloudvin" (category: Terraform). Now run:

terraform plan
# Runs REMOTELY on HCP Terraform; logs stream to your terminal AND the UI.
# Plan shows random_pet.name will be created with prefix from var.greeting.

terraform apply
# Remote apply; with manual apply you confirm here (or in the UI).

Expected: the apply creates random_pet.name and prints an output like pet = "kloudvin-clever-otter". Note that the greeting you set in the UI (kloudvin) overrode the config default (hello) — that’s variable precedence in action.

5. Validation — confirm state and runs are remote

terraform output -raw pet
# Reads the output from HCP-hosted state, not a local file.

terraform state list
# random_pet.name — pulled from remote state

In the UI: open the workspace’s States tab — you’ll see a state version from the apply (download/diff available). Open Runs — you’ll see the plan and apply with full logs. There is no terraform.tfstate file on your disk — state lives in HCP Terraform.

6. (Optional) See locking/serialisation

Trigger a terraform apply and, while it waits or runs, start a second terraform plan from another terminal — the second run queues behind the first. That’s per-workspace serialisation: no two runs collide.

Cleanup

terraform destroy
# Remote destroy run; removes random_pet from state.

Then, in the UI, delete the workspace (Settings → Destruction and Deletion → Delete workspace) — for a workspace with no real cloud resources this is safe. Since you used only the random provider, nothing was created in any cloud and there is no bill.

Cost note

This entire lab runs on the free tier with the random provider, so it incurs ₹0. The only “resource under management” was a random_pet, which is trivial. When you later point a workspace at AWS/Azure/GCP, costs come from the real resources you create — not from HCP Terraform’s free tier itself.

Common mistakes & troubleshooting

Symptom Likely cause Fix
terraform apply runs locally and writes terraform.tfstate on disk, ignoring HCP No cloud {} block (or remote backend) in the config, or you forgot to init after adding it Add the cloud {} block, run terraform init (migrate state if prompted), confirm the workspace appears in the UI.
Error: Required token could not be found Not logged in to HCP Terraform from the CLI Run terraform login; it stores a token in ~/.terraform.d/credentials.tfrc.json.
Cross-workspace terraform_remote_state read is denied The producer workspace hasn’t authorised the consumer In the producer’s settings, enable remote state sharing for the consumer (allowlist it) or turn on global sharing.
Provider auth fails on remote runs but works locally Credentials are in your shell, not in the workspace Add cloud creds as sensitive environment variables on the workspace (or via a variable set), or use dynamic provider credentials (OIDC).
A monorepo pushes one commit but every workspace runs No trigger paths/patterns configured Set VCS trigger patterns (paths) per workspace so only relevant changes start a run.
A variable set value is “ignored” by a workspace The workspace defines the same variable directly — workspace-level overrides variable sets Remove the workspace-level variable, or accept the override (it’s the documented precedence).
PR shows no plan / no status check VCS connection or webhook misconfigured, or the workspace tracks a different branch Re-check the VCS provider connection, the workspace’s branch/working directory, and that the repo’s webhook is healthy.
“Latest” Terraform version broke a run after an upgrade Workspace set to “latest” instead of a pinned version Pin the workspace’s Terraform version (and your config’s required_version) so upgrades are deliberate.

Best practices

Security notes

Interview & exam questions

1. What is an HCP Terraform workspace, and how is it different from a CLI workspace? An HCP Terraform workspace is a managed container bundling one state file, its own variables, run history, a configuration source and its own permissions — typically one environment of one component. A CLI workspace (the terraform workspace command) is just multiple named state instances behind one local configuration and backend. They share a word but are unrelated concepts; HCP workspaces are the unit of isolation in the platform.

2. Name the three workspace workflows and when you’d use each. VCS-driven (config from a connected repo; PR → speculative plan, merge → run) — the team default for GitOps and review. CLI-driven (cloud {} block; terraform plan/apply triggers remote runs) — the inner dev loop and debugging. API-driven (upload config versions and create runs via the API) — building your own platform/CI integration.

3. Explain the three execution modes. Remote (default) runs plan/apply on HCP’s compute. Local runs them on your machine/CI while HCP still hosts and locks state and supplies variables. Agent runs them on a self-hosted agent inside your network (outbound-only connection) for private/air-gapped infrastructure — a paid feature.

4. Why don’t two simultaneous applies corrupt state in HCP Terraform? Runs are serialised per workspace — a workspace processes one run at a time and queues the rest, and state is locked for the duration of a run. There’s no lock table to manage; concurrency control is built in.

5. What’s the difference between a Terraform variable and an environment variable in a workspace? A Terraform variable is an input to your config, seen as var.x. An environment variable is set in the run’s shell (e.g. AWS_ACCESS_KEY_ID, TF_LOG) and read by providers/the CLI. Credentials usually go in (sensitive) environment variables; inputs go in Terraform variables.

6. Describe variable precedence when a variable set and a workspace both define the same key. Workspace-level variables override variable sets. Overall, lowest-to-highest: defaults/host env → variable sets (more-specific scope beats global) → workspace variables → run-specific -var/API values (highest). A -var on a CLI-driven run wins over everything.

7. Walk through the run lifecycle. Queued → plancost estimation → (post-plan run tasks) → policy check (Sentinel/OPA) → awaiting confirmation (manual apply) → (pre-apply run tasks) → apply (writes a new state version) → (post-apply run tasks) → completed. Speculative plans stop after plan and never apply.

8. What are the three policy enforcement levels and what does each do? advisory (warn, continue), soft-mandatory (block, but an authorised user can override), hard-mandatory (block with no override). They let you tune how strictly a policy gates a run.

9. How does one workspace consume another’s outputs, and what authorisation is needed? With the terraform_remote_state data source (or tfe_outputs) reading the producer’s outputs. The producer must share its state with the consumer — either global remote state sharing or, preferably, an allowlist of specific consumer workspaces.

10. What is a no-code module and why does it matter? A private-registry module marked no-code ready with user-editable variables defined; developers provision a new workspace running that module from a UI form without writing any HCL. It gives platform teams self-service for developers while keeping control of the underlying module, guardrails and version (paid feature).

11. cloud {} block vs remote backend — which and why? Both connect a config to HCP Terraform for state and remote runs. The cloud {} block is the modern, recommended surface — it supports mapping CLI workspaces to many HCP workspaces by tags and is what HashiCorp documents going forward. The remote backend is the legacy form; prefer cloud {} for new work. You can’t use both.

12. A developer needs to ship infrastructure but must never hold the AWS keys. How do you set that up in HCP Terraform? Give their team Write on their project’s workspaces; store the cloud credentials in a sensitive variable set (which they can’t read) or use dynamic OIDC credentials; and enforce guardrails with hard-mandatory policy they can’t bypass. They apply via reviewed runs without ever seeing the secret or breaking the rules.

13. What does a run trigger do? It chains workspaces: when a source workspace completes a successful apply, a triggered workspace automatically starts a run — used to model layered stacks (e.g. network → app) without an external orchestrator.

14. HCP Terraform vs OpenTofu — what’s the relationship? OpenTofu is the open-source fork of the CLI/engine (drop-in for HCL and commands) but has no managed SaaS. HCP Terraform is the hosted platform (remote runs, hosted state, registry, governance). With OpenTofu you assemble those capabilities yourself (remote backend + your own CI + OPA + your own module sources).

Quick check

  1. In the hierarchy, which level owns permissions for a group of workspaces — organisation, project, or workspace?
  2. You add a cloud {} block, run terraform plan, and it executes remotely without applying. Which workflow and which kind of plan is this?
  3. A variable set says region = "ap-south-1" but the workspace itself sets region = "us-east-1". Which value does the run use, and why?
  4. You want a developer to propose changes for review but never apply. Which team access level do you grant?
  5. True or false: with the random provider only, running this lesson’s lab on the free tier will incur cloud charges.

Answers

  1. The project. You grant a team access to a project and it applies to every workspace in it — the main unit for scaling RBAC. (Workspace-level access exists for finer control; the organisation owns org-wide capabilities and billing.)
  2. CLI-driven workflow, and a speculative planterraform plan under a cloud {} block runs remotely and is plan-only by default; terraform apply would be the real, confirmable apply.
  3. us-east-1 — the workspace-level variable overrides the variable set with the same key. Variable sets provide defaults that any single workspace can override.
  4. Plan access — it allows queuing speculative plans but not applying. (Read is view-only; Write would let them apply.)
  5. False. The lab uses only the random provider, which creates nothing in any cloud, and HCP Terraform’s free tier itself is free — so it costs ₹0.

Exercise

Using a free HCP Terraform account and only the random/local providers (no cloud spend), do the following: (a) create an organisation and a second project beside the default; (b) create a CLI-driven workspace in that project with a cloud {} block and confirm in the UI it’s in Remote execution mode with no local terraform.tfstate; © add one Terraform variable in the UI and prove (via the plan output) that it overrides the config default — that’s precedence; (d) make the same variable come from a variable set instead and observe that the workspace-level value still wins; (e) open the States tab and download a state version, then open the Runs tab and read the plan/apply logs; (f) add a second workspace and configure a run trigger so it starts when the first applies, then apply the first and watch the second queue; (g) clean up by destroying and deleting both workspaces. Bonus: read the first workspace’s output from the second using a terraform_remote_state data source — and notice it fails until you allowlist the consumer in the producer’s remote-state-sharing settings.

Certification mapping

Glossary

Next steps

You now understand HCP Terraform end to end: the organisation → project → workspace hierarchy, the three workflows, remote runs and execution modes, hosted/locked/versioned state and how to share it, variables and variable sets with their precedence, the full plan → cost → policy → apply pipeline with run triggers and run tasks, the private registry, no-code self-service, and teams/RBAC. The very next thing to nail down is the word that trips up almost everyone — workspace — because HCP workspaces and CLI workspaces are completely different beasts and choosing the wrong one for environment isolation is a classic mistake:

TerraformHCP TerraformWorkspacesRemote StatePrivate RegistryOpenTofu
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments