12 Prompts for Terraform and IaC: Modules, State, and Multi-Cloud
Why These Prompts?
If you've been building infrastructure with Terraform for a while, you know the patterns: code grows into unmanageable monoliths, state becomes a source of friction, and multi-cloud strategies get bogged down in provider-specific syntax. This isn't a problem of missing features — it's a problem of structure and workflow. Over the past few years, I've used AI assistants to cut review time and standardize Terraform practice across AWS, Azure, and GCP. The prompts below are the ones that survived production use. They are not magic; they are structured requests that produce consistent, reviewable results.
The Terraform community has produced excellent official guidance. Before you start, I strongly recommend reading the Terraform Module Creation documentation and the Terraform style guide. These prompts assume you know the basics of Terraform syntax.
Each prompt includes a practical example based on real infrastructure scenarios I've encountered. You can copy the prompt, adapt the resource names, and use it as your own starting point.
1. Generate a VPC Module
Scenario: A team needs a reusable VPC module that can be used across multiple projects. The ad-hoc VPC configuration has become inconsistent between environments.
Prompt:
Create a Terraform module for an AWS VPC that supports public and private subnets across three availability zones. Use variables for vpc_cidr, environment, and project. Provide the following:
- variables.tf with type constraints and descriptions
- main.tf with aws_vpc, aws_subnet, aws_internet_gateway, and aws_route_table resources
- outputs.tf exposing vpc_id, subnet_ids, and azs
- a tags.tf implementing a local `tags` map that includes Environment, Project, and ManagedBy: "Terraform"
- requirements.tf pinning terraform >= 1.3 and aws provider >= 5.0
Solution and results: Using this prompt, the assistant produces a module skeleton you can immediately review and extend. In my experience, the generated code is usually correct, but you must verify the routing between public and private subnets — a common spot for accidental misconfiguration. I evaluate the output by running terraform validate and terraform plan in the module's example directory.
Lessons learned: Always require the assistant to include tags. Tagging is often skipped in generated code, and a module without usable tags is a maintenance headache.
2. Refactor a Monolithic Configuration into Modules
Scenario: A Terraform configuration grew to over 2,000 lines in a single main.tf. The team wants to split it into modules without destroying and recreating resources.
Prompt:
Analyze the Terraform code I'll paste. Suggest a module structure that groups resources by domain (e.g., networking, compute, database). For each group, list the resources that should move and provide the exact `terraform state mv` commands needed to preserve resource addresses. Also provide the module variables and outputs required to keep the configuration working.
Why it works: The prompt forces the assistant to reason about resource addressing, which is the trickiest part of a refactor. I've used this for a production migration that cut the configuration from 4,000 lines to seven modules. The key is to always run terraform plan after each state mv command to confirm no drift.
Real example: In one project, we moved an AWS RDS instance into a database module. The state mv command was terraform state mv aws_db_instance.main module.db.aws_db_instance.main. If you attempt this without the command, Terraform will propose destroying and recreating the database — which is almost certainly what you want to avoid.
3. State Lock Troubleshooting
Scenario: A colleague gets an "Error acquiring the state lock" message from the S3 backend. The lock hasn't been released and you need to figure out who's holding it.
Prompt:
I'm using a Terraform S3 backend with DynamoDB locking. I get "Error acquiring the state lock". Give me a step-by-step diagnostic procedure:
1. How to inspect the DynamoDB lock table and find the lock item (use AWS CLI commands).
2. How to determine whether the lock is stale based on the timestamp.
3. How to safely remove the lock if it's abandoned, and what to avoid.
4. List the team-wide preventive measures (e.g., forced locking policy, CI serialization).
Solution and results: This prompt produces a concrete checklist, including the aws dynamodb get-item command with --key '{"LockID":{"S":"/bucket/key/workspace"}}'. In a real incident, the lock was held by a crashed CI job, and the timestamp was over an hour old. Deleting the item and re-running terraform plan fixed the issue. The preventive measure — adding a CI lock guard such as a GitHub Actions concurrency: terraform block — became part of the team standards.
Source: Official Terraform backend state locking docs explain the LockID format and recommended table setup.
4. Configure a Remote State Backend
Scenario: You're starting a new project and want to establish remote state from the first commit.
Prompt:
Write Terraform code to provision an Amazon S3 bucket for remote state and a DynamoDB table for locking. Use SSE-S3 encryption and include a lifecycle rule to keep only noncurrent versions for 30 days. Then provide a backend.tf snippet that references the bucket and key for the workspace "dev". Also give AWS CLI commands to create the bucket and table if you prefer manual setup.
What to expect: The assistant will generate a standard configuration with aws_s3_bucket_versioning, aws_s3_bucket_server_side_encryption_configuration, and aws_dynamodb_table. You append lifecycle { create_before_destroy = true } if you need no downtime. I also recommend setting force_destroy = false on the bucket to protect valuable state history.
Real-world use: On a client project, applying this prompt reduced accidental state corruption because everyone used the same bucket with versioning and encryption. I've seen teams skip encryption; the prompt makes it explicit.
Source: Check HashiCorp Learn's Backend configuration guide.
5. Multi-Cloud Parity: AWS → Azure, GCP
Scenario: Your architecture is defined on AWS, but you need a parallel deployment in Azure and GCP to satisfy data-residency requirements.
Prompt:
I'm deploying a two-tier app on AWS with a VPC, an EC2 instance (t3.micro), an RDS PostgreSQL (db.t3.micro), and an S3 bucket with versioning. Using Terraform, provide the equivalent resource definitions on Azure and Google Cloud. Preserve the same CIDR scheme and environment variable names. For each resource, include a short table comparing the names: e.g., aws_vpc → azurerm_virtual_network / google_compute_network.
Why it's useful: This prompt generates a side-by-side comparison that helps teams understand provider differences. For Azure, the counterpart to AWS S3 is azurerm_storage_account (blob storage) — but the access semantics differ dramatically. For GCP, google_storage_bucket. The prompt's mention of a comparison table forces the assistant to be explicit about mappings.
Example table:
| AWS | Azure | GCP |
|---|---|---|
| aws_vpc | azurerm_virtual_network | google_compute_network |
| aws_subnet | azurerm_subnet | google_compute_subnetwork |
| aws_ec2_instance | azurerm_linux_virtual_machine | google_compute_instance |
| aws_rds_postgres | azurerm_postgresql_flexible_server | google_sql_database_instance |
| aws_s3_bucket | azurerm_storage_blob | google_storage_bucket |
Results: I've used this to propose a migration plan to a financial-services client. The output wasn't production-ready, but it reduced the discovery phase from weeks to days. The caveat: service-level differences (e.g., Azure's VNet peering vs. GCP's VPC peering) still require human judgment.
6. Security Review as a Senior Engineer
Scenario: A junior engineer has written a main.tf for an IAM role and S3 bucket. You need a fast, structured review.
Prompt:
Act as a senior Terraform security reviewer. Analyze the code I paste. For each resource, look for:
- IAM policies with wildcard actions or resources
- Publicly accessible storage buckets
- Unencrypted storage or databases
- Hardcoded variable values that look like secrets
- Missing versioning or lifecycle rules
Output a table with columns: Resource, Issue, Severity (Critical/High/Medium), and a fixed code snippet. Don't skip the fixed snippet.
Practical outcome: When I ran this against our legacy S3 access policy, the model correctly flagged Action: "*" and Resource: "*" as critical. I've also paired this prompt with checkov in CI. For official reference, AWS's "Least privilege" IAM guidance is the baseline.
7. Dynamic Blocks for Reusable Rules
Scenario: You have a long list of ingress and egress rules for a security group. Instead of repeating ingress {} blocks, you want a data-driven approach.
Prompt:
Rewrite the following Terraform security group resource to use dynamic blocks. Store the rules in a local variable as a list of objects, each with from_port, to_port, protocol, cidr_blocks. Use `for_each` and `dynamic "ingress"` and `dynamic "egress"`. Ensure the rules map is declared at the top of the file in a readable format.
Example excerpt:
variable "sg_rules" {
type = list(object({
from_port = number
to_port = number
protocol = string
cidr_blocks = list(string)
}))
default = []
}
resource "aws_security_group" "app" {
name = "app_sg"
dynamic "ingress" {
for_each = var.sg_rules
content {
from_port = ingress.value.from_port
to_port = ingress.value.to_port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
}
Results: This pattern let us turn a 400-line security group into 40 lines of configuration. It also makes code review easier: the rule list is a single variable, which can be validated with validation blocks.
8. Preconditions and Postconditions in Terraform
Scenario: You want to enforce that a storage bucket has versioning enabled before the apply is considered successful, without manual review.
Prompt:
Enhance this S3 bucket resource with a precondition and a postcondition. The precondition should fail if the environment is "prod" and versioning is not enabled. The postcondition should verify that the bucket's versioning status is "Enabled" after apply. Use checks syntax if that is cleaner. Show a terraform test snippet if possible.
Why it matters: Terraform's precondition and postcondition blocks (introduced in 1.5) let you bake assertions into your configuration. For example:
resource "aws_s3_bucket" "b" {
bucket = "my-bucket"
versioning {
enabled = var.force_versioning
}
lifecycle {
precondition {
condition = !var.is_prod || var.force_versioning
error_message = "Versioning must be enabled in prod."
}
postcondition {
condition = self.versioning[0].enabled
error_message = "Versioning was not enabled on the bucket."
}
}
}
Source: HashiCorp's Custom Condition Checks covers preconditions and postconditions in detail.
9. Upgrade Terraform Version Without Breaking the State
Scenario: You're on Terraform 0.13 and need to move to 1.5. Provider and state formats have changed.
Prompt:
I'm upgrading Terraform from 0.13 to 1.5. List the breaking changes between these versions, then create a migration plan that includes:
- The `terraform state replace-provider` commands for any provider address changes (e.g., registry.terraform.io/-/aws to registry.terraform.io/hashicorp/aws)
- Required syntax updates in my configuration (e.g., `count` instead of `depends_on`? I know the contexts)
- Steps to verify the upgrade with `terraform plan` before and after.
Practical experience: The most frequent issue when upgrading is the provider source migration. For AWS, the command is:
```bashterraform state replace-provider "registry.terraform.io/-/aws" "registry.terraform.io/hashicorp/aws"
**Source**: HashiCorp's [Upgrade guides](https://developer.hashicorp.com/terraform/upgrade-guides) are the canonical reference.
## 10. Import Existing Infrastructure
**Scenario**: A team has an AWS account full of manually created resources. You want to begin managing them with Terraform without recreation.
**Prompt**:
```text
I have these existing AWS resources:
- VPC with ID vpc-0a1b2c3d4e5f67890
- Subnet with ID subnet-0a1b2c3d4e5f67890
- IAM role named "my-app-role"
Generate the `terraform import` commands for each resource and provide the initial configuration that matches these IDs. The configuration should not have any `force_new` attributes that would cause recreation, so comment out any parameters you are unsure about. After import, run `terraform plan` to show no diff.
Results: This workflow is extremely useful for legacy environments. In one case, importing a VPC and its subnets let us start making changes without downtime. Just remember that some resources, like an existing IAM role's trust policy, require careful ordering. You may need to run the import command multiple times as you refine the configuration.
Source: Terraform's Import documentation and terraform state show are your best friends.
11. Kubernetes / Helm via Terraform
Scenario: You manage an EKS cluster with Terraform, but the applications are deployed separately. The team wants to use Terraform and Helm to standardize application deployment.
Prompt:
Write a Terraform module that deploys the nginx-ingress controller to an existing EKS cluster using the Helm provider. Use a variable for the cluster's endpoint, cluster_ca_certificate, and authentication token. Example:
provider "helm" {
kubernetes {
host = var.kubernetes_endpoint
cluster_ca_certificate = base64decode(var.kubernetes_cluster_ca_certificate)
token = data.aws_eks_cluster_auth.cluster.token
}
}
Then create a `helm_release` resource for ingress-nginx with a values override for `replicaCount`.
Why it works: This prompt combines the AWS and Helm providers, which is a common stumbling block. In my experience, the assistant will also suggest setting create_namespace = true in the release. That's a good practice, but you need to add the namespace to the Kubernetes provider's namespace field during bootstrap.
Result: A working Helm deployment on EKS, ready for CI.
12. Cost Estimation with Infracost
Scenario: You want to get cost visibility on every terraform plan without waiting for a manual review.
Prompt:
Write a GitHub Actions workflow that:
- Runs `terraform plan -out=tfplan.bin`
- Runs `infracost breakdown --path tfplan.bin`
- Posts the table as a comment on the pull request
- Fails if the estimated monthly cost increases by more than 20% compared to the base branch
Use the official infracost-action/terraform-plan-usage-action. Show the YAML and any required environment variables.
Real-world insight: This prompt is an exercise in careful workflow design. The assistant might suggest using infracost-cloud or the costPolicy feature; I'd recommend starting with the free open-source CLI. A quick source check: Infracost's GitHub Action docs are current and reliable.
Result: In a recent project, adding this workflow caught a $400/month cost increase in a staging environment before the PR was merged.
Conclusion
These twelve prompts cover the most common Terraform pain points: modularization, state, multi-cloud parity, security, and cost. They are not a replacement for understanding the tool — they are a force multiplier. The best prompt is the one you adapt to your own context. Always verify the generated code with terraform validate and a plan; an AI assistant should be the junior engineer on your team, not the final reviewer.
Start with the prompts that match your current bottleneck. If state is hurting you, start with prompt #3 or #4. If you're moving to multi-cloud, jump to #5. And keep the official Terraform documentation close.
You'll find the full source of examples on my blog at asibiont.com/blog, along with common patterns for working with AI assistants and IaC.
Comments