10 Prompts for Terraform and IaC: From Modules to Multi-Cloud

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.

← All posts

Comments