# Terraform Plan Failed in CI

Common Error Patterns

Terraform plan failures typically show:

bash
Error: Error refreshing state: state lock error
bash
Error: Failed to get existing workspaces: AccessDenied
bash
Error: Provider produced inconsistent result after apply
bash
Error: required_providers block must be specified
bash
Error: Error running plan: 1 error(s) occurred

Root Causes and Solutions

1. State Lock Conflict

State is locked by another Terraform operation.

Solution:

Check for lock:

bash
terraform force-unlock LOCK_ID

For S3 backend with DynamoDB lock table:

```bash # Check DynamoDB lock aws dynamodb scan --table-name terraform-locks

# Delete lock manually (use with caution) aws dynamodb delete-item \ --table-name terraform-locks \ --key '{"LockID":{"S":"terraform-state-lock"}}' ```

In CI, use retry mechanism:

yaml
# GitHub Actions with retry
- name: Terraform Plan
  uses: nick-fields/retry@v2
  with:
    timeout_minutes: 10
    max_attempts: 3
    command: terraform plan -out=tfplan

2. Backend Authentication Failure

Cannot access state backend storage.

Solution:

For S3 backend:

```yaml # GitHub Actions - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/terraform-role aws-region: us-east-1

  • name: Terraform Init
  • run: terraform init
  • `

For Terraform Cloud:

yaml
- name: Terraform Init
  env:
    TF_TOKEN_app_terraform_io: ${{ secrets.TFC_TOKEN }}
  run: terraform init

For GCS backend:

```yaml - name: Authenticate to Google Cloud uses: google-github-actions/auth@v2 with: credentials_json: ${{ secrets.GCP_CREDENTIALS }}

  • name: Terraform Init
  • run: terraform init
  • `

3. Provider Configuration Errors

Provider not properly configured.

Solution:

Use required_providers block:

```hcl terraform { required_version = ">= 1.0"

required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } google = { source = "hashicorp/google" version = "~> 4.0" } } }

provider "aws" { region = var.aws_region } ```

For multiple provider configurations:

```hcl provider "aws" { alias = "east" region = "us-east-1" }

provider "aws" { alias = "west" region = "us-west-2" }

resource "aws_s3_bucket" "east_bucket" { provider = aws.east bucket = "east-bucket" } ```

4. Missing Variables

Required variables not provided.

Solution:

Pass variables via CLI:

bash
terraform plan \
  -var="aws_region=us-east-1" \
  -var="environment=production" \
  -var-file="production.tfvars"

Or use environment variables:

bash
export TF_VAR_aws_region="us-east-1"
export TF_VAR_environment="production"
terraform plan

In CI:

yaml
- name: Terraform Plan
  env:
    TF_VAR_aws_region: us-east-1
    TF_VAR_environment: production
  run: |
    terraform plan \
      -var-file="environments/${{ github.ref_name }}.tfvars" \
      -out=tfplan

5. Invalid Configuration Syntax

HCL syntax errors in configuration.

Solution:

Validate configuration:

bash
terraform fmt -check
terraform validate

In CI pipeline:

```yaml - name: Terraform Format Check run: terraform fmt -check -recursive

  • name: Terraform Validate
  • run: terraform validate
  • `

Use terraform-validator:

yaml
- name: Check Terraform
  uses: tf-actions/tf-validate@v1
  with:
    path: terraform/

6. State Drift

Actual infrastructure differs from Terraform state.

Solution:

Refresh state before plan:

bash
terraform refresh
terraform plan

Or use -refresh-only:

bash
terraform plan -refresh-only

Detect drift:

bash
# Show state vs actual
terraform plan -detailed-exitcode
# Exit code:
# 0 = no changes
# 1 = error
# 2 = changes present

Import existing resources:

bash
terraform import aws_s3_bucket.mybucket mybucket-name

7. Module Download Failures

Cannot download modules from registry.

Solution:

Use module caching:

