LAB44_GCP_State Architecture: Multi-Environment Workspaces and GCS

State Architecture: Multi-Environment Workspaces and GCS

☁ GCP 🔥 Intermediate Terraform Backend Workspaces GCS

Scenario

You are building infrastructure that supports "dev", "staging", and "prod" environments. Instead of using completely separate Git directories with duplicate code, you will use a single codebase leveraging Terraform Workspaces and a GCS Remote Backend. You will dynamically size a Compute Engine instance based directly on the name of the currently active workspace.

Your Objectives
  • In step 1, define the `terraform { backend "gcs" { ... } }` block.
  • Deploy a google_compute_instance.
  • Map its `machine_type` using a Terraform `map` local variable, looking up the value using terraform.workspace.
  • Deploy the configuration twice via the CLI using `terraform workspace new dev` and `terraform workspace new prod`.

Additional Context

GCS Backend Structure: When using the GCS backend with workspaces, Terraform stores state files like this: `gs://[BUCKET_NAME]/[PREFIX]/default.tfstate`. If you create a workspace called "prod", it stores it at: `gs://[BUCKET_NAME]/[PREFIX]/env:/prod/default.tfstate`.

Dynamic Sizing: `terraform.workspace` is a natively available string inside HCL containing the current active workspace name (e.g. "default", "dev", "prod"). You can use `lookup(local.map, terraform.workspace, "fallback")` to configure different values without needing separate `.tfvars` files.

backend.tf

terraform {
  backend "gcs" {
    # You MUST create this bucket out-of-band BEFORE running terraform init!
    bucket  = "my-terraform-state-bucket-555"
    prefix  = "multi-env/state"
  }
}

variables.tf

variable "project_id" { type = string }

main.tf

provider "google" {
  project = var.project_id
  region  = "us-central1"
}

# Map environments to different compute sizes
locals {
  machine_sizes = {
    default = "f1-micro"
    dev     = "e2-micro"
    stag    = "e2-small"
    prod    = "e2-medium"
  }
  
  # Fetch the size based on workspace. If workspace is unnamed in map, fallback to default.
  active_size = lookup(local.machine_sizes, terraform.workspace, local.machine_sizes["default"])
}

resource "google_compute_instance" "app_server" {
  # Dynamically name the resource based on the environment!
  name         = "app-server-${terraform.workspace}"
  machine_type = local.active_size
  zone         = "us-central1-a"

  boot_disk {
    initialize_params { image = "debian-cloud/debian-11" }
  }

  network_interface {
    network = "default"
  }
}

outputs.tf

output "deployed_machine" {
  value = "${google_compute_instance.app_server.name} running on ${google_compute_instance.app_server.machine_type}"
}

Workflow Commands

# Initialize the Remote Backend connection
terraform init

# --- DEV DEPLOYMENT ---
terraform workspace new dev
terraform apply -var="project_id=my-gcp-project-123"
# Output shows: app-server-dev running on e2-micro

# --- PROD DEPLOYMENT ---
terraform workspace new prod
terraform apply -var="project_id=my-gcp-project-123"
# Output shows: app-server-prod running on e2-medium

# --- CLEANUP (Must destroy workspaces individually) ---
terraform workspace select dev
terraform destroy -auto-approve -var="project_id=my-gcp-project-123"

terraform workspace select prod
terraform destroy -auto-approve -var="project_id=my-gcp-project-123"