Fatskills
Practice. Master. Repeat.
Study Guide: TECH **Terraform Workspaces vs. Separate State Files: The Zero-Fluff, Hands-On Guide**
Source: https://www.fatskills.com/cloud-application-developer/chapter/tech-terraform-workspaces-vs-separate-state-files-the-zero-fluff-hands-on-guide

TECH **Terraform Workspaces vs. Separate State Files: The Zero-Fluff, Hands-On Guide**

By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.

⏱️ ~9 min read

Terraform Workspaces vs. Separate State Files: The Zero-Fluff, Hands-On Guide


1. What This Is & Why It Matters

You’re a cloud engineer managing infrastructure for a SaaS app with dev, staging, and prod environments. You need to: - Deploy the same infrastructure (VPC, EKS, RDS) across all three environments.
- Keep configurations DRY (Don’t Repeat Yourself) but allow slight variations (e.g., smaller EC2 instances in dev).
- Avoid state file collisions (e.g., accidentally destroying prod while working on dev).
- Automate deployments without manually editing backend configs.

This is where terraform workspace and separate state files come in.
- Workspaces let you reuse the same Terraform codebase with isolated state files (e.g., dev, staging, prod).
- Separate state files mean completely independent Terraform projects (e.g., terraform-dev/, terraform-prod/).

Why does this matter in production?
- If you mix up state files, you risk accidentally modifying prod while working on dev.
- If you hardcode environment names, you’ll end up with duplicate code that’s hard to maintain.
- If you don’t isolate state, a misconfigured terraform apply could wipe out critical resources.

Real-world scenario:
You inherit a Terraform repo where all environments share a single state file. A teammate runs terraform destroy -target=aws_instance.web in dev, but because the state isn’t isolated, it deletes the prod instance too. Now you’re firefighting at 2 AM.


2. Core Concepts & Components


1. terraform workspace

  • Definition: A built-in Terraform feature that creates isolated state files within the same backend (e.g., S3).
  • Production insight: Workspaces are not environments—they’re just state isolation. You still need to dynamically configure resources (e.g., var.environment).
  • Example:
    bash terraform workspace new dev # Creates a new workspace with its own state terraform workspace select dev # Switches to the dev workspace

2. Separate State Files (Multiple Backends)

  • Definition: Completely independent Terraform projects (e.g., terraform-dev/, terraform-prod/) with separate state files.
  • Production insight: More explicit and secure (no accidental workspace switches), but harder to keep in sync (changes must be replicated manually).
  • Example:
    ```bash # In terraform-dev/ terraform init -backend-config="bucket=my-tf-state-dev"

# In terraform-prod/ terraform init -backend-config="bucket=my-tf-state-prod" ```

3. Backend (Remote State Storage)

  • Definition: Where Terraform stores state files (e.g., S3, Azure Blob, Terraform Cloud).
  • Production insight: Always use remote backends—local state files are a single point of failure.
  • Example (S3 backend):
    hcl terraform {
    backend "s3" {
    bucket = "my-tf-state"
    key = "terraform.tfstate"
    region = "us-east-1"
    } }

4. Workspace-Specific Variables

  • Definition: Variables that change per workspace (e.g., instance_type = "t3.micro" in dev, instance_type = "m5.large" in prod).
  • Production insight: Use terraform.workspace in variables or workspace-specific .tfvars files.
  • Example:
    ```hcl variable "instance_type" {
    default = {
    dev = "t3.micro"
    staging = "t3.medium"
    prod = "m5.large"
    } }

resource "aws_instance" "web" {
instance_type = var.instance_type[terraform.workspace] } ```

5. State File Locking

  • Definition: Prevents concurrent Terraform runs from corrupting state (e.g., DynamoDB for S3 backends).
  • Production insight: Always enable locking—otherwise, two engineers running terraform apply at the same time can destroy your infrastructure.
  • Example (DynamoDB lock for S3):
    hcl terraform {
    backend "s3" {
    bucket = "my-tf-state"
    key = "terraform.tfstate"
    region = "us-east-1"
    dynamodb_table = "terraform-lock" # Enables locking
    } }

