EC2 is easy to launch and easy to leak. An untagged instance nobody remembers, a stopped fleet still billing for volumes, snapshots left behind after a deregister - these are the everyday ways EC2 automation quietly costs money.
Walk this list once now, then use it as a review checklist before shipping any code that launches, manages, or tears down instances.
Tag at launch, not after. Use TagSpecifications on RunInstances so an instance is never briefly untagged; a crash between launch and a later CreateTags leaves an orphan.
Tag every resource, not just instances. Volumes, snapshots, AMIs, and network interfaces all take tags; untagged storage is the hardest cost to trace.
Adopt a consistent tag schema. Standard keys like Name, Environment, Owner, and Cost-Center make filtering and cost allocation possible.
Filter by tag server-side. Scope automation with the tag:Key filter on DescribeInstances instead of pulling everything and filtering in code.
Tag at launch so every resource is attributable from the moment it exists.
Resolve AMI ids at runtime. Read the latest official id from an SSM public parameter; a hardcoded id is region-specific and goes stale on every AWS refresh.
Sort DescribeImages by CreationDate. The API returns matches unordered, so sort descending to pick the newest build.
Provision through launch templates. Capture configuration once in a versioned template instead of repeating dozens of inline RunInstances parameters.
Point ASGs at $Default, not $Latest. Promote a launch template version deliberately so new versions roll out under your control.
Bake images with a consistent snapshot. Leave NoReboot false (or flush data yourself) so CreateImage captures a clean file system.
Resolve the AMI, then launch from a template - no hardcoded ids.
Wait on transitions with waiters. Use instance_running / instance_stopped (boto3) or waitUntilInstanceRunning (SDK v3) rather than hand-rolled polling loops.
Stop idle instances, but know storage still bills. Stopping halts compute charges; the EBS volumes and any unattached Elastic IP keep costing money.
Delete snapshots after deregistering an AMI.DeregisterImage leaves backing snapshots that bill by the gigabyte-month; delete them too.
Sweep for orphans on a schedule. List instances, volumes, and snapshots by tag and age, and clean up anything past its purpose.
Never hardcode a public IP. A stop/start assigns a new one; use an Elastic IP or DNS if you need a stable address.
Wait for the transition instead of guessing it finished.
Right-size the instance type. Start small and scale up on evidence; oversized instances are the most common EC2 waste.
Use Spot for interruptible work. Batch, CI, and stateless tiers can run on Spot at a large discount with proper interruption handling.
Let Auto Scaling match capacity to demand. A target-tracking ASG adds and removes instances so you do not pay for idle headroom.
Prefer newer-generation instances. Newer families usually give more performance per dollar than the generation they replace.
Set InstanceInitiatedShutdownBehavior intentionally. Decide whether an OS-level shutdown stops or terminates, so a reboot script does not destroy an instance.
Enforce IMDSv2. Set MetadataOptions.HttpTokens to required so a token is mandatory, closing the SSRF credential-theft path.
Keep secrets out of user data. User data is readable on the instance and via DescribeInstanceAttribute; fetch secrets from Secrets Manager or SSM at boot.
Use IAM roles, not embedded keys. Attach an instance profile so the SDK gets temporary credentials from IMDS instead of long-lived keys.
Rely on built-in retries. Both SDKs retry throttling and transient errors with backoff; tune max_attempts rather than reimplementing retries.
Log the request id on failure. Capture ResponseMetadata.RequestId (boto3) or $metadata.requestId (SDK v3) for support and correlation.
Because a failure between launch and a follow-up CreateTags leaves an untagged, unattributable instance. TagSpecifications on RunInstances makes the resource tagged from the moment it exists.
What tags should every instance have?
At minimum Name, Environment, and Owner, plus a cost-allocation tag like Cost-Center. A consistent schema is what makes filtering and billing reports usable.
Why not hardcode an AMI id?
Ids are region-specific and change on every AWS refresh. A hardcoded id breaks in another region and silently goes stale. Resolve it from an SSM parameter at runtime.
Does stopping an instance stop all charges?
No. It stops compute charges, but the EBS volumes keep billing for storage, and an allocated but unattached Elastic IP keeps billing too.
What is the most common EC2 cost leak?
Orphaned resources: untagged stopped instances, unattached volumes, and snapshots left after an AMI deregister. A scheduled sweep by tag and age catches them.
Should I write my own retry logic for EC2 calls?
Usually no. Both SDKs retry throttling and transient errors with backoff. Tune retry mode and max attempts instead of reimplementing it.
How do I make sure an instance is actually running before I use it?
Use a waiter - instance_running in boto3 or waitUntilInstanceRunning in SDK v3 - which polls until the state is reached instead of guessing with a sleep.
Why enforce IMDSv2?
Because IMDSv1's unauthenticated metadata access let SSRF bugs steal instance role credentials. Requiring a token (HttpTokens: required) closes that path.
Where should I put configuration that is too big for user data?
Have lightweight user data pull versioned config or run SSM commands, or use a configuration-management tool. Keep boot-time user data small and idempotent.
How do I keep a stable address across stop/start?
Attach an Elastic IP, or front the instance with DNS. A plain stop/start assigns a new public IPv4, so never hardcode the address.
What is the cleanup order for a custom AMI?
Read the snapshot ids from the AMI's block device mappings, call DeregisterImage, then DeleteSnapshot for each snapshot. Deregistering alone leaves the snapshots billing.
How do I avoid paying for idle capacity?
Right-size the instance type, use Spot for interruptible work, and let a target-tracking Auto Scaling group add and remove capacity with demand.