Introduction
Infrastructure as Code (IaC) has transformed how teams manage cloud resources, and Terraform by HashiCorp remains the dominant tool for provisioning across AWS, Azure, and GCP. Yet many engineers struggle with structuring modules, handling state safely, and orchestrating multi-cloud environments. This collection of 10 prompts will help you generate Terraform configurations that follow best practices—from reusable modules to remote state locking and cross-cloud networking. Each prompt includes a realistic example and output, so you can adapt them directly to your workflows.
1. Generate a Reusable VPC Module
Task: Create a Terraform module that provisions a VPC with public and private subnets, an internet gateway, and route tables, supporting multiple environments via variable input.
Prompt:
Write a Terraform module for AWS that creates a VPC with CIDR block 10.0.0.0/16. Include public subnets in us-east-1a and us-east-1b, private subnets in the same AZs, an internet gateway, and a NAT gateway for private subnets. Use variables for environment (dev/staging/prod) and region. Output the VPC ID, subnet IDs, and NAT gateway IP.
Example Output:
# variables.tf
variable "environment" {
description = "Environment name"
type = string
}
variable "region" {
description = "AWS region"
type = string
default = "us-east-1"
}
# main.tf
provider "aws" {
region = var.region
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "${var.environment}-vpc"
}
}
resource "aws_subnet" "public" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)
availability_zone = "${var.region}${element(["a", "b"], count.index)}"
map_public_ip_on_launch = true
tags = {
Name = "${var.environment}-public-${count.index + 1}"
}
}
resource "aws_internet_gateway" "gw" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.environment}-igw"
}
}
resource "aws_eip" "nat" {
domain = "vpc"
}
resource "aws_nat_gateway" "nat" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public[0].id
tags = {
Name = "${var.environment}-nat"
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.gw.id
}
tags = {
Name = "${var.environment}-public-rt"
}
}
resource "aws_route_table_association" "public" {
count = 2
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
output "vpc_id" {
value = aws_vpc.main.id
}
output "public_subnet_ids" {
value = aws_subnet.public[*].id
}
output "nat_gateway_ip" {
value = aws_eip.nat.public_ip
}
2. Manage State with Remote Backend (S3 + DynamoDB)
Task: Configure Terraform state storage in an S3 bucket with DynamoDB for state locking and consistency.
Prompt:
Generate a Terraform backend configuration that stores state in an S3 bucket named "mycompany-tfstate" in region us-west-2, with DynamoDB table "tf-locks" for locking. Include server-side encryption and versioning on the bucket. Provide the DynamoDB table schema.
Example Output:
# backend.tf erraform {
backend "s3" {
bucket = "mycompany-tfstate"
key = "prod/network/terraform.tfstate"
region = "us-west-2"
encrypt = true
dynamodb_table = "tf-locks"
}
}
# Setup script (apply once before backend)
resource "aws_s3_bucket" "tfstate" {
bucket = "mycompany-tfstate"
acl = "private"
versioning {
enabled = true
}
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
}
resource "aws_dynamodb_table" "terraform_locks" {
name = "tf-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
3. Provision an EC2 Instance with Security Groups
Task: Launch an EC2 instance with specific security group rules (SSH from office IP, HTTP from anywhere) and user data for a web server.
Prompt:
Create Terraform code to launch an EC2 t3.micro instance in us-east-1 using Amazon Linux 2. Attach a security group allowing SSH (port 22) from 203.0.113.0/24 and HTTP (port 80) from 0.0.0.0/0. Include user data that installs Apache and serves a custom index.html.
Example Output:
provider "aws" {
region = "us-east-1"
}
data "aws_ami" "amazon_linux_2" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
resource "aws_security_group" "web_sg" {
name = "web-sg"
description = "Allow SSH and HTTP"
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["203.0.113.0/24"]
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux_2.id
instance_type = "t3.micro"
vpc_security_group_ids = [aws_security_group.web_sg.id]
user_data = <<-EOF
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from Terraform</h1>" > /var/www/html/index.html
EOF
tags = {
Name = "web-server"
}
}
4. Create a Multi-Cloud Network with AWS and Azure
Task: Set up a VPC in AWS and a VNet in Azure, then connect them via a VPN tunnel for hybrid networking.
Prompt:
Write Terraform code to create an AWS VPC (10.1.0.0/16) with a VPN gateway, and an Azure VNet (10.2.0.0/16) with a local network gateway pointing to the AWS VPN endpoint. Use a shared secret "secret123" for the IPsec tunnel. Output the VPN public IPs from both sides.
Example Output:
# aws/main.tf
provider "aws" {
region = "us-east-1"
}
resource "aws_vpc" "main" {
cidr_block = "10.1.0.0/16"
}
resource "aws_vpn_gateway" "gw" {
vpc_id = aws_vpc.main.id
}
resource "aws_customer_gateway" "azure" {
bgp_asn = 65000
ip_address = "AZURE_VPN_PUBLIC_IP" # Replace with actual Azure VPN IP
type = "ipsec.1"
}
resource "aws_vpn_connection" "main" {
vpn_gateway_id = aws_vpn_gateway.gw.id
customer_gateway_id = aws_customer_gateway.azure.id
type = "ipsec.1"
static_routes_only = true
tunnel1_preshared_key = "secret123"
}
output "aws_vpn_public_ip" {
value = aws_vpn_connection.main.tunnel1_address
}
# azure/main.tf
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "rg" {
name = "hybrid-rg"
location = "East US"
}
resource "azurerm_virtual_network" "vnet" {
name = "hybrid-vnet"
address_space = ["10.2.0.0/16"]
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
}
resource "azurerm_local_network_gateway" "aws" {
name = "aws-gateway"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
gateway_address = "AWS_VPN_PUBLIC_IP" # Replace with AWS VPN IP
address_space = ["10.1.0.0/16"]
}
resource "azurerm_virtual_network_gateway" "vpn" {
name = "hybrid-vpn-gw"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
type = "Vpn"
vpn_type = "RouteBased"
sku = "VpnGw1"
ip_configuration {
name = "vnetGatewayConfig"
subnet_id = azurerm_subnet.gateway.id
public_ip_address_id = azurerm_public_ip.vpn.id
}
}
resource "azurerm_public_ip" "vpn" {
name = "vpn-pip"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
allocation_method = "Dynamic"
}
output "azure_vpn_public_ip" {
value = azurerm_public_ip.vpn.ip_address
}
5. Use Terraform Workspaces for Environment Isolation
Task: Demonstrate how to use Terraform workspaces to manage dev, staging, and prod environments with a single configuration.
Prompt:
Show how to structure a Terraform configuration for an S3 bucket where the bucket name includes the workspace name. Provide an example using terraform.workspace to differentiate dev, staging, and prod.
Example Output:
# main.tf
resource "aws_s3_bucket" "data" {
bucket = "${var.project}-${terraform.workspace}-data"
acl = "private"
tags = {
Environment = terraform.workspace
}
}
# Usage
# terraform workspace new dev
# terraform workspace select dev
# terraform apply - creates "myproject-dev-data"
# terraform workspace new prod
# terraform workspace select prod
# terraform apply - creates "myproject-prod-data"
6. Generate an IAM Role with Least Privilege Policy
Task: Create an IAM role for an EC2 instance that allows only S3 read access to a specific bucket, following least privilege.
Prompt:
Write Terraform to create an IAM role named "ec2-s3-reader" with a trust policy allowing EC2 to assume it. Attach a policy that grants only s3:GetObject and s3:ListBucket on the bucket "my-app-assets". Include an instance profile.
Example Output:
resource "aws_iam_role" "ec2_s3_reader" {
name = "ec2-s3-reader"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
}
]
})
}
resource "aws_iam_policy" "s3_read" {
name = "s3-read-only"
description = "Read-only access to my-app-assets"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:ListBucket"
]
Resource = [
"arn:aws:s3:::my-app-assets",
"arn:aws:s3:::my-app-assets/*"
]
}
]
})
}
resource "aws_iam_role_policy_attachment" "attach" {
role = aws_iam_role.ec2_s3_reader.name
policy_arn = aws_iam_policy.s3_read.arn
}
resource "aws_iam_instance_profile" "profile" {
name = "ec2-s3-reader-profile"
role = aws_iam_role.ec2_s3_reader.name
}
7. Define a Terraform Module for Kubernetes on EKS
Task: Create a reusable module that provisions an EKS cluster with managed node groups, using variables for cluster name, instance types, and node count.
Prompt:
Write a Terraform module for AWS EKS. Use the official AWS EKS module. Accept variables: cluster_name, instance_types (list), desired_size, min_size, max_size. Output the cluster endpoint and kubeconfig-certificate-authority-data.
Example Output:
# modules/eks/main.tf
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 19.0"
cluster_name = var.cluster_name
cluster_version = "1.27"
vpc_id = var.vpc_id
subnet_ids = var.subnet_ids
eks_managed_node_groups = {
main = {
desired_size = var.desired_size
min_size = var.min_size
max_size = var.max_size
instance_types = var.instance_types
}
}
}
output "cluster_endpoint" {
value = module.eks.cluster_endpoint
}
output "cluster_certificate_authority_data" {
value = module.eks.cluster_certificate_authority_data
}
# modules/eks/variables.tf
variable "cluster_name" {
type = string
}
variable "vpc_id" {
type = string
}
variable "subnet_ids" {
type = list(string)
}
variable "instance_types" {
type = list(string)
default = ["t3.medium"]
}
variable "desired_size" {
type = number
default = 2
}
variable "min_size" {
type = number
default = 1
}
variable "max_size" {
type = number
default = 5
}
8. Automate Terraform Plan and Apply in CI/CD
Task: Create a GitHub Actions workflow that runs terraform plan on pull requests and terraform apply on merge to main.
Prompt:
Generate a GitHub Actions workflow file that runs terraform init, fmt, validate, and plan on PRs to main, and terraform apply on push to main. Use the hashicorp/setup-terraform action. Store AWS credentials as secrets.
Example Output:
name: "Terraform CI"
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
terraform:
name: Terraform
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.6.0
- name: Terraform Init
run: terraform init
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Terraform Format
run: terraform fmt -check
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
if: github.event_name == 'pull_request'
run: terraform plan -out=tfplan
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Terraform Apply
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -auto-approve tfplan
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
9. Use Terraform Data Sources to Fetch Existing Resources
Task: Query an existing VPC and subnet IDs using data sources, then launch an EC2 instance in that subnet.
Prompt:
Write Terraform that uses data sources to fetch a VPC named "default" (tag Name=default) and its first public subnet. Then launch an EC2 instance in that subnet with a security group allowing SSH.
Example Output:
data "aws_vpc" "selected" {
tags = {
Name = "default"
}
}
data "aws_subnets" "public" {
filter {
name = "vpc-id"
values = [data.aws_vpc.selected.id]
}
filter {
name = "map-public-ip-on-launch"
values = ["true"]
}
}
data "aws_subnet" "first" {
id = data.aws_subnets.public.ids[0]
}
resource "aws_security_group" "ssh" {
name = "allow-ssh"
description = "Allow SSH inbound"
vpc_id = data.aws_vpc.selected.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
subnet_id = data.aws_subnet.first.id
vpc_security_group_ids = [aws_security_group.ssh.id]
tags = {
Name = "web-from-data-source"
}
}
10. Implement a Terraform Module for Multi-Cloud DNS (Route53 + Azure DNS)
Task: Create a module that manages DNS records across AWS Route53 and Azure DNS, using conditional creation based on a variable.
Prompt:
Write a Terraform module that accepts a variable "cloud_provider" (aws or azure). If "aws", create a Route53 A record pointing to an IP. If "azure", create an Azure DNS A record. Output the FQDN.
Example Output:
# modules/dns/main.tf
variable "cloud_provider" {
description = "Cloud provider for DNS"
type = string
validation {
condition = contains(["aws", "azure"], var.cloud_provider)
error_message = "Must be aws or azure."
}
}
variable "zone_name" {
type = string
}
variable "record_name" {
type = string
}
variable "ip_address" {
type = string
}
resource "aws_route53_record" "a" {
count = var.cloud_provider == "aws" ? 1 : 0
zone_id = data.aws_route53_zone.selected[0].zone_id
name = var.record_name
type = "A"
ttl = 300
records = [var.ip_address]
}
data "aws_route53_zone" "selected" {
count = var.cloud_provider == "aws" ? 1 : 0
name = var.zone_name
}
resource "azurerm_dns_a_record" "a" {
count = var.cloud_provider == "azure" ? 1 : 0
name = var.record_name
zone_name = var.zone_name
resource_group_name = var.resource_group_name
ttl = 300
records = [var.ip_address]
}
output "fqdn" {
value = var.cloud_provider == "aws" ? (
var.record_name == "@" ? var.zone_name : "${var.record_name}.${var.zone_name}"
) : (
"${var.record_name}.${var.zone_name}"
)
}
Conclusion
These 10 prompts cover the core patterns every IaC practitioner needs—from modular design and state management to multi-cloud networking and CI/CD integration. By using these as templates, you can accelerate your Terraform workflows while following community best practices. The official Terraform documentation at developer.hashicorp.com/terraform provides further details on providers and modules. Start with the VPC module, add remote state, and gradually expand to multi-cloud setups. Experiment, iterate, and treat your infrastructure as code.
Comments