Introduction
Infrastructure as Code (IaC) has transformed how teams manage cloud resources, and Terraform remains the de facto standard in 2026. Whether you're provisioning a single EC2 instance or orchestrating a multi-cloud deployment across AWS, Azure, and GCP, the quality of your Terraform code directly impacts reliability, security, and cost. But writing good Terraform isn't just about syntax—it's about asking the right questions, designing reusable modules, managing state safely, and handling complex dependencies. This article presents a curated set of prompts designed to help you think through every stage of Terraform development, from basic module creation to advanced multi-cloud strategies. Each prompt includes a clear task, the exact prompt to use, and an example result to illustrate the output.
Why Prompts Matter for Terraform
Prompts are not just for AI assistants—they are mental frameworks that guide your engineering decisions. A well-structured prompt forces you to consider inputs, outputs, constraints, and edge cases. In the context of Terraform, prompts help you:
- Define clear interfaces for modules
- Anticipate state locking and remote backend requirements
- Plan for provider versioning and drift detection
- Design for multi-cloud without vendor lock-in
According to HashiCorp's 2025 State of Cloud Strategy Survey, over 70% of organizations now use multiple cloud providers, and Terraform is the most common tool for managing that complexity. The prompts below are organized by skill level: Basic, Advanced, and Expert.
Basic Prompts
1. Create a Reusable Module for an S3 Bucket
Task: Design a Terraform module that provisions an S3 bucket with configurable versioning, encryption, and lifecycle rules.
Prompt:
Write a Terraform module that creates an S3 bucket with the following features:
- Configurable bucket name (with prefix)
- Optional versioning (enabled/disabled/suspended)
- Optional server-side encryption (AES256 or aws:kms)
- Optional lifecycle rules (transition to Glacier after N days)
- Output the bucket ARN and ID
- Use sensible defaults so it works out of the box
Example Result (partial):
# modules/s3-bucket/main.tf
variable "bucket_prefix" {
description = "Prefix for bucket name"
type = string
}
variable "enable_versioning" {
description = "Enable object versioning"
type = bool
default = true
}
variable "encryption_type" {
description = "Encryption type: AES256 or aws:kms"
type = string
default = "AES256"
}
variable "lifecycle_days" {
description = "Days before transition to Glacier (0 = no lifecycle)"
type = number
default = 0
}
resource "aws_s3_bucket" "this" {
bucket = "${var.bucket_prefix}-${random_id.suffix.hex}"
force_destroy = true
}
resource "aws_s3_bucket_versioning" "this" {
bucket = aws_s3_bucket.this.id
versioning_configuration {
status = var.enable_versioning ? "Enabled" : "Suspended"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
bucket = aws_s3_bucket.this.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = var.encryption_type
}
}
}
resource "random_id" "suffix" {
byte_length = 4
}
output "bucket_arn" {
value = aws_s3_bucket.this.arn
}
output "bucket_id" {
value = aws_s3_bucket.this.id
}
2. Terraform State Locking with DynamoDB
Task: Set up a remote backend with state locking to prevent concurrent modifications.
Prompt:
Provide a Terraform configuration that uses S3 as a remote backend and DynamoDB for state locking. Include the necessary resources to create the S3 bucket and DynamoDB table, then show how to configure the backend block.
Example Result:
# backend-setup/main.tf
resource "aws_s3_bucket" "terraform_state" {
bucket = "my-org-terraform-state-2026"
force_destroy = true
}
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_dynamodb_table" "terraform_lock" {
name = "terraform-state-lock"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
# In your root module, use:
# terraform {
# backend "s3" {
# bucket = "my-org-terraform-state-2026"
# key = "prod/terraform.tfstate"
# region = "us-east-1"
# dynamodb_table = "terraform-state-lock"
# encrypt = true
# }
# }
3. Multi-Provider Configuration for AWS and GCP
Task: Configure a root module that provisions resources in both AWS and GCP using aliased providers.
Prompt:
Write a Terraform configuration that creates an AWS EC2 instance and a GCP Compute Engine VM in the same root module. Use provider aliases to specify regions. Output the public IPs of both instances.
Example Result:
# providers.tf
provider "aws" {
alias = "aws_west"
region = "us-west-2"
}
provider "google" {
alias = "gcp_east"
project = "my-gcp-project"
region = "us-east1"
}
# main.tf
resource "aws_instance" "web" {
provider = aws.aws_west
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = { Name = "web-aws" }
}
resource "google_compute_instance" "web" {
provider = google.gcp_east
name = "web-gcp"
machine_type = "e2-micro"
zone = "us-east1-b"
boot_disk {
initialize_params {
image = "debian-cloud/debian-11"
}
}
network_interface {
network = "default"
access_config {}
}
}
output "aws_public_ip" {
value = aws_instance.web.public_ip
}
output "gcp_public_ip" {
value = google_compute_instance.web.network_interface[0].access_config[0].nat_ip
}
Advanced Prompts
4. Terraform Workspaces for Environment Isolation
Task: Use Terraform workspaces to manage dev, staging, and prod environments with the same configuration.
Prompt:
Explain how to use Terraform workspaces to deploy the same infrastructure to three environments: dev, staging, and prod. Show how to reference the workspace name in resource naming and tagging. Provide a sample configuration that creates an EC2 instance with a name that includes the workspace.
Example Result:
# main.tf
resource "aws_instance" "app" {
ami = var.ami_id
instance_type = var.instance_type[terraform.workspace]
tags = {
Name = "app-${terraform.workspace}"
Environment = terraform.workspace
}
}
# variables.tf
variable "instance_type" {
type = map(string)
default = {
dev = "t2.micro"
staging = "t2.small"
prod = "t2.medium"
}
}
# Usage:
# terraform workspace new dev
# terraform workspace select dev
# terraform apply
5. Module Composition and Dependency Injection
Task: Create a parent module that composes child modules for networking, compute, and database, with explicit dependencies.
Prompt:
Design a Terraform configuration that uses three child modules: vpc, ec2, and rds. The EC2 instance must depend on the VPC, and the RDS instance must depend on the VPC but not on the EC2 instance. Pass outputs from the VPC module as inputs to the other modules. Include depends_on where necessary.
Example Result:
# root/main.tf
module "vpc" {
source = "./modules/vpc"
cidr = "10.0.0.0/16"
}
module "ec2" {
source = "./modules/ec2"
vpc_id = module.vpc.vpc_id
subnet_id = module.vpc.public_subnet_ids[0]
instance_type = "t2.micro"
# EC2 depends on VPC being fully created
depends_on = [module.vpc]
}
module "rds" {
source = "./modules/rds"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
db_name = "appdb"
master_username = "admin"
master_password = var.db_password
# RDS depends on VPC but not on EC2
depends_on = [module.vpc]
}
6. Using Count and for_each for Dynamic Resources
Task: Provision multiple IAM users and attach policies dynamically using for_each.
Prompt:
Write a Terraform configuration that creates IAM users from a map variable, and attaches a custom policy to each user. Use for_each to iterate over the map. Output the list of user ARNs.
Example Result:
# variables.tf
variable "users" {
description = "Map of IAM users with their policy ARNs"
type = map(object({
policy_arn = string
}))
default = {
"alice" = { policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess" }
"bob" = { policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess" }
}
}
# main.tf
resource "aws_iam_user" "this" {
for_each = var.users
name = each.key
}
resource "aws_iam_user_policy_attachment" "this" {
for_each = var.users
user = aws_iam_user.this[each.key].name
policy_arn = each.value.policy_arn
}
output "user_arns" {
value = { for k, u in aws_iam_user.this : k => u.arn }
}
Expert Prompts
7. Multi-Cloud State Management with Terragrunt
Task: Use Terragrunt to manage Terraform state across multiple cloud providers with remote backends and dependency resolution.
Prompt:
Show how to use Terragrunt to configure remote state for a multi-cloud deployment that includes AWS, Azure, and GCP. Each cloud provider should have its own S3/Azure Storage/GCS backend. Use Terragrunt's dependency blocks to ensure proper order. Include a root terragrunt.hcl that generates provider configurations.
Example Result:
# terragrunt.hcl (root)
remote_state {
backend = "s3"
config = {
bucket = "my-org-terraform-state"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-state-lock"
}
}
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "aws" {
region = "us-west-2"
}
provider "azurerm" {
features {}
}
provider "google" {
project = "my-gcp-project"
region = "us-central1"
}
EOF
}
# aws/terragrunt.hcl
include {
path = find_in_parent_folders()
}
dependency "vpc" {
config_path = "../vpc"
}
inputs = {
vpc_id = dependency.vpc.outputs.vpc_id
}
8. Custom Policy as Code with Sentinel
Task: Write a Sentinel policy that enforces tagging standards and prevents public S3 buckets.
Prompt:
Create a Sentinel policy that checks all AWS resources in a Terraform plan to ensure:
1. Every resource has a "CostCenter" tag
2. No S3 bucket has public read or write access
3. The policy fails the plan if either condition is violated
Example Result:
# sentinel.sentinel
import "tfplan/v2" as tfplan
mandatory_tags = ["CostCenter"]
# Get all resources
all_resources = tfplan.resource_changes
# Check tags
violations = []
for resource in all_resources:
if "tags" in resource.change.after:
for tag in mandatory_tags:
if tag not in resource.change.after.tags:
violations.append(
"Resource {} missing tag {}".format(resource.address, tag)
)
else:
violations.append("Resource {} has no tags".format(resource.address))
# Check S3 public access
for resource in all_resources:
if resource.type == "aws_s3_bucket":
acl = resource.change.after.acl
if acl in ["public-read", "public-read-write"]:
violations.append(
"Bucket {} has public ACL: {}".format(resource.address, acl)
)
# Fail if any violations
if len(violations) > 0:
for v in violations:
print(v)
main = rule { false }
else:
main = rule { true }
9. Multi-Cloud Networking with Transit Gateway and VPC Peering
Task: Design a Terraform configuration that connects a GCP VPC to an AWS VPC via a VPN tunnel, using a Transit Gateway in AWS.
Prompt:
Write Terraform code that creates an AWS Transit Gateway, attaches an AWS VPC to it, and sets up a VPN connection to a GCP VPC using a Cloud VPN tunnel. Include the necessary GCP resources (Cloud Router, VPN gateway, external VPN gateway). Output the tunnel status.
Example Result (abbreviated):
# aws/main.tf
resource "aws_ec2_transit_gateway" "tgw" {
description = "Multi-cloud transit gateway"
}
resource "aws_ec2_transit_gateway_vpc_attachment" "vpc_attach" {
subnet_ids = aws_subnet.private[*].id
transit_gateway_id = aws_ec2_transit_gateway.tgw.id
vpc_id = aws_vpc.main.id
}
resource "aws_vpn_connection" "to_gcp" {
customer_gateway_id = aws_customer_gateway.gcp.id
transit_gateway_id = aws_ec2_transit_gateway.tgw.id
type = "ipsec.1"
tunnel1_preshared_key = "secret123"
tunnel2_preshared_key = "secret456"
}
# gcp/main.tf
resource "google_compute_router" "router" {
name = "cloud-router"
network = google_compute_network.vpc.name
}
resource "google_compute_vpn_gateway" "gw" {
name = "vpn-gw"
network = google_compute_network.vpc.name
}
resource "google_compute_external_vpn_gateway" "aws_gw" {
name = "aws-gw"
redundancy_type = "TWO_IPS_REDUNDANCY"
interface {
id = 0
ip_address = aws_vpn_connection.to_gcp.tunnel1_address
}
interface {
id = 1
ip_address = aws_vpn_connection.to_gcp.tunnel2_address
}
}
10. Drift Detection and Remediation with Terraform and OPA
Task: Use Open Policy Agent (OPA) to detect drift between the desired Terraform state and the live cloud resources, then trigger a remediation workflow.
Prompt:
Explain how to combine Terraform's refresh command with OPA policies to detect unauthorized changes (drift) in AWS resources. Provide an OPA policy that checks if security group rules were modified outside Terraform, and a shell script that runs terraform plan and applies the policy.
Example Result:
# drift.rego
package terraform.drift
# Expected security group rules from Terraform state
import data.terraform.state as state
# Actual rules from AWS (fetched via terraform refresh)
import data.aws_security_groups as actual
violations[msg] {
some sg_name
expected := state.resources["aws_security_group"][sg_name]
actual_sg := actual[sg_name]
expected_rules := expected.ingress
actual_rules := actual_sg.ingress
# Compare sets
unexpected := actual_rules - expected_rules
count(unexpected) > 0
msg := sprintf("SG %v has unexpected ingress rules: %v", [sg_name, unexpected])
}
Conclusion
Mastering Terraform and IaC requires more than memorizing syntax—it demands a systematic approach to module design, state management, and multi-cloud orchestration. The prompts in this collection serve as a starting point for building robust, maintainable infrastructure. Start with the basic prompts to solidify your foundations, then move to advanced techniques like workspaces and module composition. Finally, tackle expert-level challenges such as cross-cloud networking and policy-as-code. By internalizing these prompts, you'll be able to write Terraform that is not only functional but also scalable, secure, and production-ready. The cloud landscape continues to evolve, but the principles behind these prompts—clarity, reusability, and automation—will remain relevant for years to come.
Comments