10 Prompts for Terraform and IaC Mastery: Modules, State, Multi-Cloud in 2026

Introduction

Infrastructure as Code (IaC) has evolved from a niche DevOps practice to the backbone of modern cloud operations. Terraform, developed by HashiCorp, remains the most widely adopted IaC tool, with over 90% of cloud-native organizations using it in production as of 2026 (source: HashiCorp State of Cloud Survey 2025). However, writing effective Terraform code—especially when dealing with reusable modules, state management, and multi-cloud architectures—requires more than basic syntax knowledge. It demands targeted prompts that guide AI assistants, code generators, or even human collaborators toward production-ready solutions.

This article presents a curated collection of 10 expert-level prompts for Terraform and IaC, organized into three categories: basic, advanced, and expert. Each prompt is accompanied by a real-world example result, showing exactly how to apply it. Whether you are a site reliability engineer, a cloud architect, or a DevOps lead, these prompts will help you write cleaner, safer, and more scalable Terraform code in 2026.

Basic Prompts

1. Provision an AWS EC2 Instance with Security Groups

Task: Create a Terraform configuration that launches an EC2 instance in AWS with a security group allowing SSH and HTTP access.

Prompt:

"Generate a Terraform configuration for AWS that provisions a t3.micro EC2 instance running Ubuntu 24.04 LTS. Include a security group that allows inbound SSH (port 22) from my IP address and HTTP (port 80) from anywhere. Use the AWS provider version 5.x. Output the public IP of the instance."

Example Result:

# main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

# Security group
data "http" "my_ip" {
  url = "https://checkip.amazonaws.com"
}

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 = ["${chomp(data.http.my_ip.response_body)}/32"]
  }

  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"]
  }
}

# EC2 instance
resource "aws_instance" "web" {
  ami                    = "ami-0c55b159cbfafe1f0" # Ubuntu 24.04 LTS
  instance_type          = "t3.micro"
  vpc_security_group_ids = [aws_security_group.web_sg.id]

  tags = {
    Name = "web-server"
  }
}

output "public_ip" {
  value = aws_instance.web.public_ip
}

Explanation: This prompt is basic but covers essential concepts: provider configuration, data sources (dynamic IP detection), resource dependencies, and outputs. It’s a solid starting point for beginners.

2. Create a Terraform Module for a VPC

Task: Write a reusable Terraform module that creates a VPC with public and private subnets.

Prompt:

"Design a Terraform module named 'vpc' that accepts inputs for VPC CIDR, environment name, and number of public/private subnets. The module should create the VPC, internet gateway, public subnets with auto-assigned public IPs, private subnets, and a single NAT gateway for private subnet internet access. Output the subnet IDs."

Example Result:

# modules/vpc/main.tf
variable "vpc_cidr" {
  type = string
}

variable "environment" {
  type = string
}

variable "public_subnet_count" {
  type    = number
  default = 2
}

variable "private_subnet_count" {
  type    = number
  default = 2
}

resource "aws_vpc" "main" {
  cidr_block = var.vpc_cidr
  tags = {
    Name = "${var.environment}-vpc"
  }
}

resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.main.id
  tags = {
    Name = "${var.environment}-igw"
  }
}

resource "aws_subnet" "public" {
  count                   = var.public_subnet_count
  vpc_id                  = aws_vpc.main.id
  cidr_block              = cidrsubnet(var.vpc_cidr, 8, count.index)
  map_public_ip_on_launch = true
  availability_zone       = data.aws_availability_zones.available.names[count.index]
  tags = {
    Name = "${var.environment}-public-${count.index}"
  }
}

resource "aws_subnet" "private" {
  count             = var.private_subnet_count
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 8, count.index + var.public_subnet_count)
  availability_zone = data.aws_availability_zones.available.names[count.index]
  tags = {
    Name = "${var.environment}-private-${count.index}"
  }
}

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"
  }
}

output "public_subnet_ids" {
  value = aws_subnet.public[*].id
}

output "private_subnet_ids" {
  value = aws_subnet.private[*].id
}

data "aws_availability_zones" "available" {
  state = "available"
}

Explanation: This module demonstrates best practices: variable validation (implicit via type constraints), resource naming conventions, and dynamic subnet calculation using cidrsubnet. It’s ready for reuse across multiple environments.

Advanced Prompts

3. Implement Remote State Backend with DynamoDB Locking

Task: Configure Terraform to use an S3 backend for state storage with DynamoDB for state locking.

Prompt:

