Terraform Modules: How to Write, Use, and Publish Reusable Infrastructure
Write reusable Terraform modules with proper structure, inputs, outputs, and versioning. Covers local modules, registry modules, module composition
DevOps
Learn Terraform workspaces to manage dev, staging, and production with the same code. Covers workspace commands, terraform.workspace variable
Workspaces let you use the same Terraform configuration to manage multiple environments — each with its own state file.
Same .tf files → different state files → different infrastructure
workspace "dev" → terraform.tfstate.d/dev/terraform.tfstate → dev infra
workspace "staging" → terraform.tfstate.d/staging/terraform.tfstate → staging infra
workspace "prod" → terraform.tfstate.d/prod/terraform.tfstate → prod infraInstead of duplicating code into dev/, staging/, prod/ directories, workspaces let you switch environments with one command.
# Create workspaces
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
# Switch to dev
terraform workspace select dev
terraform apply
# Switch to prod
terraform workspace select prod
terraform applyEach apply creates separate infrastructure managed by separate state.
| Command | Description |
|---|---|
terraform workspace list | List all workspaces |
terraform workspace show | Show current workspace |
terraform workspace new NAME | Create a new workspace |
terraform workspace select NAME | Switch to a workspace |
terraform workspace delete NAME | Delete a workspace |
$ terraform workspace list
default
dev
* staging
prodThe * marks the current workspace.
The terraform.workspace variable returns the current workspace name. Use it to vary configuration per environment:
resource "aws_instance" "app" {
ami = var.ami_id
instance_type = terraform.workspace == "prod" ? "t3.large" : "t3.micro"
tags = {
Name = "app-${terraform.workspace}"
Environment = terraform.workspace
}
}locals {
instance_type = {
dev = "t3.micro"
staging = "t3.small"
prod = "t3.large"
}
instance_count = {
dev = 1
staging = 2
prod = 3
}
}
resource "aws_instance" "app" {
count = local.instance_count[terraform.workspace]
ami = var.ami_id
instance_type = local.instance_type[terraform.workspace]
tags = {
Name = "app-${terraform.workspace}-${count.index}"
Environment = terraform.workspace
}
}resource "aws_s3_bucket" "data" {
bucket = "myapp-data-${terraform.workspace}"
}
resource "aws_db_instance" "main" {
identifier = "myapp-db-${terraform.workspace}"
# ...
}Each workspace creates uniquely-named resources: myapp-data-dev, myapp-data-staging, myapp-data-prod.
Workspaces work with all remote backends. Each workspace gets its own state file:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "app/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
# Workspaces store state at:
# app/env:/dev/terraform.tfstate
# app/env:/staging/terraform.tfstate
# app/env:/prod/terraform.tfstate
}
}S3 automatically prefixes the key with env:/WORKSPACE_NAME/.
terraform {
cloud {
organization = "my-org"
workspaces {
tags = ["app"]
}
}
}In Terraform Cloud, workspaces are a first-class concept with their own variables, runs, and permissions.
# variables.tf
variable "ami_id" {
type = string
}
locals {
config = {
dev = {
vpc_cidr = "10.0.0.0/16"
instance_type = "t3.micro"
instance_count = 1
}
staging = {
vpc_cidr = "10.1.0.0/16"
instance_type = "t3.small"
instance_count = 2
}
prod = {
vpc_cidr = "10.2.0.0/16"
instance_type = "t3.large"
instance_count = 3
}
}
env = local.config[terraform.workspace]
}
# main.tf
resource "aws_vpc" "main" {
cidr_block = local.env.vpc_cidr
tags = {
Name = "vpc-${terraform.workspace}"
Environment = terraform.workspace
}
}
resource "aws_subnet" "app" {
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(local.env.vpc_cidr, 8, 1)
tags = {
Name = "app-subnet-${terraform.workspace}"
}
}
resource "aws_instance" "app" {
count = local.env.instance_count
ami = var.ami_id
instance_type = local.env.instance_type
subnet_id = aws_subnet.app.id
tags = {
Name = "app-${terraform.workspace}-${count.index}"
Environment = terraform.workspace
}
}
# outputs.tf
output "vpc_id" {
value = aws_vpc.main.id
}
output "instance_ips" {
value = aws_instance.app[*].private_ip
}Deploy:
terraform workspace select dev
terraform apply -var="ami_id=ami-12345678"
terraform workspace select prod
terraform apply -var="ami_id=ami-12345678"Two approaches to managing multiple environments:
project/
├── main.tf
├── variables.tf
├── outputs.tf
└── terraform.tfstate.d/
├── dev/
├── staging/
└── prod/Pros: No code duplication, easy to switch, consistent config. Cons: Same code must work for all environments, easy to apply to wrong workspace.
project/
├── modules/
│ └── app/
├── dev/
│ ├── main.tf
│ └── terraform.tfvars
├── staging/
│ ├── main.tf
│ └── terraform.tfvars
└── prod/
├── main.tf
└── terraform.tfvarsPros: Clear separation, different configs, harder to accidentally apply to wrong env. Cons: Code duplication, harder to keep in sync.
Add the workspace to your shell prompt:
# In ~/.bashrc or ~/.zshrc
export PS1="\$(terraform workspace show 2>/dev/null) \w $ "Or check before every apply:
echo "Current workspace: $(terraform workspace show)"
terraform plan# Prevent accidental destroy in prod
resource "aws_db_instance" "main" {
# ...
lifecycle {
prevent_destroy = terraform.workspace == "prod" ? true : false
}
}Note: prevent_destroy doesn't support expressions directly. Use a variable instead:
variable "protect_resources" {
type = bool
default = false
}
resource "aws_db_instance" "main" {
# ...
lifecycle {
prevent_destroy = true # Set per-workspace via tfvars
}
}terraform apply -var-file="${terraform.workspace}.tfvars"
# Or in a script:
terraform apply -var-file="$(terraform workspace show).tfvars"Error: Currently selected workspace "staging" does not exist.Create it first: terraform workspace new staging
The default workspace always exists and cannot be deleted. Switch to a named workspace instead.
Each workspace has isolated state. If you see resources from another workspace in your plan, check that you're in the correct workspace: terraform workspace show.
Learn by doing with interactive courses on CopyPasteLearn:
Terraform workspaces let you manage dev, staging, and prod with the same .tf files and separate state. Use terraform.workspace to vary instance sizes, counts, and naming. Use workspaces when environments are similar; use separate directories when they're fundamentally different. Always check terraform workspace show before applying to production.
Write reusable Terraform modules with proper structure, inputs, outputs, and versioning. Covers local modules, registry modules, module composition
Configure Terraform remote state with AWS S3 and DynamoDB locking. Complete setup with encryption, versioning, IAM permissions, and team access patterns.
Install and run Terraform on Ubuntu 26.04 LTS Resolute Raccoon. Covers sudo-rs as default, APT 3.2 rollback, Kernel 7.0, Wayland-only, ROCm, and building...
Set up OCI Load Balancer with Terraform — backend sets, listeners, SSL certificates, and health checks. Step-by-step guide with code examples and best practi...