6. terraform_remote_state (Data Source)

  • Definition: Lets one Terraform project read state from another (e.g., prod VPC outputs for a dev app).
  • Production insight: Avoid overusing this—it creates tight coupling between projects.
  • Example:
    ```hcl data "terraform_remote_state" "vpc" {
    backend = "s3"
    config = {
    bucket = "my-tf-state"
    key = "vpc/terraform.tfstate"
    region = "us-east-1"
    } }

resource "aws_instance" "app" {
subnet_id = data.terraform_remote_state.vpc.outputs.subnet_id } ```

7. Environment-Specific .tfvars Files

  • Definition: Override variables per environment (e.g., dev.tfvars, prod.tfvars).
  • Production insight: Never commit secrets (use sops, vault, or AWS Secrets Manager).
  • Example:
    bash terraform apply -var-file="dev.tfvars" # Applies dev-specific vars

8. terraform import (State Migration)

  • Definition: Bring existing resources under Terraform control (e.g., manually created EC2 instances).
  • Production insight: Always test in a non-prod environment first—a bad import can destroy resources.
  • Example:
    bash terraform import aws_instance.web i-1234567890abcdef0


3. Step-by-Step Hands-On: Workspaces vs. Separate State Files


Prerequisites

✅ AWS account with admin IAM permissions
✅ AWS CLI configured (aws configure) ✅ Terraform installed (terraform -v) ✅ S3 bucket for state storage (my-tf-state)


Option 1: Using Workspaces (Single Codebase, Isolated State)

Goal: Deploy an EC2 instance in dev and prod using the same Terraform code but different workspaces.


Step 1: Initialize Terraform with S3 Backend

mkdir terraform-workspaces && cd terraform-workspaces

main.tf


terraform {
  backend "s3" {
bucket = "my-tf-state"
key = "workspaces/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-lock" # Enable locking } } provider "aws" { region = "us-east-1" } variable "instance_type" { default = {
dev = "t3.micro"
prod = "m5.large" } } resource "aws_instance" "web" { ami = "ami-0c55b159cbfafe1f0" # Amazon Linux 2 instance_type = var.instance_type[terraform.workspace] tags = {
Environment = terraform.workspace } }

Step 2: Create Workspaces

terraform init
terraform workspace new dev
terraform workspace new prod

Step 3: Deploy to Dev

terraform workspace select dev
terraform apply -auto-approve

Verify:


aws ec2 describe-instances --filters "Name=tag:Environment,Values=dev"

Step 4: Deploy to Prod

terraform workspace select prod
terraform apply -auto-approve

Verify:


aws ec2 describe-instances --filters "Name=tag:Environment,Values=prod"

Step 5: List Workspaces & State

terraform workspace list
terraform state list


Option 2: Separate State Files (Independent Projects)

Goal: Deploy the same EC2 instance but with completely separate Terraform projects (e.g., terraform-dev/, terraform-prod/).


Step 1: Create Separate Directories

mkdir -p terraform-separate/{dev,prod}

Step 2: Initialize Dev Project

cd terraform-separate/dev

main.tf


terraform {
  backend "s3" {
bucket = "my-tf-state"
key = "separate/dev/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-lock" } } provider "aws" { region = "us-east-1" } resource "aws_instance" "web" { ami = "ami-0c55b159cbfafe1f0" instance_type = "t3.micro" tags = {
Environment = "dev" } }
terraform init
terraform apply -auto-approve

Step 3: Initialize Prod Project

cd ../prod

main.tf


terraform {
  backend "s3" {
bucket = "my-tf-state"
key = "separate/prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-lock" } } provider "aws" { region = "us-east-1" } resource "aws_instance" "web" { ami = "ami-0c55b159cbfafe1f0" instance_type = "m5.large" tags = {
Environment = "prod" } }
terraform init
terraform apply -auto-approve

Step 4: Verify Both Instances

aws ec2 describe-instances --filters "Name=tag:Environment,Values=dev"
aws ec2 describe-instances --filters "Name=tag:Environment,Values=prod"


4. ? Production-Ready Best Practices


Security

Never store secrets in .tfvars → Use AWS Secrets Manager or Vault.
Enable state locking (DynamoDB for S3) → Prevents race conditions.
Restrict IAM permissions → Use least privilege for Terraform roles.
Encrypt state files → Enable S3 bucket encryption and KMS.

Cost Optimization

Use smaller instances in dev/stagingt3.micro vs. m5.large.
Tag resources by environment → Helps with cost allocation reports.
Clean up unused workspacesterraform workspace delete old-env.

Reliability & Maintainability

