Terraform Cloud and Terraform Enterprise
HashiCorp offers two managed products built on top of Terraform: HCP Terraform (formerly Terraform Cloud, SaaS) and Terraform Enterprise (self-hosted). Both provide remote state storage, remote execution, team access controls, policy enforcement, and a private module registry beyond what open-source Terraform offers.
HCP Terraform vs Terraform Enterprise
| Feature | HCP Terraform (Free) | HCP Terraform (Plus) | Terraform Enterprise |
|---|---|---|---|
| Remote state | ✅ | ✅ | ✅ |
| Remote execution | ✅ | ✅ | ✅ |
| Private registry | ✅ | ✅ | ✅ |
| Team management | Limited | ✅ | ✅ |
| Sentinel policies | ❌ | ✅ | ✅ |
| Audit logging | ❌ | ✅ | ✅ |
| SSO/SAML | ❌ | ✅ | ✅ |
| Self-hosted | ❌ | ❌ | ✅ |
| Custom agents | ❌ | ✅ | ✅ |
| Air-gapped deployment | ❌ | ❌ | ✅ |
Connecting to HCP Terraform
# Log in to HCP Terraform (stores token in ~/.terraform.d/credentials.tfrc.json)terraform login
# Verify loginterraform login --help# Configure the cloud block in main.tfterraform { cloud { organization = "mycompany"
workspaces { name = "production-aws" } }
required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } }}terraform init # Downloads providers, registers workspaceterraform plan # Runs remotely in HCP Terraformterraform apply # Remote apply with approval workflowWorkspaces in HCP Terraform
Each workspace in HCP Terraform is a fully isolated unit with:
- Its own state file
- Its own variables (including sensitive workspace-level secrets)
- Its own run history
- Its own access controls
# Multiple workspaces via tags (target all workspaces matching a tag)terraform { cloud { organization = "mycompany"
workspaces { tags = ["production", "aws"] # Match workspaces with these tags } }}Managing workspace variables via Terraform:
# Use the tfe provider to manage HCP Terraform programmaticallyprovider "tfe" { token = var.tfe_token}
resource "tfe_workspace" "production" { name = "production-aws" organization = "mycompany"
terraform_version = "1.9.5" execution_mode = "remote" auto_apply = false # Require approval
tag_names = ["production", "aws"]}
resource "tfe_variable" "aws_region" { key = "TF_VAR_region" value = "us-east-1" category = "env" workspace_id = tfe_workspace.production.id}
resource "tfe_variable" "db_password" { key = "TF_VAR_database_password" value = var.database_password category = "env" sensitive = true workspace_id = tfe_workspace.production.id}Sentinel Policy as Code
Sentinel (HCP Terraform Plus+) enforces policies before terraform apply runs:
import "tfplan/v2" as tfplan
# Require all resources to have Environment and Team tagsmain = rule { all tfplan.resource_changes as _, changes { all changes.change.after.tags as k, _ { k in ["Environment", "Team", "CostCenter"] } }}# sentinel.hcl — policy set configurationpolicy "require-all-resources-tagged" { source = "./sentinel/require-all-resources-tagged.sentinel" enforcement_level = "hard-mandatory" # Block apply on failure}
policy "restrict-instance-types" { source = "./sentinel/restrict-instance-types.sentinel" enforcement_level = "soft-mandatory" # Warn but allow override}Enforcement levels:
hard-mandatory— plan is blocked, cannot be overriddensoft-mandatory— can be overridden by authorized usersadvisory— warning only, never blocks
Private Module Registry
Host internal modules in HCP Terraform’s private registry:
# Publish a module to the private registry# Module must be in a git repo with naming convention:# terraform-<provider>-<module># e.g., terraform-aws-networking, terraform-aws-ecs-service# Reference a private registry modulemodule "networking" { source = "app.terraform.io/mycompany/networking/aws" version = "~> 2.0"
environment = var.environment cidr_block = "10.0.0.0/16"}Private registry modules are:
- Only accessible to your organization
- Versioned with git tags
- Documented with auto-generated README rendering
- Auditable — who consumed which module version
Team Access Controls
resource "tfe_team" "platform" { name = "platform-engineering" organization = "mycompany"}
resource "tfe_team" "developers" { name = "developers" organization = "mycompany"}
# Platform team: admin on productionresource "tfe_team_access" "platform_production" { access = "admin" team_id = tfe_team.platform.id workspace_id = tfe_workspace.production.id}
# Developers: read + plan on production, write on stagingresource "tfe_team_access" "dev_production" { access = "read" team_id = tfe_team.developers.id workspace_id = tfe_workspace.production.id}
resource "tfe_team_access" "dev_staging" { access = "write" team_id = tfe_team.developers.id workspace_id = tfe_workspace.staging.id}Run Triggers
Automatically start a downstream workspace when an upstream workspace successfully applies:
# When networking workspace applies, trigger application workspaceresource "tfe_run_trigger" "app_after_networking" { workspace_id = tfe_workspace.application.id sourceable_id = tfe_workspace.networking.id}This creates a dependency graph of workspaces — networking must apply before application, which must apply before database, etc. All managed in HCP Terraform without manual orchestration.