Terraform Ephemeral Preview Environments Prompt
Design per-PR ephemeral preview environments with Terraform — unique naming/tagging, TTL auto-destroy in CI, DNS/subdomain per env, cost caps, and safe teardown on PR close without nuking the wrong workspace.
- Target user
- Platform engineers building PR preview infra for app teams
- Difficulty
- Advanced
- Tools
- Claude, ChatGPT, Cursor
The prompt
You are a platform engineer who has run PR-scoped ephemeral environments at scale and knows exactly how they leak money and collide on names when done naively.
I will provide:
- Cloud + provider(s) and the app shape (containers, DB, DNS zone)
- CI system (GitHub Actions, GitLab CI, etc.) and where PR events fire
- Backend config (S3/DynamoDB, GCS, or HCP Terraform) and current workspace strategy
- Any TTL / budget constraints
Your job:
1. **Isolation model** — decide between workspace-per-PR (`terraform workspace new pr-${PR_NUMBER}`) and dir-per-PR (copied tfvars + separate state key). State the tradeoff: workspaces share provider/backend config and are cheaper to spin up; dir-per-PR gives stronger blast-radius isolation. Recommend one for MY setup and justify it.
2. **Deterministic, collision-proof naming** — every resource name, tag, DNS record, and state key must derive from the PR number (and repo) so two PRs can NEVER target the same resource. Show the `locals` block that builds `name_prefix = "pr-${var.pr_number}"` and a `resource_suffix` guaranteed unique. Call out cloud name-length limits (S3 bucket 63 chars, RDS identifier 63, etc.).
3. **DNS / subdomain per env** — provision `pr-${var.pr_number}.preview.example.com` via a Route53/Cloud DNS record scoped to the env, and output the URL for the CI to comment on the PR.
4. **TTL auto-destroy** — apply a `ttl-expires` tag/label with an ISO timestamp, and describe BOTH teardown triggers: (a) the PR-close CI job that runs `terraform destroy`, and (b) a scheduled reaper that destroys any env whose `ttl-expires` is in the past (covers abandoned/stuck PRs). Never rely on PR-close alone.
5. **Cost control** — smallest viable instance sizes, spot/preemptible where safe, `skip_final_snapshot = true` on ephemeral DBs, and a hard budget alarm. Estimate monthly cost if N envs run concurrently.
6. **Safe teardown** — the destroy job must (a) select the EXACT workspace/state for that PR, (b) refuse to run against `default`/prod workspaces, (c) `terraform state list` to confirm scope before destroy, and (d) delete the workspace only after a clean destroy. This is a DESTRUCTIVE operation: destroying the wrong workspace wipes another PR's env or prod.
7. **Orphan detection** — a query (by tag) that finds resources with no matching open PR so cost leaks are caught.
Output: (a) the isolation recommendation, (b) naming/tagging `locals`, (c) the CI create + destroy job outlines, (d) the reaper design, (e) a teardown safety checklist.
Bias toward: deterministic naming, TTL reaper as the safety net, and guard rails that make destroying prod impossible.
---
Input template:
```
Cloud/provider:
App shape (containers/DB/DNS):
CI system + PR event source:
Backend (S3+DynamoDB / GCS / HCP TF):
DNS zone:
TTL requirement:
Concurrent-env budget ceiling:
```
Run this prompt with AI
Test it, get an AI-improved version, or compare models — live in the Prompt Workspace. No copy-paste.
Why this prompt works
Ephemeral preview environments fail in two predictable ways: they collide (two PRs racing to own the same S3 bucket or DNS record) and they leak (an abandoned PR leaves a running RDS instance billing for weeks). This prompt forces the model to attack both up front — deterministic PR-derived naming eliminates collisions, and a dual teardown design (PR-close job plus an independent TTL reaper) eliminates leaks. It also treats terraform destroy as the load-bearing destructive operation it is, demanding workspace guards before any teardown runs.
How to use it
- Fill in the input template with your cloud, CI, and backend details.
- Paste the whole prompt into Claude or Cursor.
- Start with workspace-per-PR unless you need hard tenant isolation — it is the cheapest correct default.
- Wire the generated create job into your
pull_request: [opened, synchronize]trigger and the destroy job intopull_request: [closed]. - Deploy the reaper as a scheduled workflow (hourly is plenty) so nothing survives its TTL.
Useful commands
# Create/select an isolated workspace for the PR
terraform workspace new "pr-${PR_NUMBER}" || terraform workspace select "pr-${PR_NUMBER}"
# Plan/apply with the PR number threaded through as a variable
terraform apply -auto-approve \
-var "pr_number=${PR_NUMBER}" \
-var "ttl_expires=$(date -u -d '+48 hours' +%Y-%m-%dT%H:%M:%SZ)"
# SAFE teardown: confirm scope, refuse prod/default, then destroy
CURRENT=$(terraform workspace show)
case "$CURRENT" in
default|prod|prod-*) echo "REFUSING to destroy $CURRENT" && exit 1 ;;
esac
terraform state list # eyeball the blast radius
terraform destroy -auto-approve -var "pr_number=${PR_NUMBER}"
terraform workspace select default
terraform workspace delete "pr-${PR_NUMBER}"
# Reaper: find AWS resources whose ttl-expires tag is in the past
aws resourcegroupstaggingapi get-resources \
--tag-filters Key=managed-by,Values=pr-preview \
--query "ResourceTagMappingList[].[ResourceARN, Tags[?Key=='ttl-expires'].Value | [0]]" \
--output text
Patterns
Deterministic naming and TTL tagging, all derived from the PR number so two PRs can never collide:
variable "pr_number" {
type = string
}
variable "ttl_expires" {
type = string
default = ""
}
locals {
name_prefix = "pr-${var.pr_number}"
common_tags = {
"managed-by" = "pr-preview"
"pr-number" = var.pr_number
"ttl-expires" = var.ttl_expires
"environment" = "ephemeral"
}
}
resource "aws_s3_bucket" "assets" {
# 63-char limit: prefix + account-scoped suffix keeps it unique and short
bucket = "${local.name_prefix}-assets-${data.aws_caller_identity.me.account_id}"
tags = local.common_tags
}
resource "aws_route53_record" "preview" {
zone_id = var.preview_zone_id
name = "${local.name_prefix}.preview.example.com"
type = "A"
alias {
name = aws_lb.app.dns_name
zone_id = aws_lb.app.zone_id
evaluate_target_health = true
}
}
output "preview_url" {
value = "https://${aws_route53_record.preview.name}"
}
GitHub Actions teardown job with a hard guard against destroying anything that is not a PR workspace:
name: preview-teardown
on:
pull_request:
types: [closed]
jobs:
destroy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Destroy PR environment
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
terraform init -input=false
terraform workspace select "pr-${PR_NUMBER}"
CURRENT=$(terraform workspace show)
if [[ "$CURRENT" == "default" || "$CURRENT" == prod* ]]; then
echo "::error::Refusing to destroy protected workspace $CURRENT"
exit 1
fi
terraform state list
terraform destroy -auto-approve -var "pr_number=${PR_NUMBER}"
terraform workspace select default
terraform workspace delete "pr-${PR_NUMBER}" Related prompts
-
Terraform Checkov Custom Policy Authoring Prompt
Write custom Checkov policies for Terraform — Python checks extending BaseResourceCheck and YAML-based policies, a .checkov.yaml config, inline skip suppressions and baselines, then wire soft-fail vs hard-fail gating into CI.
-
Terraform Vault Provider Secrets Integration Prompt
Inject HashiCorp Vault secrets into Terraform via data sources and ephemeral resources without persisting plaintext to state.
-
Ephemeral Resource Design for Short-Lived Credentials Prompt
Design ephemeral resources and ephemeral values to fetch and pass short-lived secrets without ever writing them to state or plan
-
Terraform OIDC CI Authentication Design Prompt
Replace long-lived cloud credentials in Terraform CI pipelines with short-lived OIDC-federated identity.
More Terraform prompts & error guides
Browse every Terraform prompt and troubleshooting guide in one place.
Reading prompts? Get all 500 in one free PDF
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.