Creating reliable Auto Scaling Groups in AWS with Terraform requires a precise understanding of launch configurations and how Terraform handles their immutable nature. Launch configurations define the instance template an Auto Scaling Group uses to launch EC2 instances, and because they cannot be modified after creation, the entire update workflow revolves around replacement, naming strategy, and safe dependency ordering.
Introduction
Terraform makes it possible to declaratively define an Amazon EC2 Auto Scaling launch configuration with aws_launch_configuration. The resource allows you to specify launch configuration details such as the Amazon Machine Image, instance type, key pair, security groups, and block device mapping when creating a launch configuration using Terraform.
The core challenge is immutability. A launch configuration is an instance configuration template that an Auto Scaling group uses to launch EC2 instances. Launch configurations are immutable — you can’t modify a launch configuration after you’ve created it. To change the launch configuration for an Auto Scaling group, you must create a new launch configuration and then update your Auto Scaling group with it.
This article covers the cardinality rules, immutable replacement behavior, required and optional arguments, lifecycle management with create_before_destroy, and practical patterns for AMI updates and Spot pricing.
Cardinality and Relationship Rules
Understanding how launch configurations relate to Auto Scaling groups prevents design errors.
There is 1-to-many cardinality between launch config and autoscaling groups. You can specify 1 launch configuration for many Auto Scaling groups. However, 1 ASG can have only 1 launch configuration at a time.
In Terraform, the aws_launch_configuration resource allows you to create and manage an Amazon EC2 Auto Scaling launch configuration. The aws_autoscaling_group resource then references the launch configuration by name.
Example from reference material:
```
resource "awslaunchconfiguration" "example" {
name = "example"
imageid = "ami-002"
instancetype = "t2.micro"
}
resource "awsautoscalinggroup" "example" {
launchconfiguration = awslaunchconfiguration.example.id
minsize = 1
max_size = 2
}
```
When image_id in the Launch Configuration is changed, Terraform will replace the Launch Configuration and update the Auto Scaling Group accordingly. The Auto Scaling Group is being updated in place, meaning it’s being modified without being destroyed and recreated. The change in the Auto Scaling Group is in the launch_configuration attribute.
Immutability and Replacement Behavior
Terraform treats a Launch Configuration as replace-on-change. In order to update a Launch Configuration, Terraform will destroy the existing resource and create a replacement.
A typical plan for an AMI change shows the replacement pattern:
```
module.ec2.awsautoscalinggroup.example_asg will be updated in-place
~ resource "awsautoscalinggroup" "exampleasg" {
id = "acme-us-east-2"
~ launchconfiguration = "acme-us-east-2-20231009074215927400000001" -> (known after apply)
name = "acme-us-east-2"
# (23 unchanged attributes hidden)
}
module.ec2.awslaunchconfiguration.example_lc must be replaced
+/- resource "awslaunchconfiguration" "examplelc" {
~ arn = "arn:aws:autoscaling:us-east-2:204004877656:launchConfiguration:d0737a01-dab9-4592-a147-af755884b25f:launchConfigurationName/acme-us-east-2-20231009074215927400000001" -> (known after apply)
~ id = "acme-us-east-2-20231009074215927400000001" -> (known after apply)
~ imageid = "ami-001" -> "ami-002" # forces replacement
~ name = "acme-us-east-2-20231009074215927400000001" -> (known after apply)
# (9 unchanged attributes hidden)
}
```
Plan: 1 to add, 1 to change, 1 to destroy.
This is why regular maintenance such as AMI updates for CVE removals requires a new launch configuration rather than an in-place edit. We are often updating the AMIs we’re using due to security issues and CVE discovery.
Safe Update Pattern with Lifecycle
Because replacement destroys first by default, a common failure mode is that the Auto Scaling Group temporarily references a non-existent launch configuration. The recommended pattern is to specify create_before_destroy in a lifecycle block.
Either omit the Launch Configuration name attribute, or specify a partial name with name_prefix. Example:
```
data "awsami" "ubuntu" {
mostrecent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical
}
resource "awslaunchconfiguration" "asconf" {
nameprefix = "terraform-lc-example-"
imageid = data.awsami.ubuntu.id
instancetype = "t2.micro"
lifecycle {
createbefore_destroy = true
}
}
resource "awsautoscalinggroup" "bar" {
name = "terraform-asg-example"
launchconfiguration = awslaunchconfiguration.asconf.name
minsize = 1
maxsize = 2
lifecycle {
createbeforedestroy = true
}
}
```
With this setup Terraform generates a unique name for your Launch Configuration and can then update the AutoScaling Group without conflict before destroying the previous Launch Configuration.
The name_prefix approach is preferred over a static name because Terraform can generate a new unique name on each replacement, avoiding name collisions during the create-before-destroy window.
Argument Reference and Configuration Options
The aws_launch_configuration resource supports a set of required and optional arguments.
Required arguments:
image_id- The EC2 image ID to launch.instance_type- The size of instance to launch.
Optional arguments documented in reference material:
name- The name of the launch configuration. If you leave this blank, Terraform will auto-generate a unique name.name_prefix- Creates a unique name beginning with the specified prefix. Conflicts withname.iam_instance_profile- The IAM instance profile to associate with launched instances.key_name- The key name that should be used for the instance.security_groups- A list of associated security group IDs.associate_public_ip_address- Associate a public ip address with an instance in a VPC.vpc_classic_link_id- The ID of a ClassicLink-enabled VPC.region- Region where this resource will be managed. Defaults to the Region set in the provider configuration.ebs_block_device- Additional EBS block devices to attach to the instance.ebs_optimized- If true, the launched EC2 instance will be EBS-optimized.enable_monitoring- Enables/disables detailed monitoring.
Additional optional arguments:
spot_price- Set the spot instance pricing to be used for the Auto Scaling Group to reserve instances. Simply specifying thespot_priceparameter will set the price on the Launch Configuration which will attempt to reserve your instances at this price.
A complete argument table:
| Argument | Type | Required | Description |
|---|---|---|---|
| image_id | string | Yes | The EC2 image ID to launch |
| instance_type | string | Yes | The size of instance to launch |
| name | string | No | The name of the launch configuration |
| name_prefix | string | No | Prefix for auto-generated unique name, conflicts with name |
| iaminstanceprofile | string | No | IAM instance profile to associate |
| key_name | string | No | Key pair name for the instance |
| security_groups | list(string) | No | Security group IDs |
| associatepublicip_address | bool | No | Associate public IP in VPC |
| spot_price | string | No | Spot price for Spot instances |
| ebs_optimized | bool | No | EBS optimized instance |
| enable_monitoring | bool | No | Enable detailed monitoring |
Example with Spot pricing:
resource "aws_launch_configuration" "as_conf" {
image_id = data.aws_ami.ubuntu.id
instance_type = "m4.large"
spot_price = "0.001"
lifecycle {
create_before_destroy = true
}
}
Practical AMI Update Workflow
The aim of this page is to explain how to create a launch configuration for an EC2 instance using Terraform based on the particular example of updating an AMI attribute of launch configuration for an ASG due as part of regular maintenance.
A typical workflow uses a data source to find the latest AMI and feeds it into the launch configuration:
```
data "awsami" "ubuntu" {
mostrecent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"]
}
resource "awslaunchconfiguration" "asconf" {
imageid = data.awsami.ubuntu.id
instancetype = "m4.large"
spotprice = "0.001"
lifecycle {
createbefore_destroy = true
}
}
resource "awsautoscalinggroup" "bar" {
name = "terraform-asg-example"
launchconfiguration = awslaunchconfiguration.asconf.name
}
```
When the data source returns a new AMI ID, Terraform plans a replacement of the launch configuration and an in-place update of the Auto Scaling Group to point to the new configuration name.
Launch Templates vs Launch Configurations
While the focus here is launch configurations, AWS now recommends launch templates for new designs. Launch templates offer versioning, more granular block device mapping, and user data handling.
A basic launch template example from reference material shows:
```
data "awsami" "al2023" {
mostrecent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
resource "awslaunchtemplate" "app" {
nameprefix = "app-"
description = "Launch template for application servers"
imageid = data.awsami.al2023.id
instancetype = "t3.medium"
lifecycle {
createbeforedestroy = true
}
vpcsecuritygroupids = [
awssecuritygroup.app.id,
awssecuritygroup.monitoring.id,
]
iaminstanceprofile {
name = awsiaminstanceprofile.app.name
}
userdata = base64encode(templatefile("${path.module}/scripts/bootstrap.sh", {
environment = var.environment
appversion = var.appversion
}))
tagspecifications {
resource_type = "instance"
tags = {
Name = "app-server"
Environment = var.environment
}
}
}
```
Launch templates are mutable via versioning, whereas launch configurations are immutable and must be replaced.
Best Practices Summary
- Use
name_prefixinstead of a staticnameto allow Terraform to generate unique names on replacement. - Always set
lifecycle { create_before_destroy = true }on both the launch configuration and the Auto Scaling Group to avoid downtime. - Reference AMIs via a
data "aws_ami"block withmost_recent = trueand filters to enable automated patching. - For Spot usage, set
spot_priceon the launch configuration. - Prefer launch templates for new projects; keep launch configurations only where legacy Auto Scaling Groups require them.
Conclusion
Terraform launch configurations provide a declarative way to define instance templates for Auto Scaling Groups, but their immutable nature forces a replace-on-change lifecycle. The effective pattern is to combine name_prefix with create_before_destroy to generate unique names and avoid conflicts during updates, and to drive AMI changes through data sources so security patches propagate via controlled replacements.
The 1-to-many cardinality means one launch configuration can be shared across multiple Auto Scaling Groups, but each group can only reference one configuration at a time, making coordinated rollouts predictable. Optional features such as Spot pricing, EBS optimization, and monitoring flags add flexibility, while arguments like image_id and instance_type remain the core of the definition.
Understanding the plan output — where the Auto Scaling Group is updated in-place while the launch configuration is replaced — is key to safe production operations. For long-term designs, migrating to launch templates offers versioning and richer configuration, but the principles of create-before-destroy and unique naming remain essential.