bash
terraform init -get=false
terraform init -plugin-dir=/plugins

For private modules:

hcl
module "private_module" {
  source  = "app.terraform.io/organization/module/aws"
  version = "1.0.0"
}

Configure Terraform Cloud token:

yaml
- name: Terraform Init
  env:
    TF_TOKEN_app_terraform_io: ${{ secrets.TFC_TOKEN }}
  run: terraform init

8. Workspace/Environment Issues

Wrong workspace selected.

Solution:

Create and select workspace:

bash
terraform workspace new production
terraform workspace select production
terraform plan

In CI:

yaml
- name: Terraform Workspace
  run: |
    terraform workspace select ${{ github.ref_name }} || \
      terraform workspace new ${{ github.ref_name }}

Use workspace-specific state:

hcl
terraform {
  backend "s3" {
    bucket         = "terraform-state"
    key            = "terraform.tfstate"
    dynamodb_table = "terraform-locks"
    region         = "us-east-1"
    workspace_key_prefix = "environments"
  }
}

CI Pipeline Configuration

GitHub Actions Full Workflow

```yaml name: Terraform

on: push: branches: [main] pull_request: branches: [main]

jobs: terraform: runs-on: ubuntu-latest permissions: id-token: write contents: read steps: - name: Checkout uses: actions/checkout@v4

  • name: Setup Terraform
  • uses: hashicorp/setup-terraform@v3
  • with:
  • terraform_version: 1.5.0
  • name: Configure AWS credentials
  • uses: aws-actions/configure-aws-credentials@v4
  • with:
  • role-to-assume: arn:aws:iam::123456789012:role/terraform-role
  • aws-region: us-east-1
  • name: Terraform Format
  • run: terraform fmt -check
  • name: Terraform Init
  • run: terraform init
  • name: Terraform Validate
  • run: terraform validate
  • name: Terraform Plan
  • run: terraform plan -out=tfplan
  • name: Terraform Apply
  • if: github.ref == 'refs/heads/main' && github.event_name == 'push'
  • run: terraform apply -auto-approve tfplan
  • `

GitLab CI Configuration

```yaml stages: - validate - plan - apply

variables: TF_IN_AUTOMATION: "true" TF_ROOT: "terraform"

validate: stage: validate image: hashicorp/terraform:1.5 script: - terraform fmt -check - terraform init - terraform validate

plan: stage: plan image: hashicorp/terraform:1.5 script: - terraform init - terraform plan -out=tfplan artifacts: paths: - tfplan

apply: stage: apply image: hashicorp/terraform:1.5 script: - terraform init - terraform apply -auto-approve tfplan rules: - if: $CI_COMMIT_BRANCH == "main" dependencies: - plan ```

Debugging Commands

```bash # Verbose plan output terraform plan -out=tfplan -verbose

# Show plan details terraform show tfplan

# Debug mode TF_LOG=DEBUG terraform plan

# Trace mode TF_LOG=TRACE terraform plan 2>&1 | tee terraform-debug.log

# Check state terraform state list terraform state show aws_s3_bucket.main

# State manipulation terraform state mv aws_s3_bucket.old aws_s3_bucket.new terraform state rm aws_s3_bucket.unmanaged ```

Quick Reference

ErrorSolution
State lockterraform force-unlock
Auth failedConfigure provider credentials
Missing varsPass -var or TF_VAR_*
Syntax errorRun terraform fmt and validate
Module failUse token for private modules
DriftRun terraform refresh

Prevention Tips

  1. 1.Use terraform fmt and validate in CI
  2. 2.Configure proper state locking
  3. 3.Use OIDC authentication for cloud providers
  4. 4.Run plan on every PR before apply
  5. 5.Store plan artifacts for audit
  6. 6.Use sentinel/policy-as-code for governance
  • [AWS CloudFormation Stack Failed](#)
  • [Kubernetes Deployment Failed in CI](#)
  • [AWS IAM Permission Denied](#)