Skip to main content

Overview

Before deploying infrastructure with Terraform, you need to create a remote backend to store Terraform state files. This guide walks through setting up Google Cloud Storage (GCS) buckets for secure, versioned, and collaborative Terraform state management.
This is a one-time setup per GCP project. Once complete, all Terraform environments (dev, staging, prod) will use these buckets.

State Bucket

Stores Terraform state with versioning

Log Bucket

Audits all state bucket access

Encryption

Google-managed encryption at rest

Lifecycle

Auto-cleanup of old versions (30 days)

Why Remote State?

Problem: Local state files can’t be shared across team members.Solution: GCS backend allows multiple engineers to work on the same infrastructure.
Problem: Concurrent Terraform runs can corrupt state.Solution: GCS provides automatic state locking (no DynamoDB needed like AWS).
Problem: Losing state file means losing track of infrastructure.Solution: GCS versioning allows recovery of previous state versions.
Problem: Need to know who accessed/modified state.Solution: Access logging tracks all operations on state bucket.

Prerequisites

1

Install gcloud CLI

# Verify installation
gcloud version
Install gcloud CLI →
2

Authenticate

gcloud auth login
gcloud auth application-default login
3

Create/Select GCP Project

  • New Project
  • Existing Project
gcloud projects create YOUR-PROJECT-ID \
  --name="MCP LangGraph"

gcloud config set project YOUR-PROJECT-ID

# Link billing account
gcloud billing projects link YOUR-PROJECT-ID \
  --billing-account=BILLING_ACCOUNT_ID
4

Enable Required APIs

gcloud services enable \
  storage-api.googleapis.com \
  cloudresourcemanager.googleapis.com \
  iam.googleapis.com
5

Install Terraform

# Verify Terraform 1.5.0+
terraform version
Install Terraform →

Quick Setup (5 minutes)

1

Navigate to Backend Setup

cd terraform/backend-setup-gcp
2

Create Configuration File

cat > terraform.tfvars <<EOF
project_id    = "your-gcp-project-id"
region        = "us-central1"
bucket_prefix = "mcp-langgraph"
EOF
# Required
project_id = "your-project-123"
region     = "us-central1"

# Optional
bucket_prefix                = "mcp-langgraph"  # Default: project_id
terraform_service_account    = "terraform@project.iam.gserviceaccount.com"
state_bucket_force_destroy   = false            # DANGER: true allows deletion
log_bucket_force_destroy     = true             # Logs can be destroyed
state_bucket_lifecycle_age   = 30               # Days to keep old versions
log_bucket_lifecycle_age     = 7                # Days to keep logs
state_bucket_storage_class   = "STANDARD"       # STANDARD or NEARLINE
labels = {
  environment = "shared"
  managed_by  = "terraform"
}
3

Initialize Terraform

terraform init
Should see: “Terraform has been successfully initialized!”
4

Plan & Review

terraform plan
Expected resources:
  • google_storage_bucket.terraform_state - State storage
  • google_storage_bucket.terraform_logs - Access logs
  • google_storage_bucket_iam_member.* - IAM bindings (if SA specified)
5

Apply Configuration

terraform apply
Type yes when prompted.Duration: ~30-60 seconds
6

Save Outputs

terraform output -json > backend-config.json
Outputs:
  • state_bucket_name - Use this in backend configurations
  • log_bucket_name - For audit trail access
  • backend_config_hcl - Copy-paste backend block

What Gets Created

1. State Bucket

resource "google_storage_bucket" "terraform_state" {
  name          = "${var.bucket_prefix}-terraform-state"
  location      = var.region
  storage_class = "STANDARD"

  versioning {
    enabled = true  # Allows state recovery
  }

  lifecycle_rule {
    condition {
      num_newer_versions = 3
      age                = 30
    }
    action {
      type = "Delete"  # Auto-cleanup old versions
    }
  }

  encryption {
    default_kms_key_name = null  # Google-managed
  }

  logging {
    log_bucket = google_storage_bucket.terraform_logs.name
  }
}
```yaml
```yaml Features
- Versioning: Enabled (keep last 3 versions)
- Encryption: Google-managed keys
- Lifecycle: Delete versions older than 30 days
- Logging: All access logged to separate bucket
- Location: us-central1 (configurable)
- Storage Class: STANDARD (instant access)

2. Log Bucket

resource "google_storage_bucket" "terraform_logs" {
  name          = "${var.bucket_prefix}-terraform-logs"
  location      = var.region
  storage_class = "STANDARD"

  lifecycle_rule {
    condition {
      age = 7  # Keep logs for 7 days
    }
    action {
      type = "Delete"
    }
  }
}
Purpose: Audit trail for all state bucket operations (reads, writes, deletes).

Using the Backend

In Environment Configurations

After backend setup, configure each environment to use the state bucket:
  • Production
  • Staging
  • Development
