Terraform Demos
Copy and modify these pre-built Terraform configurations for your infrastructure.
AWSAWS S3 Bucket with Versioning
●AWSUploadedCreate an S3 bucket with versioning, encryption, and lifecycle policy.
By System · Storage
▾
AWS S3 Bucket with Versioning
●AWSUploadedCreate an S3 bucket with versioning, encryption, and lifecycle policy.
By System · Storage
main.tf · variables.tf · outputs.tf
Downloadterraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
resource "aws_s3_bucket" "main" {
bucket = var.bucket_name
tags = {
Name = var.bucket_name
Environment = var.environment
}
}
resource "aws_s3_bucket_versioning" "main" {
bucket = aws_s3_bucket.main.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "main" {
bucket = aws_s3_bucket.main.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_s3_bucket_public_access_block" "main" {
bucket = aws_s3_bucket.main.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
variable "bucket_name" {
description = "S3 bucket name"
type = string
}
variable "aws_region" {
description = "AWS region"
type = string
default = "us-east-1"
}
variable "environment" {
description = "Environment"
type = string
default = "dev"
}Troubleshooting & Solutions
Common Issues
- Bucket name already exists
- Cannot access bucket
- Versioning not enabled
Solutions
- Use a globally unique bucket name
- Check IAM permissions
- Verify versioning configuration
Troubleshooting Steps
- Verify bucket name is globally unique
- Check IAM permissions for S3
- Review bucket policy
- Ensure region is correct
Version compatibility: Terraform >= 1.0, AWS Provider >= 4.0
AWSAWS VPC with Public and Private Subnets
●AWSUploadedA complete VPC setup with public and private subnets, an internet gateway, and a NAT gateway.
By System · Networking
▾
AWS VPC with Public and Private Subnets
●AWSUploadedA complete VPC setup with public and private subnets, an internet gateway, and a NAT gateway.
By System · Networking
main.tf · variables.tf · outputs.tf
Downloadterraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = { Name = "${var.environment}-vpc" }
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = { Name = "${var.environment}-igw" }
}
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = { Name = "${var.environment}-public-${count.index}" }
}
resource "aws_subnet" "private" {
count = length(var.private_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.private_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
tags = { Name = "${var.environment}-private-${count.index}" }
}
resource "aws_eip" "nat" {
domain = "vpc"
}
resource "aws_nat_gateway" "main" {
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.main.id
}
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main.id
}
}
resource "aws_route_table_association" "public" {
count = length(aws_subnet.public)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "private" {
count = length(aws_subnet.private)
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private.id
}
variable "aws_region" {
type = string
default = "us-east-1"
}
variable "environment" {
type = string
default = "dev"
}
variable "vpc_cidr" {
type = string
default = "10.0.0.0/16"
}
variable "public_subnet_cidrs" {
type = list(string)
default = ["10.0.1.0/24", "10.0.2.0/24"]
}
variable "private_subnet_cidrs" {
type = list(string)
default = ["10.0.101.0/24", "10.0.102.0/24"]
}
variable "availability_zones" {
type = list(string)
default = ["us-east-1a", "us-east-1b"]
}Troubleshooting & Solutions
Common Issues
- CIDR block overlap with an existing VPC
- NAT gateway costs more than expected
- Private subnet has no internet access
Solutions
- Pick a non-overlapping CIDR range
- Use one NAT gateway per environment, not per AZ, for cost-sensitive setups
- Confirm the private route table points at the NAT gateway
Troubleshooting Steps
- Check CIDR blocks don't overlap with peered VPCs
- Verify route table associations are correct per subnet
- Confirm NAT gateway is in a public subnet
- Check availability zone names match the target region
Version compatibility: Terraform >= 1.0, AWS Provider >= 5.0
AWSAWS EC2 Instance with Security Group
●AWSUploadedLaunch an EC2 instance with a properly scoped security group and user data.
By System · Compute
▾
AWS EC2 Instance with Security Group
●AWSUploadedLaunch an EC2 instance with a properly scoped security group and user data.
By System · Compute
main.tf · variables.tf · outputs.tf
Downloadterraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
}
resource "aws_security_group" "app" {
name = "${var.app_name}-sg"
description = "Allow SSH and app traffic"
vpc_id = var.vpc_id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [var.ssh_cidr]
}
ingress {
from_port = var.app_port
to_port = var.app_port
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" "app" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
subnet_id = var.subnet_id
vpc_security_group_ids = [aws_security_group.app.id]
user_data = <<-EOF
#!/bin/bash
apt-get update -y
apt-get install -y docker.io
systemctl enable docker
systemctl start docker
EOF
tags = { Name = var.app_name }
}
variable "aws_region" {
type = string
default = "us-east-1"
}
variable "app_name" {
type = string
}
variable "instance_type" {
type = string
default = "t3.micro"
}
variable "app_port" {
type = number
default = 3000
}
variable "ssh_cidr" {
type = string
default = "0.0.0.0/0"
}
variable "vpc_id" {
type = string
}
variable "subnet_id" {
type = string
}Troubleshooting & Solutions
Common Issues
- Instance unreachable via SSH
- AMI not found in region
- Security group too permissive
Solutions
- Scope ssh_cidr to your own IP, not 0.0.0.0/0, in real deployments
- AMI IDs are region-specific — use the data source, not a hardcoded ID
- Restrict ingress rules to only the ports actually needed
Troubleshooting Steps
- Confirm the subnet has a route to an internet gateway if public access is needed
- Check the security group allows your actual source IP
- Verify the instance has a public IP if you're connecting externally
- Check user_data logs at /var/log/cloud-init-output.log on the instance
Version compatibility: Terraform >= 1.0, AWS Provider >= 5.0
AWSAWS Application Load Balancer
●AWSUploadedCreate an ALB with a target group and listener for HTTP traffic.
By System · Networking
▾
AWS Application Load Balancer
●AWSUploadedCreate an ALB with a target group and listener for HTTP traffic.
By System · Networking
main.tf · variables.tf · outputs.tf
Downloadresource "aws_lb" "main" {
name = "${var.app_name}-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = var.public_subnet_ids
}
resource "aws_security_group" "alb" {
name = "${var.app_name}-alb-sg"
vpc_id = var.vpc_id
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_lb_target_group" "app" {
name = "${var.app_name}-tg"
port = var.app_port
protocol = "HTTP"
vpc_id = var.vpc_id
health_check {
path = "/health"
healthy_threshold = 2
unhealthy_threshold = 3
interval = 15
}
}
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.main.arn
port = 80
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}
variable "app_name" {
type = string
}
variable "app_port" {
type = number
default = 3000
}
variable "vpc_id" {
type = string
}
variable "public_subnet_ids" {
type = list(string)
}Troubleshooting & Solutions
Common Issues
- Target group shows unhealthy targets
- ALB times out
- 502 from the ALB
Solutions
- Confirm the health check path actually exists and returns 2xx
- Check the target's security group allows traffic from the ALB's security group
- Verify the target's app is listening on the port the target group expects
Troubleshooting Steps
- Check target group health in the console/CLI
- Confirm the ALB's subnets are public with a route to an IGW
- Verify listener rules point at the right target group
- Check the target instance's own security group, not just the ALB's
Version compatibility: Terraform >= 1.0, AWS Provider >= 5.0
AWSAWS Auto Scaling Group with Launch Template
●AWSUploadedCreate an ASG with a launch template and target-tracking scaling policy.
By System · Compute
▾
AWS Auto Scaling Group with Launch Template
●AWSUploadedCreate an ASG with a launch template and target-tracking scaling policy.
By System · Compute
main.tf · variables.tf · outputs.tf
Downloadresource "aws_launch_template" "app" {
name_prefix = "${var.app_name}-"
image_id = var.ami_id
instance_type = var.instance_type
network_interfaces {
security_groups = [var.security_group_id]
}
tag_specifications {
resource_type = "instance"
tags = { Name = var.app_name }
}
}
resource "aws_autoscaling_group" "app" {
name = "${var.app_name}-asg"
desired_capacity = var.desired_capacity
min_size = var.min_size
max_size = var.max_size
vpc_zone_identifier = var.subnet_ids
target_group_arns = var.target_group_arns
launch_template {
id = aws_launch_template.app.id
version = "$Latest"
}
}
resource "aws_autoscaling_policy" "cpu" {
name = "${var.app_name}-cpu-scaling"
autoscaling_group_name = aws_autoscaling_group.app.name
policy_type = "TargetTrackingScaling"
target_tracking_configuration {
predefined_metric_specification {
predefined_metric_type = "ASGAverageCPUUtilization"
}
target_value = 60
}
}
variable "app_name" {
type = string
}
variable "ami_id" {
type = string
}
variable "instance_type" {
type = string
default = "t3.micro"
}
variable "security_group_id" {
type = string
}
variable "subnet_ids" {
type = list(string)
}
variable "target_group_arns" {
type = list(string)
default = []
}
variable "desired_capacity" {
type = number
default = 2
}
variable "min_size" {
type = number
default = 1
}
variable "max_size" {
type = number
default = 4
}Troubleshooting & Solutions
Common Issues
- Instances launch then immediately terminate
- Scaling policy never triggers
- New launch template version not picked up
Solutions
- Check the launch template's AMI/instance type is valid in the target AZ
- Confirm CloudWatch metrics are actually being published for the ASG
- Reference "$Latest" (not a pinned version) if you want new instances to pick up template changes automatically
Troubleshooting Steps
- Check ASG activity history for the termination reason
- Verify the launch template's security group and subnet are compatible
- Confirm target tracking has enough data points before expecting it to scale
- Check IAM permissions for the ASG service-linked role
Version compatibility: Terraform >= 1.0, AWS Provider >= 5.0
AWSAWS RDS PostgreSQL Database
●AWSUploadedCreate a secure RDS PostgreSQL instance with automated backups.
By System · Storage
▾
AWS RDS PostgreSQL Database
●AWSUploadedCreate a secure RDS PostgreSQL instance with automated backups.
By System · Storage
main.tf · variables.tf · outputs.tf
Downloadresource "aws_db_subnet_group" "main" {
name = "${var.app_name}-db-subnets"
subnet_ids = var.private_subnet_ids
}
resource "aws_security_group" "db" {
name = "${var.app_name}-db-sg"
vpc_id = var.vpc_id
ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [var.app_security_group_id]
}
}
resource "aws_db_instance" "main" {
identifier = "${var.app_name}-db"
engine = "postgres"
engine_version = "16"
instance_class = var.instance_class
allocated_storage = 20
storage_encrypted = true
db_name = var.db_name
username = var.db_username
password = var.db_password
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.db.id]
backup_retention_period = 7
skip_final_snapshot = false
final_snapshot_identifier = "${var.app_name}-final-snapshot"
}
variable "app_name" {
type = string
}
variable "vpc_id" {
type = string
}
variable "private_subnet_ids" {
type = list(string)
}
variable "app_security_group_id" {
type = string
}
variable "instance_class" {
type = string
default = "db.t3.micro"
}
variable "db_name" {
type = string
}
variable "db_username" {
type = string
sensitive = true
}
variable "db_password" {
type = string
sensitive = true
}Troubleshooting & Solutions
Common Issues
- Connection timeout from the app
- Password shows up in state in plain text
- Instance stuck in "creating" for a long time
Solutions
- Confirm the app's security group is allowed in the DB security group's ingress rule
- Use a secrets manager (AWS Secrets Manager, SSM Parameter Store) instead of a plain .tfvars value for db_password in real deployments
- Large allocated_storage or Multi-AZ can add 10-20 minutes to creation — this is normal
Troubleshooting Steps
- Verify the DB subnet group spans private subnets in at least 2 AZs
- Check the app and DB are in the same VPC or have VPC peering
- Confirm storage_encrypted is compatible with your KMS key permissions
- Review RDS event logs in the console for the specific failure
Version compatibility: Terraform >= 1.0, AWS Provider >= 5.0
AWSAWS Lambda Function with API Gateway
●AWSUploadedCreate a Lambda function with an HTTP API Gateway integration.
By System · Other
▾
AWS Lambda Function with API Gateway
●AWSUploadedCreate a Lambda function with an HTTP API Gateway integration.
By System · Other
main.tf · variables.tf · outputs.tf
Downloadresource "aws_iam_role" "lambda" {
name = "${var.function_name}-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
resource "aws_iam_role_policy_attachment" "lambda_basic" {
role = aws_iam_role.lambda.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "aws_lambda_function" "app" {
function_name = var.function_name
role = aws_iam_role.lambda.arn
handler = var.handler
runtime = var.runtime
filename = var.zip_path
timeout = 10
}
resource "aws_apigatewayv2_api" "http" {
name = "${var.function_name}-api"
protocol_type = "HTTP"
}
resource "aws_apigatewayv2_integration" "lambda" {
api_id = aws_apigatewayv2_api.http.id
integration_type = "AWS_PROXY"
integration_uri = aws_lambda_function.app.invoke_arn
payload_format_version = "2.0"
}
resource "aws_apigatewayv2_route" "default" {
api_id = aws_apigatewayv2_api.http.id
route_key = "$default"
target = "integrations/${aws_apigatewayv2_integration.lambda.id}"
}
resource "aws_apigatewayv2_stage" "default" {
api_id = aws_apigatewayv2_api.http.id
name = "$default"
auto_deploy = true
}
resource "aws_lambda_permission" "apigw" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.app.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.http.execution_arn}/*/*"
}
variable "function_name" {
type = string
}
variable "handler" {
type = string
default = "index.handler"
}
variable "runtime" {
type = string
default = "nodejs20.x"
}
variable "zip_path" {
type = string
}Troubleshooting & Solutions
Common Issues
- 403 Forbidden calling the API
- Lambda times out on cold start
- Deployment package too large
Solutions
- Confirm the aws_lambda_permission resource exists and its source_arn matches the actual API
- Increase the timeout and memory for functions with heavy cold-start init work
- Use Lambda layers or container images for large dependencies instead of one big zip
Troubleshooting Steps
- Check CloudWatch Logs for the specific Lambda invocation error
- Verify the IAM role has the AWSLambdaBasicExecutionRole policy attached
- Confirm payload_format_version matches what your handler code expects (1.0 vs 2.0 event shape differs)
- Test the function directly with `aws lambda invoke` before blaming API Gateway
Version compatibility: Terraform >= 1.0, AWS Provider >= 5.0
AWSAWS CloudWatch Alarms and Dashboard
●AWSUploadedCreate CloudWatch alarms and a dashboard for monitoring.
By System · Security
▾
AWS CloudWatch Alarms and Dashboard
●AWSUploadedCreate CloudWatch alarms and a dashboard for monitoring.
By System · Security
main.tf · variables.tf · outputs.tf
Downloadresource "aws_cloudwatch_metric_alarm" "cpu_high" {
alarm_name = "${var.app_name}-cpu-high"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = 300
statistic = "Average"
threshold = 80
alarm_actions = [var.sns_topic_arn]
dimensions = {
InstanceId = var.instance_id
}
}
resource "aws_cloudwatch_dashboard" "main" {
dashboard_name = "${var.app_name}-dashboard"
dashboard_body = jsonencode({
widgets = [
{
type = "metric"
x = 0
y = 0
width = 12
height = 6
properties = {
metrics = [["AWS/EC2", "CPUUtilization", "InstanceId", var.instance_id]]
period = 300
stat = "Average"
region = var.aws_region
title = "CPU Utilization"
}
}
]
})
}
variable "app_name" {
type = string
}
variable "aws_region" {
type = string
default = "us-east-1"
}
variable "instance_id" {
type = string
}
variable "sns_topic_arn" {
type = string
}Troubleshooting & Solutions
Common Issues
- Alarm never triggers
- Too many false-positive alerts
- Dashboard shows no data
Solutions
- Confirm the dimensions block exactly matches the resource's actual metric dimensions
- Tune evaluation_periods/threshold based on real baseline traffic, not guesses
- Check the metric namespace and name are spelled exactly as AWS publishes them
Troubleshooting Steps
- Check alarm state history in the CloudWatch console
- Verify the SNS topic has a confirmed subscription
- Confirm the monitored resource is actually publishing that metric
- Check the region matches where the resource actually lives
Version compatibility: Terraform >= 1.0, AWS Provider >= 5.0
AWSAWS Route53 Hosted Zone and Records
●AWSUploadedCreate a Route53 hosted zone with DNS records.
By System · Networking
▾
AWS Route53 Hosted Zone and Records
●AWSUploadedCreate a Route53 hosted zone with DNS records.
By System · Networking
main.tf · variables.tf · outputs.tf
Downloadresource "aws_route53_zone" "main" {
name = var.domain_name
}
resource "aws_route53_record" "root" {
zone_id = aws_route53_zone.main.zone_id
name = var.domain_name
type = "A"
alias {
name = var.alb_dns_name
zone_id = var.alb_zone_id
evaluate_target_health = true
}
}
resource "aws_route53_record" "www" {
zone_id = aws_route53_zone.main.zone_id
name = "www.${var.domain_name}"
type = "CNAME"
ttl = 300
records = [var.domain_name]
}
variable "domain_name" {
type = string
}
variable "alb_dns_name" {
type = string
}
variable "alb_zone_id" {
type = string
}Troubleshooting & Solutions
Common Issues
- DNS not resolving after apply
- Domain registered elsewhere doesn't pick up records
- Certificate validation stuck pending
Solutions
- DNS propagation can take up to 48 hours even after records are correct — check with `dig`, not just the browser
- Update the domain's nameservers at the registrar to Route53's assigned NS records
- Add the ACM validation CNAME record this hosted zone needs for certificate issuance
Troubleshooting Steps
- Run `dig NS <domain>` to confirm the registrar points at Route53's nameservers
- Run `dig <domain>` to confirm the A/CNAME record resolves
- Check the alias target's zone_id matches the actual ALB/CloudFront zone, not a guessed value
- Verify there's no conflicting record (e.g. both A and CNAME) at the same name
Version compatibility: Terraform >= 1.0, AWS Provider >= 5.0
AWSEKS Cluster with Deployment
●AWSUploadedCreate an EKS cluster with a sample deployment and service.
By System · Containers
▾
EKS Cluster with Deployment
●AWSUploadedCreate an EKS cluster with a sample deployment and service.
By System · Containers
main.tf · variables.tf · outputs.tf · deployment.yaml
Downloadresource "aws_iam_role" "eks_cluster" {
name = "${var.cluster_name}-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "eks.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
resource "aws_iam_role_policy_attachment" "eks_cluster_policy" {
role = aws_iam_role.eks_cluster.name
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
}
resource "aws_eks_cluster" "main" {
name = var.cluster_name
role_arn = aws_iam_role.eks_cluster.arn
version = var.kubernetes_version
vpc_config {
subnet_ids = var.subnet_ids
}
depends_on = [aws_iam_role_policy_attachment.eks_cluster_policy]
}
resource "aws_iam_role" "eks_nodes" {
name = "${var.cluster_name}-node-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "ec2.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
resource "aws_iam_role_policy_attachment" "eks_worker_policy" {
role = aws_iam_role.eks_nodes.name
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
}
resource "aws_iam_role_policy_attachment" "eks_cni_policy" {
role = aws_iam_role.eks_nodes.name
policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
}
resource "aws_eks_node_group" "main" {
cluster_name = aws_eks_cluster.main.name
node_group_name = "${var.cluster_name}-nodes"
node_role_arn = aws_iam_role.eks_nodes.arn
subnet_ids = var.subnet_ids
scaling_config {
desired_size = var.desired_nodes
min_size = 1
max_size = var.max_nodes
}
}
variable "cluster_name" {
type = string
}
variable "kubernetes_version" {
type = string
default = "1.30"
}
variable "subnet_ids" {
type = list(string)
}
variable "desired_nodes" {
type = number
default = 2
}
variable "max_nodes" {
type = number
default = 4
}Troubleshooting & Solutions
Common Issues
- Nodes never join the cluster
- kubectl can't authenticate
- Cluster creation takes 15+ minutes
Solutions
- Confirm subnets are tagged correctly for EKS auto-discovery and span at least 2 AZs
- Run `aws eks update-kubeconfig --name <cluster>` to refresh local kubectl auth
- 10-15 minute creation time is normal for EKS — this isn't a hang
Troubleshooting Steps
- Check node group status and any scaling activity errors
- Verify the node IAM role has all three required policies attached
- Confirm security groups allow node-to-control-plane communication
- Check `kubectl get nodes` after update-kubeconfig to confirm nodes actually joined
Version compatibility: Terraform >= 1.0, AWS Provider >= 5.0, Kubernetes 1.28+
AzureAzure Virtual Network with Subnets
●AzureUploadedCreate an Azure VNet with public and private subnets, an NSG, and a route table.
By System · Networking
▾
Azure Virtual Network with Subnets
●AzureUploadedCreate an Azure VNet with public and private subnets, an NSG, and a route table.
By System · Networking
main.tf · variables.tf · outputs.tf
Downloadterraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
}
}
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "main" {
name = "${var.app_name}-rg"
location = var.location
}
resource "azurerm_virtual_network" "main" {
name = "${var.app_name}-vnet"
address_space = [var.vnet_cidr]
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
}
resource "azurerm_subnet" "public" {
name = "public"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = [var.public_subnet_cidr]
}
resource "azurerm_subnet" "private" {
name = "private"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = [var.private_subnet_cidr]
}
resource "azurerm_network_security_group" "main" {
name = "${var.app_name}-nsg"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
security_rule {
name = "AllowSSH"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
resource "azurerm_subnet_network_security_group_association" "public" {
subnet_id = azurerm_subnet.public.id
network_security_group_id = azurerm_network_security_group.main.id
}
variable "app_name" {
type = string
}
variable "location" {
type = string
default = "East US"
}
variable "vnet_cidr" {
type = string
default = "10.1.0.0/16"
}
variable "public_subnet_cidr" {
type = string
default = "10.1.1.0/24"
}
variable "private_subnet_cidr" {
type = string
default = "10.1.2.0/24"
}Troubleshooting & Solutions
Common Issues
- Subnet delegation conflicts
- NSG rule priority collision
- Resource group location mismatch
Solutions
- Keep subnet address prefixes within the VNet's address_space and non-overlapping
- Give every NSG rule a unique priority number — Azure rejects duplicates
- Make sure every resource in the group uses the same location as the resource group unless intentionally distributed
Troubleshooting Steps
- Run `az network vnet subnet list` to confirm actual subnet ranges
- Check NSG associations with `az network nsg show`
- Verify service principal / CLI auth has Network Contributor role
- Confirm the subscription has quota for the resource types being created
Version compatibility: Terraform >= 1.0, AzureRM Provider >= 3.0
AzureAzure Virtual Machine with Extensions
●AzureUploadedCreate an Azure VM with a custom script extension and a managed disk.
By System · Compute
▾
Azure Virtual Machine with Extensions
●AzureUploadedCreate an Azure VM with a custom script extension and a managed disk.
By System · Compute
main.tf · variables.tf · outputs.tf
Downloadresource "azurerm_network_interface" "main" {
name = "${var.app_name}-nic"
location = var.location
resource_group_name = var.resource_group_name
ip_configuration {
name = "internal"
subnet_id = var.subnet_id
private_ip_address_allocation = "Dynamic"
}
}
resource "azurerm_linux_virtual_machine" "main" {
name = var.app_name
resource_group_name = var.resource_group_name
location = var.location
size = var.vm_size
admin_username = var.admin_username
network_interface_ids = [azurerm_network_interface.main.id]
admin_ssh_key {
username = var.admin_username
public_key = var.ssh_public_key
}
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts-gen2"
version = "latest"
}
}
resource "azurerm_virtual_machine_extension" "docker" {
name = "install-docker"
virtual_machine_id = azurerm_linux_virtual_machine.main.id
publisher = "Microsoft.Azure.Extensions"
type = "CustomScript"
type_handler_version = "2.1"
settings = jsonencode({
commandToExecute = "apt-get update -y && apt-get install -y docker.io"
})
}
variable "app_name" {
type = string
}
variable "location" {
type = string
default = "East US"
}
variable "resource_group_name" {
type = string
}
variable "subnet_id" {
type = string
}
variable "vm_size" {
type = string
default = "Standard_B1s"
}
variable "admin_username" {
type = string
default = "azureuser"
}
variable "ssh_public_key" {
type = string
}Troubleshooting & Solutions
Common Issues
- SSH key format rejected
- Extension fails silently
- VM size unavailable in the region
Solutions
- admin_ssh_key expects the exact OpenSSH public key format (ssh-rsa/ssh-ed25519 ...)
- Check extension status with `az vm extension list` — failures log to /var/log/azure on the VM
- Pick a VM size actually available in your target region/zone via `az vm list-sizes`
Troubleshooting Steps
- Verify the SSH public key has no trailing newline issues
- Check the custom script extension's exit code and stdout/stderr logs
- Confirm the subnet has enough free IP addresses
- Check the subscription's quota for the requested VM size family
Version compatibility: Terraform >= 1.0, AzureRM Provider >= 3.0
GCPGCP VPC with Firewall Rules
●GCPUploadedCreate a GCP VPC network with custom firewall rules for web and SSH access.
By System · Networking
▾
GCP VPC with Firewall Rules
●GCPUploadedCreate a GCP VPC network with custom firewall rules for web and SSH access.
By System · Networking
main.tf · variables.tf · outputs.tf
Downloadterraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
}
}
provider "google" {
project = var.project_id
region = var.region
}
resource "google_compute_network" "main" {
name = "${var.app_name}-vpc"
auto_create_subnetworks = false
}
resource "google_compute_subnetwork" "main" {
name = "${var.app_name}-subnet"
ip_cidr_range = var.subnet_cidr
region = var.region
network = google_compute_network.main.id
}
resource "google_compute_firewall" "allow_ssh" {
name = "${var.app_name}-allow-ssh"
network = google_compute_network.main.id
allow {
protocol = "tcp"
ports = ["22"]
}
source_ranges = [var.ssh_source_range]
target_tags = ["ssh"]
}
resource "google_compute_firewall" "allow_web" {
name = "${var.app_name}-allow-web"
network = google_compute_network.main.id
allow {
protocol = "tcp"
ports = ["80", "443"]
}
source_ranges = ["0.0.0.0/0"]
target_tags = ["web"]
}
variable "project_id" {
type = string
}
variable "region" {
type = string
default = "us-central1"
}
variable "app_name" {
type = string
}
variable "subnet_cidr" {
type = string
default = "10.2.0.0/24"
}
variable "ssh_source_range" {
type = string
default = "0.0.0.0/0"
}Troubleshooting & Solutions
Common Issues
- Firewall rule has no effect
- Instance unreachable despite allow rule
- auto_create_subnetworks conflict
Solutions
- Firewall rules only apply to instances carrying the matching target_tags — confirm the instance has the tag
- Check the instance actually has an external IP if connecting from outside GCP
- Set auto_create_subnetworks = false and define subnets explicitly to avoid the default "legacy" network conflicting
Troubleshooting Steps
- Run `gcloud compute firewall-rules list` to confirm the rule is active
- Check the target instance's network tags match target_tags exactly
- Verify the subnet's region matches the instance's region
- Use `gcloud compute networks subnets list` to confirm CIDR ranges don't overlap
Version compatibility: Terraform >= 1.0, Google Provider >= 5.0
GCPGoogle Compute Engine Instance
●GCPUploadedCreate a GCE instance with a startup script and proper tags.
By System · Compute
▾
Google Compute Engine Instance
●GCPUploadedCreate a GCE instance with a startup script and proper tags.
By System · Compute
main.tf · variables.tf · outputs.tf
Downloadresource "google_compute_instance" "app" {
name = var.app_name
machine_type = var.machine_type
zone = var.zone
tags = ["web", "ssh"]
boot_disk {
initialize_params {
image = "ubuntu-os-cloud/ubuntu-2204-lts"
size = 20
}
}
network_interface {
network = var.network_id
subnetwork = var.subnetwork_id
access_config {}
}
metadata_startup_script = <<-EOF
#!/bin/bash
apt-get update -y
apt-get install -y docker.io
systemctl enable docker
systemctl start docker
EOF
service_account {
scopes = ["cloud-platform"]
}
}
variable "app_name" {
type = string
}
variable "machine_type" {
type = string
default = "e2-small"
}
variable "zone" {
type = string
default = "us-central1-a"
}
variable "network_id" {
type = string
}
variable "subnetwork_id" {
type = string
}Troubleshooting & Solutions
Common Issues
- Startup script doesn't run
- No external IP assigned
- Instance stuck provisioning
Solutions
- Check startup script logs via `gcloud compute instances get-serial-port-output`
- An empty `access_config {}` block is what actually grants an ephemeral external IP — remove it for internal-only instances
- Verify the boot disk image family/project name is spelled exactly right
Troubleshooting Steps
- Check serial port output for boot and startup-script errors
- Confirm the service account has the scopes/IAM roles the startup script needs
- Verify zone matches an actual zone in the target region
- Check project quotas for CPUs/instances in that region
Version compatibility: Terraform >= 1.0, Google Provider >= 5.0