AutoDeploy

Terraform Demos

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

AWS

AWS EC2 Instance with Security Group

AWSUploaded

Launch an EC2 instance with a properly scoped security group and user data.

By System · Compute

main.tf · variables.tf · outputs.tf

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

  1. Confirm the subnet has a route to an internet gateway if public access is needed
  2. Check the security group allows your actual source IP
  3. Verify the instance has a public IP if you're connecting externally
  4. Check user_data logs at /var/log/cloud-init-output.log on the instance

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

AWS

AWS Auto Scaling Group with Launch Template

AWSUploaded

Create an ASG with a launch template and target-tracking scaling policy.

By System · Compute

main.tf · variables.tf · outputs.tf

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

  1. Check ASG activity history for the termination reason
  2. Verify the launch template's security group and subnet are compatible
  3. Confirm target tracking has enough data points before expecting it to scale
  4. Check IAM permissions for the ASG service-linked role

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

Azure

Azure Virtual Machine with Extensions

AzureUploaded

Create an Azure VM with a custom script extension and a managed disk.

By System · Compute

main.tf · variables.tf · outputs.tf

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

  1. Verify the SSH public key has no trailing newline issues
  2. Check the custom script extension's exit code and stdout/stderr logs
  3. Confirm the subnet has enough free IP addresses
  4. Check the subscription's quota for the requested VM size family

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

GCP

Google Compute Engine Instance

GCPUploaded

Create a GCE instance with a startup script and proper tags.

By System · Compute

main.tf · variables.tf · outputs.tf

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

  1. Check serial port output for boot and startup-script errors
  2. Confirm the service account has the scopes/IAM roles the startup script needs
  3. Verify zone matches an actual zone in the target region
  4. Check project quotas for CPUs/instances in that region

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