Documentation Index
Fetch the complete documentation index at: https://mintlify.com/hashicorp/terraform/llms.txt
Use this file to discover all available pages before exploring further.
HCP Terraform & Terraform Enterprise Integration
Terraform’s cloud backend provides seamless integration with HCP Terraform (formerly Terraform Cloud) and Terraform Enterprise for remote state storage, execution, and team collaboration.
Overview
The cloud integration enables:
- Remote state management: Store state securely in HCP Terraform
- Remote execution: Run Terraform operations in HCP Terraform’s infrastructure
- Team collaboration: Share workspaces and coordinate changes
- Policy enforcement: Apply Sentinel policies and OPA checks
- VCS integration: Trigger runs from version control systems
- Cost estimation: Preview infrastructure costs before applying
Configuration
Basic Cloud Block
terraform {
cloud {
organization = "my-organization"
workspaces {
name = "my-workspace"
}
}
}
Configuration Options
From internal/cloud/backend.go:135-180:
terraform {
cloud {
# Hostname of HCP Terraform or Terraform Enterprise
hostname = "app.terraform.io" # default
# Organization containing target workspaces
organization = "my-org"
# Optional: API token for authentication
token = "<token>" # Better to use credentials file
workspaces {
# Option 1: Single workspace by name
name = "production"
# Option 2: Multiple workspaces by tags
tags = ["web", "production"]
# Option 3: Key-value tags
tags = {
environment = "production"
tier = "web"
}
# Optional: Project for workspace organization
project = "infrastructure"
}
}
}
Workspace Strategies
The cloud backend supports three workspace selection strategies:
1. Single Workspace (Name Strategy)
workspaces {
name = "production"
}
Implementation:
case WorkspaceNameStrategy:
names = append(names, b.WorkspaceMapping.Name)
return names, diags
Location: internal/cloud/backend.go:630-632
workspaces {
tags = ["web", "app", "production"]
}
Queries workspaces matching all specified tags:
if b.WorkspaceMapping.Strategy() == WorkspaceTagsStrategy {
options.Tags = strings.Join(b.WorkspaceMapping.TagsAsSet, ",")
}
Location: internal/cloud/backend.go:638-639
workspaces {
tags = {
environment = "production"
region = "us-west-2"
}
}
Matches workspaces with specific tag key-value pairs:
if b.WorkspaceMapping.Strategy() == WorkspaceKVTagsStrategy {
options.TagBindings = b.WorkspaceMapping.asTFETagBindings()
}
Location: internal/cloud/backend.go:640-650
Authentication
Environment Variables
Configuration values can be set via environment variables:
# Organization (required if not in config)
export TF_CLOUD_ORGANIZATION="my-org"
# Hostname (optional, defaults to app.terraform.io)
export TF_CLOUD_HOSTNAME="terraform.example.com"
# Project (optional)
export TF_CLOUD_PROJECT="infrastructure"
From internal/cloud/backend.go:453-595:
func resolveCloudConfig(obj cty.Value) (cloudConfig, tfdiags.Diagnostics) {
// Config beats environment, environment beats defaults
if val := obj.GetAttr("hostname"); !val.IsNull() && val.AsString() != "" {
ret.hostname = val.AsString()
} else {
ret.hostname = os.Getenv("TF_CLOUD_HOSTNAME")
}
if ret.hostname == "" {
ret.hostname = defaultHostname // app.terraform.io
}
}
CLI Credentials
Authenticate using terraform login:
# Login to default hostname (app.terraform.io)
terraform login
# Login to custom hostname
terraform login terraform.example.com
Credentials are stored in CLI configuration:
token, err := CliConfigToken(hostname, b.services)
if err != nil {
return diags.Append(tfdiags.AttributeValue(
tfdiags.Error,
strings.ToUpper(err.Error()[:1])+err.Error()[1:],
"",
cty.Path{cty.GetAttrStep{Name: "hostname"}},
))
}
Location: internal/cloud/backend.go:296-309
Service Discovery
The cloud backend uses service discovery to locate the TFE API:
hostname, err := svchost.ForComparison(b.Hostname)
if err == nil {
host, err = b.services.Discover(hostname)
if err == nil {
b.ServicesHost = host
tfcService, err = host.ServiceURL(tfeServiceID) // "tfe.v2"
}
}
Location: internal/cloud/backend.go:263-279
Remote Operations
The cloud backend determines when to run operations remotely vs. locally:
func (b *Cloud) Operation(ctx context.Context, op *backendrun.Operation) (*backendrun.RunningOperation, error) {
// Retrieve workspace
w, err := b.fetchWorkspace(ctx, b.Organization, op.Workspace)
// Check if we need local execution
if b.forceLocal || isLocalExecutionMode(w.ExecutionMode) {
b.forceLocal = true
return b.local.Operation(ctx, op)
}
// Run remotely
switch op.Type {
case backendrun.OperationTypePlan:
f = b.opPlan
case backendrun.OperationTypeApply:
f = b.opApply
}
}
Location: internal/cloud/backend.go:881-934
Local vs Remote Execution
Execution mode is determined by:
- Force local flag:
TF_FORCE_LOCAL_BACKEND environment variable
- Workspace settings: Workspace execution mode configuration
- Operations entitlement: Organization’s operations capability
// Configure local backend fallback
b.local = backendLocal.NewWithBackend(b)
// Determine if forced to use local backend
b.forceLocal = os.Getenv("TF_FORCE_LOCAL_BACKEND") != "" || !entitlements.Operations
Location: internal/cloud/backend.go:428-431
Remote Plan Execution
When running terraform plan with cloud backend:
Plan Options
type Cloud struct {
// CLI and Colorize control CLI output
CLI cli.Ui
CLIColor *colorstring.Colorize
// ContextOpts are base context options
ContextOpts *terraform.ContextOpts
// client is the HCP Terraform or Terraform Enterprise API client
client *tfe.Client
// Organization contains target workspaces
Organization string
// WorkspaceMapping contains workspace selection strategies
WorkspaceMapping WorkspaceMapping
}
Location: internal/cloud/backend.go:55-84
Configuration Upload
configOptions := tfe.ConfigurationVersionCreateOptions{
AutoQueueRuns: tfe.Bool(false),
Speculative: tfe.Bool(planOnly), // true for plan without -out
Provisional: tfe.Bool(provisional), // true for plan with -out
}
cv, err := b.uploadConfigurationVersion(stopCtx, cancelCtx, op, w, configOptions)
Location: internal/cloud/backend_plan.go:132-141
Run Creation
runOptions := tfe.RunCreateOptions{
ConfigurationVersion: cv,
Refresh: tfe.Bool(op.PlanRefresh),
Workspace: w,
AutoApply: tfe.Bool(op.AutoApprove),
SavePlan: tfe.Bool(op.PlanOutPath != ""),
}
Location: internal/cloud/backend_plan.go:143-149
Version Compatibility
The cloud backend verifies Terraform version compatibility:
// Check minimum API version
currentAPIVersion, parseErr := version.NewVersion(b.client.RemoteAPIVersion())
desiredAPIVersion, _ := version.NewVersion("2.5")
if parseErr != nil || currentAPIVersion.LessThan(desiredAPIVersion) {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Unsupported Terraform Enterprise version",
`The 'cloud' option is not supported with this version of Terraform Enterprise.`,
))
}
Location: internal/cloud/backend.go:396-425
func (b *Cloud) VerifyWorkspaceTerraformVersion(workspaceName string) tfdiags.Diagnostics {
workspace, err := b.getRemoteWorkspace(context.Background(), workspaceName)
// Skip check for "latest" pseudo-version
if workspace.TerraformVersion == "latest" {
return nil
}
// Skip check for local execution mode
if isLocalExecutionMode(workspace.ExecutionMode) {
return nil
}
// Verify version compatibility
remoteConstraint, err := version.NewConstraint(workspace.TerraformVersion)
if !remoteConstraint.Check(fullTfversion) {
// Return error or warning based on ignoreVersionConflict
}
}
Location: internal/cloud/backend.go:1065-1162
Workspace Management
Auto-Creation
Workspaces are created automatically if they don’t exist:
if err == tfe.ErrResourceNotFound {
workspaceCreateOptions := tfe.WorkspaceCreateOptions{
Name: tfe.String(name),
Project: configuredProject,
}
if b.WorkspaceMapping.Strategy() == WorkspaceTagsStrategy {
workspaceCreateOptions.Tags = b.WorkspaceMapping.tfeTags()
} else if b.WorkspaceMapping.Strategy() == WorkspaceKVTagsStrategy {
workspaceCreateOptions.TagBindings = b.WorkspaceMapping.asTFETagBindings()
}
workspace, err = b.client.Workspaces.Create(context.Background(), b.Organization, workspaceCreateOptions)
}
Location: internal/cloud/backend.go:772-810
Tag Synchronization
The backend keeps workspace tags in sync:
tagCheck, errFromTagCheck := b.workspaceTagsRequireUpdate(context.Background(), workspace, b.WorkspaceMapping)
if tagCheck.requiresUpdate {
if !tagCheck.supportsKVTags {
options := tfe.WorkspaceAddTagsOptions{
Tags: b.WorkspaceMapping.tfeTags(),
}
err = b.client.Workspaces.AddTags(context.Background(), workspace.ID, options)
} else {
options := tfe.WorkspaceAddTagBindingsOptions{
TagBindings: b.WorkspaceMapping.asTFETagBindings(),
}
_, err = b.client.Workspaces.AddTagBindings(context.Background(), workspace.ID, options)
}
}
Location: internal/cloud/backend.go:837-861
Integration Patterns
Policy Enforcement
The cloud backend handles policy evaluation during runs:
Location: internal/cloud/backend_taskStage_policyEvaluation.go
Cost Estimation
Cost estimates are displayed during planning:
Location: internal/cloud/backend_plan.go
State Locking
Remote state is automatically locked during operations:
Location: internal/cloud/state.go
Testing Integration
Run tests remotely on HCP Terraform:
terraform test -cloud-run=app.terraform.io/my-org/my-module
Implementation:
runner = &cloud.TestSuiteRunner{
ConfigDirectory: ".",
Config: config,
Services: c.Services,
Source: args.CloudRunSource,
GlobalVariables: variables,
OperationParallelism: args.OperationParallelism,
Filters: args.Filter,
}
Location: internal/command/test.go:140-157
Best Practices
1. Use Environment Variables for Configuration
# .envrc or CI/CD configuration
export TF_CLOUD_ORGANIZATION="my-org"
export TF_CLOUD_PROJECT="infrastructure"
terraform {
cloud {
organization = "my-org"
workspaces {
tags = {
environment = "production"
team = "platform"
region = "us-west-2"
}
}
}
}
3. Use TF_WORKSPACE for Multi-Workspace Setups
# Select workspace dynamically
export TF_WORKSPACE="production-us-west-2"
terraform plan
4. Leverage Local Execution When Needed
# Force local execution for debugging
export TF_FORCE_LOCAL_BACKEND=1
terraform plan
5. Implement Proper Error Handling
The cloud integration includes retry logic:
func (b *Cloud) retryLogHook(attemptNum int, resp *http.Response) {
if b.CLI != nil {
if output := b.viewHooks.RetryLogHook(attemptNum, resp, true); len(output) > 0 {
b.CLI.Output(b.Colorize().Color(output))
}
}
}
Location: internal/cloud/backend.go:611-619
Troubleshooting
Connection Issues
Enable retry logging:
// Enable retries for server errors
b.client.RetryServerErrors(true)
Location: internal/cloud/backend.go:434
Version Conflicts
Ignore version conflicts if needed:
func (b *Cloud) IgnoreVersionConflict() {
b.ignoreVersionConflict = true
}
Location: internal/cloud/backend.go:1054-1056
Debugging Remote Runs
The cloud backend provides detailed output during remote operations through the IntegrationOutputWriter interface:
Location: internal/cloud/cloud_integration.go:18-117