"Write a Terraform configuration that uses an S3 bucket as a remote backend for state files, with DynamoDB table for state locking. Assume the S3 bucket and DynamoDB table already exist. The backend should encrypt state at rest using AES-256. Also include a local backend fallback for development."

Example Result:

# backend.tf
terraform {
  backend "s3" {
    bucket         = "my-org-terraform-state"
    key            = "prod/network/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
  }
}

# For local development, override with:
# terraform init -backend-config="key=dev/network/terraform.tfstate"

Explanation: Remote state is critical for team collaboration. This prompt ensures state is centrally stored and locked to prevent concurrent modifications. The DynamoDB table with primary key LockID (string) is standard practice since 2019.

4. Manage Multi-Cloud Infrastructure with AWS and Azure

Task: Create a Terraform configuration that deploys a web application across AWS and Azure, with shared state.

Prompt:

"Design a multi-cloud Terraform setup that provisions an EC2 instance in AWS (us-east-1) and a VM in Azure (East US) as part of the same deployment. Use a single state file stored in S3. The AWS instance should run Nginx, and the Azure VM should run Apache. Output both public IPs. Use provider aliases."

Example Result:

# main.tf
terraform {
  backend "s3" {
    bucket = "multi-cloud-state"
    key    = "webapp/terraform.tfstate"
    region = "us-east-1"
  }
}

provider "aws" {
  region = "us-east-1"
}

provider "azurerm" {
  features {}
  alias = "az"
}

resource "aws_instance" "web_aws" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"
  user_data     = <<-EOF
              #!/bin/bash
              apt-get update
              apt-get install -y nginx
              systemctl start nginx
              EOF
  tags = {
    Name = "multi-cloud-aws"
  }
}

resource "azurerm_resource_group" "rg" {
  provider = azurerm.az
  name     = "multi-cloud-rg"
  location = "East US"
}

resource "azurerm_linux_virtual_machine" "web_azure" {
  provider            = azurerm.az
  name                = "multi-cloud-vm"
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  size                = "Standard_B1s"
  admin_username      = "adminuser"
  admin_password      = "Password1234!"
  disable_password_authentication = false
  network_interface_ids = [azurerm_network_interface.nic.id]

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_reference {
    publisher = "Canonical"
    offer     = "UbuntuServer"
    sku       = "18.04-LTS"
    version   = "latest"
  }
}

output "aws_ip" {
  value = aws_instance.web_aws.public_ip
}

output "azure_ip" {
  value = azurerm_linux_virtual_machine.web_azure.public_ip_address
}

Explanation: Multi-cloud deployments are powerful but complex. This prompt results in a unified state file, allowing cross-cloud dependencies. Note: In production, use SSH keys instead of passwords.

5. Use Terraform Workspaces for Environment Separation

Task: Implement Terraform workspaces to manage development, staging, and production environments.

Prompt:

"Create a Terraform configuration that uses workspaces to deploy an application across dev, staging, and prod environments. Each workspace should have different instance sizes and tags. Use a single S3 backend with workspace-based state keys."

Example Result:

# main.tf
terraform {
  backend "s3" {
    bucket = "workspace-state"
    key    = "app/terraform.tfstate"
    region = "us-east-1"
  }
}

locals {
  instance_type = {
    dev     = "t2.micro"
    staging = "t3.small"
    prod    = "t3.medium"
  }
  environment = terraform.workspace
}

resource "aws_instance" "app" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = local.instance_type[local.environment]
  tags = {
    Name        = "app-${local.environment}"
    Environment = local.environment
  }
}

Explanation: Workspaces are a lightweight way to manage environments without duplicating code. The state key automatically becomes app/env:workspace_name/terraform.tfstate. Use workspaces for simple environment separation; for complex cases, consider directory-based layouts.

Expert Prompts

6. Build a Custom Terraform Provider for Internal API

Task: Write a skeleton for a custom Terraform provider that manages resources on an internal API.

Prompt:

"Generate the Go code structure for a custom Terraform provider called 'internalapi' that manages a resource 'internalapi_service'. The resource should have attributes: name (string, required), endpoint (string, required), and enabled (bool, optional, default true). Implement CRUD operations using HTTP calls to https://api.internal.example.com/v1/services. Include provider configuration for API key."

Example Result:

// provider.go
package main

import (
    "context"
    "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
    "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func Provider() *schema.Provider {
    return &schema.Provider{
        Schema: map[string]*schema.Schema{
            "api_key": {
                Type:        schema.TypeString,
                Required:    true,
                Sensitive:   true,
                Description: "API key for internal API",
            },
        },
        ResourcesMap: map[string]*schema.Resource{
            "internalapi_service": resourceService(),
        },
        DataSourcesMap: map[string]*schema.Resource{},
    }
}