Use terraform.workspace for dynamic configs → Avoid hardcoding environment names.
Keep backend configs in a separate filebackend.tf (not main.tf).
Use terraform_remote_state sparingly → Prevents tight coupling.
Enforce naming conventionsproject-env-resource (e.g., myapp-dev-vpc).

Observability

Log all Terraform runs → Use Terraform Cloud or AWS CloudTrail.
Monitor state file changes → Set up S3 event notifications.
Use terraform plan in CI/CD → Prevents accidental destructive changes.


5. ⚠️ Common Mistakes & Traps

Mistake Symptom Fix/Prevention
Using workspaces but hardcoding environment names terraform apply in dev affects prod Use terraform.workspace in variables.
Not enabling state locking Two engineers run terraform apply at the same time → state corruption Always use DynamoDB/S3 locking.
Storing secrets in .tfvars Secrets committed to Git → security breach Use AWS Secrets Manager or Vault.
Mixing workspaces and separate state files Confusion over which approach to use → inconsistent deployments Pick one strategy and stick with it.
Not testing terraform import Importing a resource destroys it Test in a non-prod environment first.


6. ? Exam/Certification Focus


Typical Question Patterns

  1. "You need to deploy the same infrastructure in dev and prod. Which approach is best?"
  2. Workspaces (if environments are similar).
  3. Separate state files (if environments are very different).

  4. "How do you prevent two engineers from running terraform apply at the same time?"

  5. Enable state locking (DynamoDB for S3).

  6. "How do you dynamically set instance_type based on the environment?"
    hcl
    instance_type = var.instance_type[terraform.workspace]

  7. "What’s the risk of using terraform_remote_state?"

  8. Tight coupling → Changes in one project break another.

Key ⚠️ Trap Distinctions

Concept Workspaces Separate State Files
State isolation ✅ Yes (same backend) ✅ Yes (different backends)
Code reuse ✅ High (same codebase) ❌ Low (duplicate code)
Environment drift ❌ Possible (if not managed) ✅ Less likely (explicit separation)
CI/CD complexity ✅ Simple (single pipeline) ❌ Complex (multiple pipelines)


7. ? Hands-On Challenge (With Solution)

Challenge:
You have a Terraform project using workspaces (dev, prod). A teammate accidentally runs terraform destroy in prod instead of dev. How do you recover?

Solution:
1. Restore from S3 versioning (if enabled):
bash
aws s3 cp s3://my-tf-state/workspaces/terraform.tfstate ./terraform.tfstate.backup
2. Re-import the state (if resources still exist):
bash
terraform workspace select prod
terraform import aws_instance.web i-1234567890abcdef0
3. Prevent this in the future:
- Enable S3 versioning (so you can roll back).
- Use terraform plan in CI/CD (prevents accidental destroys).

Why it works:
- S3 versioning lets you roll back to a previous state.
- terraform import brings existing resources back under Terraform control.


8. ? Rapid-Reference Crib Sheet

Command/Concept Usage Notes
terraform workspace new <name> Creates a new workspace ✅ Isolates state
terraform workspace select <name> Switches workspaces ⚠️ Always check before apply
terraform workspace list Lists all workspaces ✅ Shows current workspace (* = active)
terraform workspace delete <name> Deletes a workspace ⚠️ Destroys resources if not empty
terraform.workspace Returns current workspace name ✅ Use in variables
backend "s3" Configures remote state ✅ Always enable locking
terraform import Brings existing resources under Terraform ⚠️ Test in non-prod first
terraform_remote_state Reads state from another project ⚠️ Avoid tight coupling
-var-file="dev.tfvars" Applies environment-specific vars ✅ Never commit secrets
dynamodb_table (in backend) Enables state locking Always use this


9. ? Where to Go Next

  1. Terraform Workspaces Docs – Official guide.
  2. Terraform Backends Docs – Remote state best practices.
  3. Terraform Import Guide – How to bring existing resources under Terraform control.
  4. AWS S3 Backend Best Practices – Security & cost optimization.

Final Takeaway

  • Use workspaces when environments are similar (same code, different configs).
  • Use separate state files when environments are very different (e.g., prod has multi-region DR).
  • Always enable state locking (DynamoDB for S3).
  • Never commit secrets (use AWS Secrets Manager or Vault).
  • Test terraform import in non-prod first—it can destroy resources.

Now go break something in dev (not prod) and fix it. ?



ADVERTISEMENT