Auto Scaling Group with Launch Template
Scenario
Traffic to your web application spikes every weekday morning. A single EC2 instance can't handle peak load, and you can't afford to run large instances 24/7. The solution is an Auto Scaling Group that launches additional instances during peak load and terminates them during off-peak hours.
Build an ASG using a Launch Template that installs nginx via a user data script on boot, with a target tracking scaling policy that maintains average CPU at 50%.
- Create an aws_launch_template with the latest Amazon Linux 2 AMI, t3.micro, user data, and a security group.
- Create an aws_autoscaling_group with min_size=1, max_size=4, desired_capacity=2 across two subnets.
- Add a target tracking scaling policy that maintains average CPU at 50%.
- Enable instance refresh so new launch template versions roll out gracefully.
- Output the ASG name and ARN.
Additional Context
Launch Templates (not the older Launch Configurations) are the current best practice. They support versioning, which enables the instance_refresh feature — when you update the template (new AMI, new user data), the ASG can gradually replace existing instances with the new configuration without downtime.
The user_data argument expects a base64-encoded string. Use the built-in base64encode() function or the HCL filebase64() function to encode a shell script.
versions.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}variables.tf
variable "aws_region" { type = string; default = "us-east-1" }
variable "vpc_id" { type = string; description = "VPC for the ASG instances." }
variable "subnet_ids" { type = list(string); description = "List of subnet IDs (min 2 AZs)." }
variable "environment" { type = string; default = "prod" }main.tf
# main.tf
provider "aws" {
region = var.aws_region
}
data "aws_ami" "amazon_linux_2" {
most_recent = true
owners = ["amazon"]
filter { name = "name"; values = ["amzn2-ami-hvm-*-x86_64-gp2"] }
filter { name = "virtualization-type"; values = ["hvm"] }
}
resource "aws_security_group" "asg" {
name = "asg-web-sg-${var.environment}"
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"] }
tags = { Name = "asg-web-sg", Environment = var.environment }
}
# Launch Template — the reusable instance blueprint for the ASG.
resource "aws_launch_template" "web" {
name_prefix = "web-server-"
image_id = data.aws_ami.amazon_linux_2.id
instance_type = "t3.micro"
# User data runs once on first boot — installs and starts nginx.
# base64encode() encodes the shell script as required by the API.
user_data = base64encode(<<-EOF
#!/bin/bash
yum update -y
yum install -y nginx
systemctl enable nginx
systemctl start nginx
echo "Hello from $(hostname)
" > /usr/share/nginx/html/index.html
EOF
)
network_interfaces {
associate_public_ip_address = true
security_groups = [aws_security_group.asg.id]
}
tag_specifications {
resource_type = "instance"
tags = { Name = "asg-web-instance", Environment = var.environment, ManagedBy = "terraform" }
}
lifecycle {
create_before_destroy = true # create new version before deleting old template
}
}
# Auto Scaling Group.
resource "aws_autoscaling_group" "web" {
name = "web-asg-${var.environment}"
vpc_zone_identifier = var.subnet_ids # spread instances across these subnets/AZs
min_size = 1
max_size = 4
desired_capacity = 2
launch_template {
id = aws_launch_template.web.id
version = "$Latest" # always use the latest launch template version
}
# Instance refresh: when launch template changes, gradually replace instances.
# min_healthy_percentage=90 means at least 90% of desired capacity stays healthy.
instance_refresh {
strategy = "Rolling"
preferences {
min_healthy_percentage = 90
}
}
tag {
key = "ManagedBy"
value = "terraform"
propagate_at_launch = true
}
}
# Target Tracking Scaling Policy — maintains average CPU at 50%.
# ASG adds instances when above 50%, removes instances when below.
resource "aws_autoscaling_policy" "cpu_target" {
name = "web-cpu-target-tracking"
autoscaling_group_name = aws_autoscaling_group.web.name
policy_type = "TargetTrackingScaling"
target_tracking_configuration {
predefined_metric_specification {
predefined_metric_type = "ASGAverageCPUUtilization"
}
target_value = 50.0 # maintain average CPU at 50%
}
}outputs.tf
output "asg_name" { value = aws_autoscaling_group.web.name }
output "asg_arn" { value = aws_autoscaling_group.web.arn }Workflow Commands
terraform init
terraform apply -var="vpc_id=vpc-xxxx" -var='subnet_ids=["subnet-aaa","subnet-bbb"]'
# List running instances in the ASG
aws autoscaling describe-auto-scaling-instances \
--query "AutoScalingInstances[?AutoScalingGroupName=='web-asg-prod']"
terraform destroy -var="vpc_id=vpc-xxxx" -var='subnet_ids=["subnet-aaa","subnet-bbb"]'Apply complete! Resources: 4 added, 0 changed, 0 destroyed. Outputs: asg_arn = "arn:aws:autoscaling:us-east-1:123456789012:autoScalingGroup:..." asg_name = "web-asg-prod"

