Capacity Reservations are simple to buy and easy to get wrong. The usual failures: a DR launch that runs on plain On-Demand next to an unused reservation because one attribute didn't match; an open reservation quietly absorbed by a dev team's instance; a reservation that runs for months at 0% utilisation. You pay the On-Demand rate for every reserved slot, used or not. Run this checklist before you rely on one.
1. Decide why you are reserving
- The event is named and dated: DR failover target, a launch or peak, a migration cutover, or a cluster placement group job.
- The count is derived, not guessed: peak instances per AZ for the workload, not the ASG
MaxSize. - The pricing is understood: reservations are billed at On-Demand rates whether used or not. Regional Savings Plans and regional Reserved Instances can apply to the usage, while zonal RIs reserve capacity on their own. Check how your discount instruments apply before you buy both.
- An end date is set for one-off events (
--end-date-type limited). Useunlimitedonly for standing DR capacity that has an owner and a review date.
2. Match the attributes exactly
An instance only uses a reservation if all of these match:
| Attribute | Gotcha |
|---|---|
| Instance type | m7i.2xlarge doesn't use an m7i.4xlarge reservation; there is no size flexibility |
| Platform | Linux/UNIX vs Red Hat Enterprise Linux vs Windows follow the AMI's billing product, not the OS you think you run |
| Availability Zone | Zone names differ between accounts; use zone IDs (use1-az1) when sharing |
| Tenancy | default vs dedicated |
| Placement group | Only if the reservation was created in one |
- The AMI's platform is confirmed:
aws ec2 describe-images --image-ids ami-... --query 'Images[].PlatformDetails'. Marketplace and RHEL/SUSE images often surprise people. - The zone ID is used for cross-account work:
aws ec2 describe-availability-zones --query 'AvailabilityZones[].[ZoneName,ZoneId]' --output text
3. Choose open or targeted
-
openfor capacity anyone in the account may consume. Any matching instance with preferenceopen(the default) lands in it. Good for a steady estate, bad for protecting DR capacity. -
targetedfor capacity that must be there for one workload. Only launches that name the reservation, or a group containing it, use it.
aws ec2 create-capacity-reservation \
--instance-type m7i.2xlarge --instance-platform Linux/UNIX \
--availability-zone-id use1-az1 --instance-count 12 \
--instance-match-criteria targeted \
--end-date-type limited --end-date 2026-12-31T23:59:59Z \
--tag-specifications 'ResourceType=capacity-reservation,Tags=[{Key=workload,Value=payments-dr},{Key=owner,Value=platform}]'
Caution
Cancelling a reservation is immediate, and you may not get the capacity back if you recreate it later. Running instances keep running, but the guarantee for new launches is gone. Tag reservations with an owner and require a change ticket to cancel standing DR reservations.
4. Group reservations so launches can use any of them
A workload rarely fits one reservation. It spans AZs, or you add capacity in batches. Put the reservations in a Capacity Reservation group and target the group:
aws resource-groups create-group --name payments-dr-crs \
--configuration '{"Type":"AWS::EC2::CapacityReservationPool"}' \
'{"Type":"AWS::ResourceGroups::Generic","Parameters":[{"Name":"allowed-resource-types","Values":["AWS::EC2::CapacityReservation"]}]}'
aws resource-groups group-resources --group payments-dr-crs \
--resource-arns arn:aws:ec2:us-east-1:111122223333:capacity-reservation/cr-0aaa111 \
arn:aws:ec2:us-east-1:111122223333:capacity-reservation/cr-0bbb222
- Every reservation for the workload is in the group, in every AZ the ASG uses.
- Shared reservations are covered: if you share through AWS RAM, consumers can target the group. A reservation that's later unshared drops out of their group automatically, so recheck after RAM changes.
5. Wire it into launch templates and Auto Scaling
Launch template:
aws ec2 create-launch-template-version --launch-template-name payments \
--source-version '$Latest' \
--launch-template-data '{"CapacityReservationSpecification":{"CapacityReservationTarget":{"CapacityReservationResourceGroupArn":"arn:aws:resource-groups:us-east-1:111122223333:group/payments-dr-crs"}}}'
With a target set, EC2 uses matching capacity in the group and falls back to normal On-Demand if the group is full. To fail rather than fall back, set preference capacity-reservations-only where your launch path supports it.
Auto Scaling groups also have their own capacity reservation specification:
aws autoscaling update-auto-scaling-group --auto-scaling-group-name payments-dr \
--capacity-reservation-specification \
'CapacityReservationPreference=capacity-reservations-first,CapacityReservationTarget={CapacityReservationResourceGroupArns=[arn:aws:resource-groups:us-east-1:111122223333:group/payments-dr-crs]}'
- The preference is chosen deliberately:
capacity-reservations-only: launches fail if no reserved capacity is left. AZ balance follows wherever the reservations are.capacity-reservations-first: keeps AZ balance and overflows to On-Demand when an AZ's reservations are used up.- Preference names and behaviour have changed over time; check the current EC2 Auto Scaling docs for your Region.
- Instance type and platform in the ASG match the reservation. Mixed instances policies with types that have no reservation launch on plain On-Demand.
- Spot is excluded from the reserved portion. Spot instances never use On-Demand Capacity Reservations.
6. Prove it during a drill, not during the disaster
- Launch the DR ASG to full size in the drill, then check that instances report a reservation:
aws ec2 describe-instances --filters Name=tag:aws:autoscaling:groupName,Values=payments-dr \
--query 'Reservations[].Instances[].{id:InstanceId,az:Placement.AvailabilityZone,cr:CapacityReservationId,pref:CapacityReservationSpecification.CapacityReservationPreference}' \
--output table
An empty cr column means the instance runs outside the reservation. Recheck the attribute table in section 2.
- Available count goes back up after scale-in:
aws ec2 describe-capacity-reservations --filters Name=tag:workload,Values=payments-dr \
--query 'CapacityReservations[].{id:CapacityReservationId,az:AvailabilityZone,type:InstanceType,total:TotalInstanceCount,avail:AvailableInstanceCount,state:State,end:EndDate}' \
--output table
- Existing instances are accounted for if you reserve capacity you already use. Running matching instances with preference
openfill anopenreservation automatically. Atargetedreservation stays empty until instances are pointed at it withaws ec2 modify-instance-capacity-reservation-attributes. Check the docs for the instance state that requires, since it may mean a stop and start.
7. Monitor utilisation and expiry
Capacity Reservations publish metrics in the AWS/EC2CapacityReservations namespace, including InstanceUtilization, UsedInstanceCount, AvailableInstanceCount and TotalInstanceCount.
aws cloudwatch put-metric-alarm --alarm-name cr-0aaa111-idle \
--namespace AWS/EC2CapacityReservations --metric-name InstanceUtilization \
--dimensions Name=CapacityReservationId,Value=cr-0aaa111 \
--statistic Average --period 3600 --evaluation-periods 72 \
--threshold 10 --comparison-operator LessThanThreshold \
--treat-missing-data notBreaching \
--alarm-actions arn:aws:sns:us-east-1:111122223333:finops
- Idle standing reservations are alarmed. Low utilisation is expected for DR, so the alarm goes to the owner for a monthly review rather than paging anyone.
- Expiry is tracked: an EventBridge rule or a weekly report on
EndDate, so event capacity doesn't lapse a day early or run a month late. - Cost allocation tags (
workload,owner) are activated so the reserved-but-unused cost lands on the right team.
Prevent it next time
- Create reservations through IaC with tags and end dates, never by hand in the console.
- Make "launch to full DR size inside the reservation" a pass/fail step in every DR drill.
- Review all
unlimitedreservations each quarter against the current architecture. Instance type migrations, such as a move to Graviton, often leave old reservations behind. - For future events, check whether your Region supports future-dated reservations or Capacity Blocks, and compare their commitment terms before buying.