An EC2 launch template is the versioned definition of how your instances launch — AMI, instance type, security groups, user data, IAM profile, tags. Every edit creates a new immutable version, and the consumers (Auto Scaling groups, spot fleets, run-instances calls) reference either a pinned number, $Default, or $Latest. Most fleet surprises trace back to one decision: something in production referencing $Latest, so a half-finished edit went live the moment someone saved it.
What’s happening
Launch templates replaced launch configurations as the way to define instance config, and the difference that matters operationally is versioning: a launch configuration was immutable and disposable, while a launch template is a named container holding a growing list of numbered versions. (If you still have launch configurations in the account, start with launch templates vs launch configurations — they no longer receive new features and new accounts cannot create them.)
Three ways a consumer can reference a template version:
- A pinned number (
Version: "14") — the consumer launches exactly that config until a human changes the reference. Predictable, auditable, boring. This is what production wants. $Default— a pointer you move deliberately withmodify-launch-template. Editing the template does not move it; promoting a version does. This is the reasonable middle ground.$Latest— always the newest version, including the one a teammate just saved to test a theory. Convenient in dev, a loaded weapon in prod: creating a version is deploying it.
The subtle trap: creating a new version does not change $Default. Teams edit the template, see nothing change in the ASG, conclude templates “don’t work,” then someone flips the ASG to $Latest to make edits take effect — and now every future edit is a live deployment.
What to check first
- What version reference does each ASG use?
$Latestin production is finding number one. - Does
$Defaultequal the highest version number? A gap usually means someone has been editing without promoting — find out which version is actually intended. - Who can create versions?
ec2:CreateLaunchTemplateVersionin the wrong hands plus$Latestequals unreviewed production changes. - Are there description strings on versions? Empty descriptions mean nobody can reconstruct why version 9 exists.
- Is the AMI reference inside the template pinned or resolved from an SSM parameter? Both are legitimate; not knowing which is not.
How to verify
List templates with their default and latest version numbers side by side:
aws ec2 describe-launch-templates \
--query 'LaunchTemplates[].[LaunchTemplateName,DefaultVersionNumber,LatestVersionNumber]' \
--output table
Inspect what each version actually contains, and who created it when:
aws ec2 describe-launch-template-versions \
--launch-template-name web-prod \
--query 'LaunchTemplateVersions[].[VersionNumber,CreateTime,CreatedBy,VersionDescription]' \
--output table
See which version an Auto Scaling group resolves:
aws autoscaling describe-auto-scaling-groups \
--auto-scaling-group-names web-prod-asg \
--query 'AutoScalingGroups[].[AutoScalingGroupName,LaunchTemplate.Version,MixedInstancesPolicy.LaunchTemplate.LaunchTemplateSpecification.Version]'
In the console: EC2 > Launch Templates > (template) > Versions shows the full history, and EC2 Auto Scaling > Auto Scaling Groups > Details shows the version reference. CloudTrail records CreateLaunchTemplateVersion and ModifyLaunchTemplate events, which is your audit trail for “what changed and when.”
The approach
-
Pin production ASGs to
$Default, never$Latest. This turns version creation into a safe, reviewable act and version promotion into the deployment. Dev and staging groups can run$Latestif you accept the tradeoff there. -
Create versions from a known base, not from scratch. Use
--source-versionso each version is a diff against a real ancestor:aws ec2 create-launch-template-version \ --launch-template-name web-prod \ --source-version 14 \ --version-description "AMI 2026-07 monthly rebuild" \ --launch-template-data '{"ImageId":"ami-0abc1234def567890"}' -
Promote deliberately. Moving the pointer is the moment of deployment, so it belongs in your change process:
aws ec2 modify-launch-template \ --launch-template-name web-prod --default-version 15 -
Roll the fleet after promoting. Existing instances keep their old config forever; only new launches pick up the version. Trigger an ASG instance refresh so the running fleet converges instead of splitting across three configs.
-
Write a description on every version — one line stating what changed and the ticket or commit behind it. Future-you doing incident forensics at 2 a.m. will read these.
-
Manage the template through IaC (Terraform, CloudFormation) so version creation goes through review, and treat console edits as break-glass only. The EC2 documentation covers the full field set templates support — keep the template authoritative and user data thin.
-
Prune old versions occasionally, keeping enough history for rollback.
delete-launch-template-versionsremoves clutter; you cannot delete the default version.
When this is the wrong move
If you launch one instance a quarter by hand, template versioning ceremony adds little — a pinned AMI ID in a runbook does the same job. Also, do not use launch template versions as your application deployment mechanism: shipping app code by baking version-per-release templates and cycling the ASG works, but it is slow feedback compared to a real deploy pipeline, and it tangles infra changes with app changes in one version history.
Common mistakes
$Lateston a production ASG — every saved edit is instantly the live launch config for the next scale-out event.- Editing the template and expecting running instances to change. Versions apply only at launch; the fleet must be refreshed or replaced.
- Overriding template fields at the ASG or
run-instanceslevel and forgetting it — the template says one thing, launches do another, and the template stops being the source of truth. - Deleting a template that mixed-instances policies or spot fleets still reference, which surfaces as launch failures during the next scale-out, at the worst possible time.
Keeping it under control
- A weekly scripted check that flags any production ASG referencing
$Latestor a$Defaultlagging more than one version behind latest without an open change. - Restrict
ModifyLaunchTemplate(promotion) more tightly thanCreateLaunchTemplateVersion(proposal) in IAM. - Alarm on failed launches from the ASG — a bad promoted version shows up there first.
- Keep template definitions in git; a version whose diff you cannot produce is a version you cannot trust.
Versioning discipline on launch templates is cheap: pin to $Default, describe every version, promote on purpose, refresh the fleet. If nobody on your team truly owns the AWS environment end to end, that is the root of most of these problems — Stack Harbor runs AWS accounts daily under AWS Operations, and you can book an AWS review.