terraform/environments/gcp-prod/backend.tf
terraform {
  backend "gcs" {
    bucket = "mcp-langgraph-terraform-state"
    prefix = "environments/production"
  }
}
Each environment uses the same bucket but different prefixes for state isolation.

State Isolation Strategy

Recommendation: Use prefix-based strategy (one bucket) for most use cases.

Security Best Practices

IAM Permissions

Prevent Accidental Deletion

  • Terraform Variable
  • Bucket Lock (GCS)
  • Organization Policy
terraform.tfvars
state_bucket_force_destroy = false
Prevents terraform destroy from deleting the bucket.

Accessing State Files

View State

# Download current state
gsutil cp gs://mcp-langgraph-terraform-state/environments/production/default.tfstate .

# View state JSON
terraform show -json default.tfstate | jq .

# List state resources
terraform state list

Recover Previous Version

1

List Versions

gsutil ls -a gs://mcp-langgraph-terraform-state/environments/production/
Shows all versions with generation numbers.
2

Download Specific Version

gsutil cp gs://mcp-langgraph-terraform-state/environments/production/default.tfstate#GENERATION .
3

Restore Version

# Backup current state first!
gsutil cp \
  gs://mcp-langgraph-terraform-state/environments/production/default.tfstate \
  ./backup-$(date +%Y%m%d).tfstate

# Upload previous version
gsutil cp \
  ./default.tfstate#GENERATION \
  gs://mcp-langgraph-terraform-state/environments/production/default.tfstate

Cost Analysis

GCS Pricing (us-central1)

ComponentCostNotes
State storage$0.020/GB/monthTypically < 10 MB
Versioning$0.020/GB/month3 versions × 10 MB = 30 MB
Access logs$0.020/GB/month~1 MB/month
Operations$0.004/10k ops~100 ops/day = $0.12/month
Data transferFree (same region)-
Total~$0.15/monthNegligible
Backend storage costs are $0.15-0.50/month for typical usage.

Troubleshooting

Cause: GCS bucket names are globally unique across all GCP.Solution: Change bucket_prefix in terraform.tfvars:
bucket_prefix = "mcp-langgraph-unique-12345"
Cause: Insufficient IAM permissions.Solution: Grant required role:
gcloud projects add-iam-policy-binding PROJECT_ID \
  --member="user:YOUR_EMAIL" \
  --role="roles/storage.admin"
Symptom: Error 403: Storage API has not been usedSolution:
gcloud services enable storage-api.googleapis.com
Symptom: Error acquiring the state lockCause: Previous Terraform run didn’t release lock (crash/Ctrl+C).Solution: GCS automatically releases locks after 1 minute. Wait or:
# Find lock file
gsutil ls gs://mcp-langgraph-terraform-state/environments/production/

# Delete lock (if safe)
gsutil rm gs://.../default.tflock
Symptom: Error: bucket is not emptySolution:
# Option 1: Empty bucket first
gsutil rm -r gs://mcp-langgraph-terraform-state/**

# Option 2: Set force_destroy (DANGER!)
# terraform.tfvars
state_bucket_force_destroy = true
terraform apply
terraform destroy

Migration from Local State

1

Backup Local State

cp terraform.tfstate terraform.tfstate.backup
2

Add Backend Configuration

backend.tf
terraform {
  backend "gcs" {
    bucket = "mcp-langgraph-terraform-state"
    prefix = "environments/production"
  }
}
3

Re-initialize

terraform init -migrate-state
Terraform will detect local state and ask to migrate to GCS. Type yes.
4

Verify Migration

# State should now be in GCS
gsutil ls gs://mcp-langgraph-terraform-state/environments/production/

# Local state should be removed
ls -la terraform.tfstate

Advanced: Service Account Setup

For CI/CD pipelines, use a dedicated service account:
1

Create Service Account

gcloud iam service-accounts create terraform \
  --display-name="Terraform Automation"
2

Grant State Bucket Access

gcloud storage buckets add-iam-policy-binding \
  gs://mcp-langgraph-terraform-state \
  --member="serviceAccount:terraform@PROJECT.iam.gserviceaccount.com" \
  --role="roles/storage.objectUser"
3

Grant Infrastructure Permissions

gcloud projects add-iam-policy-binding PROJECT_ID \
  --member="serviceAccount:terraform@PROJECT.iam.gserviceaccount.com" \
  --role="roles/editor"
4

Use in Backend Setup

terraform.tfvars
terraform_service_account = "terraform@PROJECT.iam.gserviceaccount.com"
Re-run terraform apply to grant the SA permissions to the bucket.


Next Steps

1

✅ Backend Setup Complete

You now have remote state storage configured!
2

Review Terraform Modules

3

Choose Environment

4

Deploy Infrastructure

cd terraform/environments/gcp-prod
terraform init
terraform apply