AutoDeploy

Terraform Demos

Copy and modify these pre-built Terraform configurations for your infrastructure.

AWS

AWS VPC with Public and Private Subnets

AWSUploaded

A complete VPC setup with public and private subnets, an internet gateway, and a NAT gateway.

By System · Networking

main.tf · variables.tf · outputs.tf

Download
terraform {
  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

  1. Check CIDR blocks don't overlap with peered VPCs
  2. Verify route table associations are correct per subnet
  3. Confirm NAT gateway is in a public subnet
  4. Check availability zone names match the target region

Version compatibility: Terraform >= 1.0, AWS Provider >= 5.0

AWS

AWS Application Load Balancer

AWSUploaded

Create an ALB with a target group and listener for HTTP traffic.

By System · Networking

main.tf · variables.tf · outputs.tf

Download
resource "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

  1. Check target group health in the console/CLI
  2. Confirm the ALB's subnets are public with a route to an IGW
  3. Verify listener rules point at the right target group
  4. Check the target instance's own security group, not just the ALB's

Version compatibility: Terraform >= 1.0, AWS Provider >= 5.0

AWS

AWS Route53 Hosted Zone and Records

AWSUploaded

Create a Route53 hosted zone with DNS records.

By System · Networking

main.tf · variables.tf · outputs.tf

Download
resource "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

  1. Run `dig NS <domain>` to confirm the registrar points at Route53's nameservers
  2. Run `dig <domain>` to confirm the A/CNAME record resolves
  3. Check the alias target's zone_id matches the actual ALB/CloudFront zone, not a guessed value
  4. 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

Azure

Azure Virtual Network with Subnets

AzureUploaded

Create an Azure VNet with public and private subnets, an NSG, and a route table.

By System · Networking

main.tf · variables.tf · outputs.tf

Download
terraform {
  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

  1. Run `az network vnet subnet list` to confirm actual subnet ranges
  2. Check NSG associations with `az network nsg show`
  3. Verify service principal / CLI auth has Network Contributor role
  4. Confirm the subscription has quota for the resource types being created

Version compatibility: Terraform >= 1.0, AzureRM Provider >= 3.0

GCP

GCP VPC with Firewall Rules

GCPUploaded

Create a GCP VPC network with custom firewall rules for web and SSH access.

By System · Networking

main.tf · variables.tf · outputs.tf

Download
terraform {
  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

  1. Run `gcloud compute firewall-rules list` to confirm the rule is active
  2. Check the target instance's network tags match target_tags exactly
  3. Verify the subnet's region matches the instance's region
  4. Use `gcloud compute networks subnets list` to confirm CIDR ranges don't overlap

Version compatibility: Terraform >= 1.0, Google Provider >= 5.0