func resourceService() *schema.Resource {
    return &schema.Resource{
        CreateContext: resourceServiceCreate,
        ReadContext:   resourceServiceRead,
        UpdateContext: resourceServiceUpdate,
        DeleteContext: resourceServiceDelete,
        Schema: map[string]*schema.Schema{
            "name": {
                Type:     schema.TypeString,
                Required: true,
            },
            "endpoint": {
                Type:     schema.TypeString,
                Required: true,
            },
            "enabled": {
                Type:     schema.TypeBool,
                Optional: true,
                Default:  true,
            },
        },
    }
}

func resourceServiceCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
    // HTTP POST to /v1/services with name, endpoint, enabled
    // Set ID from response
    return nil
}

func resourceServiceRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
    // HTTP GET /v1/services/{id}
    return nil
}

func resourceServiceUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
    // HTTP PUT /v1/services/{id}
    return nil
}

func resourceServiceDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
    // HTTP DELETE /v1/services/{id}
    return nil
}

Explanation: Custom providers extend Terraform to any API. This skeleton follows the Terraform Plugin SDK v2, which is the standard for providers as of 2026. The Sensitive flag on api_key ensures it’s not logged.

7. Implement a Policy as Code with Sentinel

Task: Write a Sentinel policy to enforce tagging standards on all AWS resources.

Prompt:

"Create a Sentinel policy for Terraform Cloud that requires all AWS resources to have tags 'Environment' and 'CostCenter'. The policy should allow resources that are not taggable (e.g., data sources). Fail the plan if any taggable resource is missing these tags. Use the tfplan mock."

Example Result:

# enforce_tags.sentinel
import "tfplan/v2" as tfplan

# Get all resources that support tagging
taggable_resources = filter tfplan.resource_changes.values() as rc {
    rc.mode == "managed" and rc.type contains "aws_"
}

# Check each resource for required tags
required_tags = ["Environment", "CostCenter"]

violations = []
for resource in taggable_resources {
    tags = resource.change.after.tags
    if tags else {} {
        for tag in required_tags {
            if tag not in tags {
                append(violations, resource.address)
            }
        }
    }
}

# Enforce policy
main = rule {
    length(violations) == 0
}

Explanation: Sentinel is HashiCorp’s policy-as-code framework, integrated with Terraform Cloud. This policy ensures governance without manual reviews. As of 2026, Sentinel supports multiple policy levels: soft-mandatory, hard-mandatory, and advisory.

8. Use Terraform Stacks for Multi-Region Deployments

Task: Design a Terraform Stack (introduced in Terraform 1.7) to deploy an application across three AWS regions.

Prompt:

"Create a Terraform Stack configuration that deploys an ECS service in us-east-1, eu-west-1, and ap-southeast-1. Each stack component should use the same module but with region-specific variables. The stack should output the load balancer DNS names."

Example Result:

# stack.hcl
stack "global" {
  backend "s3" {
    bucket = "stack-state"
    key    = "global/terraform.tfstate"
  }
}

component "ecs_us_east_1" {
  source = "./modules/ecs-service"
  inputs = {
    region = "us-east-1"
    environment = "prod"
  }
  providers = {
    aws = aws.us_east_1
  }
}

component "ecs_eu_west_1" {
  source = "./modules/ecs-service"
  inputs = {
    region = "eu-west-1"
    environment = "prod"
  }
  providers = {
    aws = aws.eu_west_1
  }
}

component "ecs_ap_southeast_1" {
  source = "./modules/ecs-service"
  inputs = {
    region = "ap-southeast-1"
    environment = "prod"
  }
  providers = {
    aws = aws.ap_southeast_1
  }
}

output "lbs" {
  value = {
    us_east_1    = component.ecs_us_east_1.outputs.lb_dns
    eu_west_1    = component.ecs_eu_west_1.outputs.lb_dns
    ap_southeast_1 = component.ecs_ap_southeast_1.outputs.lb_dns
  }
}

Explanation: Terraform Stacks (GA since Terraform 1.7, late 2025) allow deploying multiple components with shared state. This is ideal for multi-region disaster recovery setups.

9. Automate Terraform State Migration from Local to Remote

Task: Write a script to migrate an existing local Terraform state to an S3 backend.

Prompt:

"Generate a bash script that takes an existing Terraform project with local state and migrates it to an S3 backend. The script should: 1) Create the S3 bucket and DynamoDB table if they don’t exist, 2) Copy the local state file to S3, 3) Update the backend configuration, and 4) Run terraform init -migrate-state."

