HCP Terraform (formerly Terraform Cloud) stores Terraform state remotely, runs plan and apply on HashiCorp infrastructure, and keeps a history of every run with locking and access control. A workspace is the unit it manages: one state file plus its variables, settings and run history. In this tutorial you will connect the Terraform CLI on Ubuntu 24.04 to HCP Terraform, run a configuration remotely, and then manage workspaces, variable sets and run triggers as code with the tfe provider.
Prerequisites
To follow this tutorial you need:
- A machine running Ubuntu 24.04 LTS, for example a CubePath VPS, with a non-root user that has
sudoprivileges and a web browser available somewhere to log in. - An HCP Terraform account at
https://app.terraform.io. The free plan is enough for everything in this guide. - Basic knowledge of Terraform configuration (
resource,variable,output).
The examples use the random provider so that runs work without any cloud credentials. The workflow is the same for real providers; only the variables you set change.
Step 1 - Installing Terraform
Install Terraform from HashiCorp's official APT repository so it updates with the rest of the system. Add the signing key and the repository:
sudo apt update
sudo apt install gnupg curl
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/hashicorp.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/hashicorp.gpg] https://apt.releases.hashicorp.com $(. /etc/os-release && echo "$VERSION_CODENAME") main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
Install the package and check the version:
sudo apt update
sudo apt install terraform
terraform version
Terraform v1.x.y
on linux_amd64
Step 2 - Creating an organization and logging in
Everything in HCP Terraform belongs to an organization. Sign in at https://app.terraform.io, create an organization (this guide uses your_org) and keep the default project.
Next, give the CLI an API token. terraform login opens a page where you generate a user token, then stores it for you:
terraform login
Answer yes, open the URL it prints, create the token, and paste it back into the terminal. The token is saved to ~/.terraform.d/credentials.tfrc.json and is used by both the CLI and the tfe provider later on.
NoteIn CI systems, skip
terraform loginand set the token in the environment variableTF_TOKEN_app_terraform_ioinstead.
Step 3 - Running a configuration in a CLI-driven workspace
The cloud block in the terraform settings tells the CLI to store state in HCP Terraform and run operations there. Create a project directory:
mkdir -p ~/tfc-demo/networking && cd ~/tfc-demo/networking
nano main.tf
terraform {
cloud {
organization = "your_org"
workspaces {
name = "demo-networking"
}
}
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
variable "environment" {
type = string
default = "dev"
}
resource "random_pet" "network" {
prefix = var.environment
}
output "network_name" {
value = random_pet.network.id
}
Replace your_org with your organization name. Initialize the directory; if the workspace does not exist yet, Terraform creates it:
terraform init
Initializing HCP Terraform...
...
HCP Terraform has been successfully initialized!
Now apply. The CLI uploads the configuration, the run executes remotely, and the logs stream back to your terminal with a link to the run in the web UI:
terraform apply
Running apply in HCP Terraform. Output will stream here. Pressing Ctrl-C
will cancel the remote apply if it's still pending. If the apply started it
will stop streaming the logs, but will not stop the apply running remotely.
...
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
network_name = "dev-cheerful-lemur"
Confirm that the state now lives in the workspace and not on disk:
terraform state list
ls
terraform state list shows random_pet.network, and there is no terraform.tfstate file in the directory. In the web UI, the workspace demo-networking shows the run and a state version.
TipBy default a workspace uses remote execution. If a configuration must reach private hosts that HCP Terraform cannot, change the workspace to local execution (Settings > General > Execution Mode). State stays remote, but plans run on your machine.
Step 4 - Setting workspace variables
Remote runs do not read your shell environment or local .tfvars files that are not uploaded, so variables and credentials are set on the workspace. There are two categories:
- Terraform variables fill
variableblocks, likeenvironmentabove. - Environment variables are exported in the run environment, which is where provider credentials such as
AWS_ACCESS_KEY_IDbelong. Mark those as sensitive so they are write-only.
In the UI, open the workspace, go to Variables, add a Terraform variable environment with value staging, and start a new run:
terraform apply
The plan now replaces random_pet.network because its prefix changed. Clicking through the UI works for one workspace; for several workspaces, manage them as code as shown next.
Step 5 - Managing workspaces as code with the tfe provider
The hashicorp/tfe provider manages HCP Terraform itself: workspaces, variables, variable sets, teams and run triggers. Keep this configuration in its own directory with its own workspace so that changes to your platform settings also go through plan and apply.
mkdir -p ~/tfc-demo/platform && cd ~/tfc-demo/platform
nano main.tf
terraform {
cloud {
organization = "your_org"
workspaces {
name = "platform"
}
}
required_providers {
tfe = {
source = "hashicorp/tfe"
version = "~> 0.60"
}
}
}
provider "tfe" {
organization = var.organization
}
variable "organization" {
type = string
}
resource "tfe_workspace" "app" {
name = "demo-app"
description = "Application layer, consumes demo-networking outputs"
auto_apply = false
}
resource "tfe_variable" "app_environment" {
workspace_id = tfe_workspace.app.id
key = "environment"
value = "staging"
category = "terraform"
description = "Deployment environment name"
}
The provider authenticates with the token stored by terraform login when it runs locally. Because this workspace runs remotely, give it a token too: in the platform workspace, add an environment variable TFE_TOKEN with an organization or team API token and mark it sensitive. Also add a Terraform variable organization with your organization name. Then apply:
terraform init
terraform apply
Plan: 2 to add, 0 to change, 0 to destroy.
...
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
The demo-app workspace now appears in the UI with the environment variable already set.
Step 6 - Sharing credentials with a variable set
A variable set holds variables that several workspaces need, such as cloud credentials, so you define them once. Add a set to ~/tfc-demo/platform/main.tf and attach it to the application workspace:
resource "tfe_variable_set" "cloud_credentials" {
name = "cloud-credentials"
description = "Provider credentials shared by application workspaces"
}
resource "tfe_variable" "example_api_token" {
variable_set_id = tfe_variable_set.cloud_credentials.id
key = "EXAMPLE_API_TOKEN"
value = var.example_api_token
category = "env"
sensitive = true
}
resource "tfe_workspace_variable_set" "app" {
variable_set_id = tfe_variable_set.cloud_credentials.id
workspace_id = tfe_workspace.app.id
}
variable "example_api_token" {
type = string
sensitive = true
}
Rename EXAMPLE_API_TOKEN to the variable your provider actually reads. Set example_api_token as a sensitive Terraform variable on the platform workspace so the secret is never written to Git, then apply:
terraform apply
When a workspace variable and a variable set define the same key, the workspace variable wins. Check the Variables page of demo-app: it lists the set and its (hidden) value.
Step 7 - Sharing outputs between workspaces
Splitting infrastructure into layers (networking, then applications) keeps each state small, but the application layer needs values from the networking layer. First allow it: in the demo-networking workspace go to Settings > General > Remote state sharing and share with demo-app (or with the whole organization).
Then create the configuration that runs in demo-app and read the outputs with the tfe_outputs data source, which only needs access to outputs instead of the full state:
mkdir -p ~/tfc-demo/app && cd ~/tfc-demo/app
nano main.tf
terraform {
cloud {
organization = "your_org"
workspaces {
name = "demo-app"
}
}
required_providers {
tfe = {
source = "hashicorp/tfe"
version = "~> 0.60"
}
}
}
data "tfe_outputs" "networking" {
organization = "your_org"
workspace = "demo-networking"
}
output "network_from_upstream" {
value = data.tfe_outputs.networking.nonsensitive_values.network_name
}
As with the platform workspace, the tfe provider in demo-app needs a TFE_TOKEN environment variable (sensitive) on the workspace. Add it, then run:
terraform init
terraform apply
Outputs:
network_from_upstream = "staging-cheerful-lemur"
Use values instead of nonsensitive_values when an output is sensitive; Terraform then treats the result as sensitive too.
Step 8 - Chaining runs with run triggers
A run trigger queues a run in one workspace every time another workspace completes a successful apply. Here, every change to networking should re-plan the application layer. Add it to ~/tfc-demo/platform/main.tf:
data "tfe_workspace" "networking" {
name = "demo-networking"
}
resource "tfe_run_trigger" "app_after_networking" {
workspace_id = tfe_workspace.app.id
sourceable_id = data.tfe_workspace.networking.id
}
terraform apply
To verify, go back to ~/tfc-demo/networking, add length = 3 inside the random_pet resource and run terraform apply. After it finishes, the demo-app workspace shows a new run triggered by demo-networking. Because auto_apply is false, that run waits for someone to confirm the plan in the UI.
Step 9 - Connecting a workspace to a Git repository (optional)
The CLI-driven workflow used so far starts runs from your terminal. A VCS-driven workspace starts them from Git instead: pull requests get a speculative plan posted as a status check, and merges to the tracked branch queue a real run.
- In Organization Settings > Version Control > Providers, connect GitHub, GitLab, Bitbucket or Azure DevOps and authorize access to your repositories.
- Create a workspace, choose Version Control Workflow, select the repository and, under advanced options, the Terraform working directory if the configuration is not at the repository root.
- Open a pull request that changes the configuration and check that the plan appears as a status check.
A VCS-driven workspace rejects terraform apply from the CLI, which is intended: changes go through review. terraform plan from the CLI still works as a speculative plan.
Troubleshooting
Required token could not be found: the CLI has no token forapp.terraform.io. Runterraform loginagain, or setTF_TOKEN_app_terraform_io.- Remote run fails with missing credentials although the provider works locally: remote runs do not see your shell. Set the credentials as environment variables on the workspace or in a variable set.
tfe_outputsreturns an authorization error: remote state sharing is not enabled for the consuming workspace on the source workspace.- Apply rejected with
Apply not allowed for workspaces with a VCS connection: the workspace is VCS-driven. Push a commit or trigger a run from the UI. - Run stuck in
Pending: another run is holding the workspace lock, or a previous plan is waiting for confirmation. Discard or confirm it in the UI.
Conclusion
You connected the Terraform CLI to HCP Terraform, ran a configuration with remote state and execution, and used the tfe provider to manage workspaces, a variable set and a run trigger as code, with outputs shared between layers. Next, add teams and workspace permissions with tfe_team and tfe_team_access, move an existing local state into a workspace with terraform init -migrate-state, and connect your production workspaces to Git so every change is reviewed.
