Skip to main content

How to Auto-Scale AWS Spot Instances with Terraform

· By Pankajbhai Chavda · 4 min read

We spun up a single AWS Spot instance using Terraform to slash our cloud bill. We covered how to deploy AWS Spot Instances using Terraform, but if you're running anything important, a single Spot instance is risky. AWS can reclaim that server at any moment with just a two-minute warning.

If your server dies, your app goes down.

To fix this, we don't stop using Spot instances — we find a better way. Today, we're going to build a self-healing Auto Scaling Group (ASG) using Terraform. We will configure it to maintain 100% Spot capacity, automatically pick the cheapest availability zones, and install a web server on boot so you can prove it actually works.

Now open your terminal and get ready for this session.

Set Up Your Project

Create a new directory for this project and create your main.tf file. We are using us-east-1 (you can choose your own region). This region has the deepest Spot capacity pools, which means fewer interruptions.

mkdir aws-spot-asg && cd aws-spot-asg
nano main.tf

The Foundation and Firewall

Before we spin up servers, we need to tell Terraform which AWS region to use, grab the latest Amazon Linux image, and open port 80 for web traffic and port 22 for SSH.

Paste this whole code into main.tf. Replace your region in this code.

provider "aws" {
  region = "us-east-1"
}

# Grab the latest Amazon Linux 2023 image
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["al2023-ami-2023.*-x86_64"]
  }
}

# Grab your default VPC automatically
data "aws_vpc" "default" {
  default = true
}

data "aws_subnets" "default" {
  filter {
    name   = "vpc-id"
    values = [data.aws_vpc.default.id]
  }
}

# Allow HTTP and SSH traffic
resource "aws_security_group" "web_sg" {
  name_prefix = "web-sg-"
  vpc_id      = data.aws_vpc.default.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 22
    to_port     = 22
    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"]
  }
}

Then save the file.

The Launch Template

An Auto Scaling Group needs a blueprint to know exactly what to build when a new server is required.

We are going to use a user_data script to automatically update the OS, install Apache, and write a simple HTML file on boot.

Add this right below your security group in main.tf:

# The blueprint for our instances
resource "aws_launch_template" "spot_app" {
  name_prefix            = "spot-app-template-"
  image_id               = data.aws_ami.amazon_linux.id
  instance_type          = "t3.micro"
  vpc_security_group_ids = [aws_security_group.web_sg.id]

  # Install Apache and write a custom homepage
  user_data = base64encode(<<-EOF
              #!/bin/bash
              yum update -y
              yum install -y httpd
              systemctl start httpd
              systemctl enable httpd
              echo "Hello from my AWS Spot Instance!" > /var/www/html/index.html
              EOF
  )
}

Then save the file.

The Auto Scaling Group

Here is where it all comes together. We are going to use mixed_instances_policy. This tells AWS: "I want exactly two servers running. Use 100% Spot instances to save money, but if t3.micro is out of stock, fall back to t3.small."

We are also using the capacity-based allocation strategy. AWS will check all available Spot pools and pick the ones that are least likely to be interrupted.

Add this final block at the bottom of your file main.tf:

# The Auto Scaling Group
resource "aws_autoscaling_group" "spot_fleet" {
  name                = "spot-asg-example"
  vpc_zone_identifier = data.aws_subnets.default.ids

  # How many servers we want running
  desired_capacity    = 2
  max_size            = 3
  min_size            = 1

  mixed_instances_policy {
    instances_distribution {
      on_demand_base_capacity                  = 0
      on_demand_percentage_above_base_capacity = 0 # 100% Spot instances
      spot_allocation_strategy                 = "capacity-optimized"
    }

    launch_template {
      launch_template_specification {
        launch_template_id = aws_launch_template.spot_app.id
        version            = "$Latest"
      }

      # Fallback options if capacity is tight
      override { instance_type = "t3.micro" }
      override { instance_type = "t3.small" }
    }
  }
}

# Output the running instances so we can test them
data "aws_instances" "spot_servers" {
  filter {
    name   = "tag:aws:autoscaling:groupName"
    values = [aws_autoscaling_group.spot_fleet.name]
  }
  filter {
    name   = "instance-state-name"
    values = ["running"]
  }
}

output "instance_public_ips" {
  value = data.aws_instances.spot_servers.public_ips
}

Then save this file.

Fixing the Credentials Error

If you want to run Terraform and you run the terraform apply command, you may sometimes see errors like the ones below.

Error: No valid credential sources found Error: failed to refresh cached credentials, no EC2 IMDS role found

Don't panic. Terraform just forgot who you are. Before you apply, you need to export your AWS credentials so Terraform can talk to your AWS account. Run these two commands and replace your_access_key and your_secret_key.

export AWS_ACCESS_KEY_ID="your_access_key_here"
export AWS_SECRET_ACCESS_KEY="your_secret_key_here"

Deploy

Now that Terraform has your keys, initialize and apply your code.

terraform init
terraform apply

Type yes when prompted. Wait a few seconds for AWS to spin up the instances and for the user_data script to finish installing Apache.

Now check whether your web servers are alive. The best way to test this is to check directly from your terminal.

curl http://YOUR_INSTANCE_IP

If it spits back "Hello from my AWS Spot Instance!", your Auto Scaling Group is working perfectly. If AWS reclaims one of those servers, the ASG will instantly spin up a replacement, run your script, and get you back to two instances automatically.

Clean Up (for tester)

If you are a tester and this was created for testing purposes, don't leave it running. Destroy the resources so AWS doesn't keep charging you. Run the command below to destroy the entire setup.

terraform destroy

Type yes when prompted to destroy the Terraform resources.

Conclusion

Spot instances are totally capable for production workloads if you handle them correctly. By wrapping them in an Auto Scaling Group with a mixed instances policy, you get the massive 90% cost savings of the Spot market without the panic of manual downtime. If AWS pulls the plug on a server, Terraform and AWS handle the replacement automatically. That is a big thing for your business, because your application stays online and your cloud bill stays low.

About the author

Pankajbhai Chavda Pankajbhai Chavda
Updated on Aug 6, 2026
-