Example Result:

#!/bin/bash
# migrate_state.sh

set -e

BUCKET_NAME="my-terraform-state-$(date +%s)"
TABLE_NAME="terraform-lock"
REGION="us-east-1"

# Create S3 bucket
aws s3api create-bucket --bucket $BUCKET_NAME --region $REGION
aws s3api put-bucket-versioning --bucket $BUCKET_NAME --versioning-configuration Status=Enabled

# Create DynamoDB table
aws dynamodb create-table \
    --table-name $TABLE_NAME \
    --attribute-definitions AttributeName=LockID,AttributeType=S \
    --key-schema AttributeName=LockID,KeyType=HASH \
    --billing-mode PAY_PER_REQUEST \
    --region $REGION

# Update backend config
echo 'terraform {
  backend "s3" {
    bucket         = "'$BUCKET_NAME'"
    key            = "terraform.tfstate"
    region         = "'$REGION'"
    encrypt        = true
    dynamodb_table = "'$TABLE_NAME'"
  }
}' > backend.tf

# Initialize with migration
terraform init -migrate-state -force-copy

echo "Migration complete. State is now in s3://$BUCKET_NAME/terraform.tfstate"

Explanation: State migration is a common task. This script automates the process, reducing human error. The -force-copy flag is used for automation; in interactive mode, Terraform prompts for confirmation.

10. Debug Terraform State Drift with Custom Script

Task: Create a script that detects and reports state drift for AWS resources.

Prompt:

"Write a Python script using boto3 that compares the actual state of AWS EC2 instances (from AWS API) with the Terraform state file (from S3). Output a report of any drift: missing instances, changed instance types, or modified tags. Use the Terraform state JSON format."

Example Result:

# drift_detector.py
import json
import boto3
import sys

# Load Terraform state from file (or S3)
with open('terraform.tfstate', 'r') as f:
    state = json.load(f)

ec2 = boto3.client('ec2', region_name='us-east-1')

# Get actual instances
actual_instances = {}
paginator = ec2.get_paginator('describe_instances')
for page in paginator.paginate():
    for reservation in page['Reservations']:
        for instance in reservation['Instances']:
            actual_instances[instance['InstanceId']] = {
                'instance_type': instance['InstanceType'],
                'tags': {t['Key']: t['Value'] for t in instance.get('Tags', [])}
            }

# Get Terraform state instances
terraform_instances = {}
for resource in state['resources']:
    if resource['type'] == 'aws_instance':
        for instance in resource['instances']:
            attrs = instance['attributes']
            terraform_instances[attrs['id']] = {
                'instance_type': attrs['instance_type'],
                'tags': attrs.get('tags', {})
            }

# Compare
drift = []
for id, tf_state in terraform_instances.items():
    if id not in actual_instances:
        drift.append(f"{id}: Missing in AWS")
    else:
        actual = actual_instances[id]
        if tf_state['instance_type'] != actual['instance_type']:
            drift.append(f"{id}: Instance type changed from {tf_state['instance_type']} to {actual['instance_type']}")
        if tf_state['tags'] != actual['tags']:
            drift.append(f"{id}: Tags changed. Terraform: {tf_state['tags']}, AWS: {actual['tags']}")

if drift:
    print("Drift detected:")
    for d in drift:
        print(f"  - {d}")
    sys.exit(1)
else:
    print("No drift detected.")

Explanation: State drift is a common issue in IaC. This script provides a programmatic way to detect drift, which can be integrated into CI/CD pipelines for proactive monitoring.

Conclusion

Terraform continues to dominate the IaC landscape in 2026, but mastery requires moving beyond basic configurations. The 10 prompts in this article cover a spectrum from foundational skills (EC2 provisioning, modules) to advanced techniques (multi-cloud, workspaces) and expert-level practices (custom providers, Sentinel policies, stacks, state migration, drift detection).

To get the most out of these prompts, adapt them to your specific cloud environments—whether AWS, Azure, Google Cloud, or a hybrid mix. Regularly review HashiCorp’s official documentation and Terraform’s changelog for new features like Stacks and enhanced provider SDKs. If you use services like AWS or Azure, consider integrating with ASI Biont for automated infrastructure management — ASI Biont supports connecting to AWS and Azure through API for real-time state monitoring; details available on asibiont.com/courses.

Remember: the best IaC is not just code that works, but code that is modular, stateful, and auditable. Use these prompts as a starting point to build infrastructure that scales across clouds and teams.

← All posts

Comments