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
Authenticate
gcloud auth login
gcloud auth application-default login
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
Enable Required APIs
gcloud services enable \
storage-api.googleapis.com \
cloudresourcemanager.googleapis.com \
iam.googleapis.com
Quick Setup (5 minutes)
Navigate to Backend Setup
cd terraform/backend-setup-gcp
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"
}
Initialize Terraform
Should see: “Terraform has been successfully initialized!”
Plan & Review
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)
Apply Configuration
Type yes when prompted. Duration : ~30-60 seconds
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
One bucket, multiple prefixes :mcp-langgraph-terraform-state/
├── environments/
│ ├── development/
│ │ └── default.tfstate
│ ├── staging/
│ │ └── default.tfstate
│ └── production/
│ └── default.tfstate
└── modules/
└── test/
└── default.tfstate
Pros :
Single backend to manage
Cost-effective (one bucket)
Easy to backup
Cons :
Accidental cross-environment changes possible (mitigated by permissions)
Recommendation : Use prefix-based strategy (one bucket) for most use cases.
Security Best Practices
IAM Permissions
Minimal (Recommended)
Admin (Development)
Grant only required permissions: gcloud storage buckets add-iam-policy-binding \
gs://mcp-langgraph-terraform-state \
--member= "serviceAccount:terraform@PROJECT.iam.gserviceaccount.com" \
--role= "roles/storage.objectUser"
Permissions :
storage.objects.get (read state)
storage.objects.create (write state)
storage.objects.delete (cleanup old versions)
Prevent Accidental Deletion
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
List Versions
gsutil ls -a gs://mcp-langgraph-terraform-state/environments/production/
Shows all versions with generation numbers.
Download Specific Version
gsutil cp gs://mcp-langgraph-terraform-state/environments/production/default.tfstate#GENERATION .
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)
Component Cost Notes State storage $0.020/GB/month Typically < 10 MB Versioning $0.020/GB/month 3 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 transfer Free (same region) - Total ~$0.15/month Negligible
Backend storage costs are $0.15-0.50/month for typical usage.
Troubleshooting
Error: Bucket name already exists
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
Cannot destroy backend bucket
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
Backup Local State
cp terraform.tfstate terraform.tfstate.backup
Add Backend Configuration
terraform {
backend "gcs" {
bucket = "mcp-langgraph-terraform-state"
prefix = "environments/production"
}
}
Re-initialize
terraform init -migrate-state
Terraform will detect local state and ask to migrate to GCS. Type yes.
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:
Create Service Account
gcloud iam service-accounts create terraform \
--display-name= "Terraform Automation"
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"
Grant Infrastructure Permissions
gcloud projects add-iam-policy-binding PROJECT_ID \
--member= "serviceAccount:terraform@PROJECT.iam.gserviceaccount.com" \
--role= "roles/editor"
Use in Backend Setup
terraform_service_account = "terraform@PROJECT.iam.gserviceaccount.com"
Re-run terraform apply to grant the SA permissions to the bucket.
Next Steps
✅ Backend Setup Complete
You now have remote state storage configured!
Deploy Infrastructure
cd terraform/environments/gcp-prod
terraform init
terraform apply