65 / 75 · 08 Infrastructure as Code Testing · Ephemeral Environments← prev⊞ allnext →☰ Read as one page
12.3Terraform Workspaces for Ephemeral Environments
Terraform workspaces provide lightweight isolation. Each workspace maintains its own state file, so resources created in one workspace do not affect another.
Workspace-Aware Configuration
# main.tf -- workspace-aware naming
locals {
env_name = terraform.workspace == "default" ? "production" : terraform.workspace
prefix = "myapp-${local.env_name}"
# Use smaller instances for PR environments
is_ephemeral = terraform.workspace != "default"
}
resource "aws_s3_bucket" "data" {
bucket = "${local.prefix}-data"
# Each PR gets its own bucket: myapp-pr-142-data
}
resource "aws_db_instance" "main" {
identifier = "${local.prefix}-db"
instance_class = local.is_ephemeral ? "db.t4g.micro" : "db.r6g.xlarge"
allocated_storage = local.is_ephemeral ? 20 : 500
# PR environments use minimal instance sizes to control cost
# Skip final snapshot for ephemeral environments
skip_final_snapshot = local.is_ephemeral
# Use a smaller backup retention for ephemeral
backup_retention_period = local.is_ephemeral ? 0 : 7
}
resource "aws_ecs_service" "app" {
name = "${local.prefix}-app"
desired_count = local.is_ephemeral ? 1 : 3
# PR environments need only one replica
}
# DNS entry for the preview URL
resource "aws_route53_record" "preview" {
count = local.is_ephemeral ? 1 : 0
zone_id = data.aws_route53_zone.preview.zone_id
name = "${local.env_name}.preview.example.com"
type = "CNAME"
records = [aws_lb.app.dns_name]
ttl = 60
}
output "preview_url" {
value = local.is_ephemeral ? "https://${local.env_name}.preview.example.com" : ""
}
CI Pipeline for Ephemeral Environments
#!/bin/bash
# scripts/ephemeral-env.sh
set -euo pipefail
ACTION="$1" # "create" or "destroy"
PR_NUM=$(echo "$GITHUB_REF" | grep -oP '\d+')
WORKSPACE="pr-${PR_NUM}"
case "$ACTION" in
create)
cd terraform/
terraform workspace new "$WORKSPACE" 2>/dev/null || \
terraform workspace select "$WORKSPACE"
terraform apply -auto-approve \
-var="pr_number=${PR_NUM}" \
-var="git_sha=${GITHUB_SHA:0:8}"
PREVIEW_URL=$(terraform output -raw preview_url)
echo "Preview environment ready: $PREVIEW_URL"
# Wait for health check
for i in $(seq 1 30); do
if curl -sf "$PREVIEW_URL/healthz" > /dev/null; then
echo "Environment is healthy"
break
fi
echo "Waiting for environment to be ready... ($i/30)"
sleep 10
done
# Run tests
cd ..
npx playwright test --base-url "$PREVIEW_URL"
TEST_RESULT=$?
# Post results to PR
if [ $TEST_RESULT -eq 0 ]; then
gh pr comment "$PR_NUM" --body \
"Preview: $PREVIEW_URL | Tests: PASSED"
else
gh pr comment "$PR_NUM" --body \
"Preview: $PREVIEW_URL | Tests: FAILED (see CI logs)"
fi
;;
destroy)
cd terraform/
terraform workspace select "$WORKSPACE"
terraform destroy -auto-approve
terraform workspace select default
terraform workspace delete "$WORKSPACE"
echo "Environment pr-${PR_NUM} destroyed"
;;
esac