AWS: Zero to Hero — Week 4: Actually Building Something — EC2, Auto Scaling, and Load Balancing
Week 4 of learning AWS from zero, with Claude as my research partner. EC2 fundamentals, pricing models, Session Manager and IMDSv2, Auto Scaling and Load Balancing explained, then an actual hands-on lab: a real auto-scaling, load-balanced web app built and torn down with the AWS CLI, plus what the July 2025 Free Tier overhaul means for the bill.
Series: AWS: Zero to Hero (9 articles)
- …
- 4AWS: Zero to Hero — Week 4: Actually Building Something — EC2, Auto Scaling, and Load Balancing This article
Account locked down, root user protected, IAM understood — last week was entirely about not touching anything yet. This week, I finally did. First real AWS service: EC2, Amazon's virtual server product, and the two things that turn "one server" into something that can actually survive real traffic — Auto Scaling and Load Balancing. And this time, I'm not stopping at the theory — the second half of this post is a real, working lab you can follow along with on your own account.
The Deep Dive
What an EC2 instance actually is
An EC2 instance is a virtual machine — the thing all of Week 2's Nitro System explanation was building toward. When you launch one, you're picking two things: an AMI (Amazon Machine Image), which is the template — OS plus whatever software is pre-installed — and an instance type, which decides the actual hardware shape: how much CPU, memory, network, and (sometimes) GPU you get.
Instance types are grouped into families based on what they're optimized for:
- General purpose (t-series, m-series) — balanced CPU/memory, the default choice for most everyday workloads.
- Compute optimized (c-series) — more CPU relative to memory, for compute-heavy workloads like batch processing.
- Memory optimized (r-series) — more RAM relative to CPU, for things like in-memory databases.
- Storage optimized (i-series, d-series) — built around fast local disk throughput.
- Accelerated computing (g-series, p-series, and the trn/inf families) — GPUs or AWS's own AI chips. Worth remembering this family name for later — it's exactly where Parts 11–13 on AI are headed.
One detail from last week that directly matters here: AWS's own Graviton processors (the Arm-based chips from the same Annapurna Labs team behind Nitro) offer up to roughly 40% better price-performance than equivalent x86 instances for compatible workloads — a genuinely free upgrade if your software can run on Arm.
How you actually pay for it (and why it matters more than it sounds)
This is where Week 1's "Capex vs. Opex" economics becomes something concrete instead of theoretical. EC2 offers three fundamentally different pricing models:
- On-Demand — pay per second/hour, no commitment. Most flexible, most expensive per hour.
- Savings Plans / Reserved Instances — commit to a usage level for 1 or 3 years, in exchange for up to roughly 72% off On-Demand pricing. Makes sense once a workload's baseline is predictable.
- Spot Instances — bid on AWS's genuinely spare capacity, at discounts up to roughly 90% — with the tradeoff that AWS can reclaim the instance with only a short warning if it needs that capacity back. Great for anything fault-tolerant (batch jobs, stateless workers), terrible for anything that can't handle being interrupted.
The existence of three completely different pricing models for the same underlying hardware is itself a good demonstration of "elastic, pay-for-what-you-use" in practice — you're not just paying less or more, you're choosing how much commitment you're willing to trade for how much discount.
Connecting to your instance the way AWS actually recommends now
The traditional way to reach an EC2 instance is SSH on port 22, using a keypair. AWS's current guidance has moved fairly firmly away from that as the default, in favor of Systems Manager Session Manager: it connects over HTTPS through an agent already running on the instance, controlled entirely through IAM (that instance needs an IAM role attached — Week 3's roles, put to immediate use), requires zero open inbound ports, needs no bastion host, and logs every session to CloudTrail and optionally CloudWatch or S3 for a full audit trail. For a beginner, the practical upside is simple: one less open port (22) for anything to scan and attack, and one less keypair file to lose track of. We'll actually use this in the lab below.
The other current security default worth knowing: EC2's Instance Metadata Service — the internal endpoint an instance uses to fetch its own temporary IAM credentials — now defaults to IMDSv2 on all newly released instance types. The older IMDSv1 answered plain, unauthenticated requests, which made it a real target for SSRF (server-side request forgery) attacks that trick an application into fetching and leaking an instance's credentials. IMDSv2 requires a signed session token first, closing that hole. You'll actually see this show up directly in the lab's startup script below, not just as theory.
Auto Scaling: elasticity, made literal
Here's where "elastic" (Week 1's buzzword) turns into an actual configuration. An Auto Scaling Group (ASG) manages a fleet of identical EC2 instances instead of one, defined by three numbers: minimum, maximum, and desired capacity. AWS keeps the actual running instance count matching "desired," automatically launching new ones or terminating extras as needed.
What actually drives that number up or down is a scaling policy:
- Target tracking — the one you'll use most: pick a metric (CPU utilization is the classic example) and a target value, and AWS adjusts capacity automatically to hold that target, the same way a thermostat holds a temperature.
- Step scaling — more manual: define specific rules like "add 2 instances if CPU crosses 70%."
- Scheduled scaling — for predictable patterns, like scaling up every weekday morning before traffic reliably rises.
ASGs also continuously run health checks, and automatically replace any instance that fails one — which means a crashed instance doesn't just sit there broken, it gets swapped out without anyone needing to notice at 3 AM.
Load Balancing: the piece that makes a fleet look like one thing
Auto Scaling gives you a fleet of instances, but something still has to decide which instance handles which request, and stop sending traffic to anything unhealthy. That's the Elastic Load Balancer (ELB)'s job, and AWS offers three flavors depending on what you actually need:
- Application Load Balancer (ALB) — operates at Layer 7 (HTTP/HTTPS), understands the actual request content, and can route based on URL path or hostname. The default choice for almost any web application, and the one we'll build below.
- Network Load Balancer (NLB) — operates at Layer 4 (raw TCP/UDP), built for extreme throughput and ultra-low latency when you don't need HTTP-aware routing.
- Gateway Load Balancer (GWLB) — a more specialized case, for routing traffic through third-party virtual security appliances (firewalls, intrusion detection) transparently.
The load balancer and the Auto Scaling Group talk to each other constantly: the ASG registers new instances with the load balancer as it creates them, and the load balancer's own health checks feed back into which instances the ASG considers "healthy" in the first place. Neither one is doing anything particularly clever in isolation — the actual magic is that the two of them, wired together, are the literal mechanism behind the "the cloud scales automatically" line everyone repeats without necessarily knowing what's behind it.
Free Tier Reality Check (read this before you touch the console)
Before building anything real, a genuinely important thing changed recently and a lot of tutorials still haven't caught up: AWS overhauled its Free Tier on July 15, 2025.
- If your AWS account was created before that date, you're on the legacy model: 750 hours/month of a t2.micro (or t3.micro where t2.micro isn't offered) free for your first 12 months, plus 30GB of EBS storage.
- If your account was created after that date — which is almost certainly true if you're brand new to AWS right now — there is no permanent free EC2 tier at all anymore. Instead, new accounts get up to $200 in credits: $100 automatically on signup, plus another $100 (in $20 chunks) for completing five onboarding tasks — launching and terminating an EC2 instance, configuring an RDS database, deploying a Lambda function, testing a Bedrock prompt, and setting up a cost budget. Once that $200 runs out, everything bills at standard rates.
Either way, a couple of things are never fully free regardless of which model you're on: a NAT Gateway running idle still costs roughly $33/month just for existing, and an Application Load Balancer bills hourly the moment it's created, whether it's serving traffic or not. Under the credit model, these just draw down your $200 faster than EC2 alone would.
Practical takeaway: do the lab below in one sitting, and actually run the cleanup step at the end — don't leave it running "to look at again later." That habit alone will save you from the single most common beginner AWS bill surprise.
Hands-On: Build a Real Auto-Scaling, Load-Balanced Web App
This is a genuine, working lab — not a simulation. You'll end up with two EC2 instances, each announcing their own instance ID, sitting behind a load balancer that spreads traffic between them, inside an Auto Scaling Group that will replace either one if it dies. Total cost if you clean up promptly: at most a few cents.
Prerequisites:
- The AWS CLI installed and configured with credentials for the IAM user or role you created back in Week 3 — not root.
- Any region you're comfortable with (I used
ap-south-1; swap it if you're elsewhere). - We'll use your account's default VPC for this — every account has one automatically, already split into public subnets across multiple AZs, which means we don't need to build custom networking yet. (That's Week 6's job — this lab is deliberately self-contained.)
- The
ZeroToHeroEC2Profileinstance profile from Week 3's IAM lab — it's what letsaws ssm start-sessionactually work on the instances we're about to launch. If you skipped that lab, create it now:
__PRE_PLACEHOLDER_0__
Step 1 — Grab the current Amazon Linux AMI dynamically
Never hardcode an AMI ID — they're region-specific and change over time. Pull the current one from AWS's own SSM parameter instead:
1
2
3
4
AMI_ID=$(aws ssm get-parameters \
--names /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 \
--query 'Parameters[0].Value' --output text)
echo $AMI_ID
Step 2 — Grab your default VPC's subnets and default security group
1
2
3
4
5
6
7
8
VPC_ID=$(aws ec2 describe-vpcs --filters Name=isDefault,Values=true \
--query 'Vpcs[0].VpcId' --output text)
SUBNET_IDS=$(aws ec2 describe-subnets --filters Name=vpc-id,Values=$VPC_ID \
--query 'Subnets[].SubnetId' --output text | tr '\t' ',')
SG_ID=$(aws ec2 describe-security-groups --filters Name=vpc-id,Values=$VPC_ID Name=group-name,Values=default \
--query 'SecurityGroups[0].GroupId' --output text)
Then open port 80 on that default security group, since it doesn't allow inbound web traffic yet:
1
2
aws ec2 authorize-security-group-ingress \
--group-id $SG_ID --protocol tcp --port 80 --cidr 0.0.0.0/0
Step 3 — Write a tiny startup script that proves IMDSv2 in practice
This is the same metadata service from the theory section above, actually being used:
1
2
3
4
5
6
7
8
9
10
11
12
cat > userdata.sh <<'SCRIPT'
#!/bin/bash
TOKEN=$(curl -sX PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
INSTANCE_ID=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/instance-id)
AZ=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/placement/availability-zone)
mkdir -p /var/www
echo "Hello from $INSTANCE_ID in $AZ" > /var/www/index.html
cd /var/www && nohup python3 -m http.server 80 &
SCRIPT
Every instance that boots from this will serve a page announcing its own instance ID and AZ — which is exactly how we'll prove the load balancer is actually spreading traffic, not just theoretically capable of it.
Step 4 — Create the Launch Template
1
2
3
4
5
6
7
8
9
10
aws ec2 create-launch-template \
--launch-template-name zero-to-hero-web \
--version-description v1 \
--launch-template-data "{
\"ImageId\":\"$AMI_ID\",
\"InstanceType\":\"t3.micro\",
\"UserData\":\"$(base64 < userdata.sh | tr -d '\n')\",
\"SecurityGroupIds\":[\"$SG_ID\"],
\"IamInstanceProfile\":{\"Name\":\"ZeroToHeroEC2Profile\"}
}"
Step 5 — Create the target group and Application Load Balancer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
TG_ARN=$(aws elbv2 create-target-group \
--name zero-to-hero-tg --protocol HTTP --port 80 \
--vpc-id $VPC_ID --health-check-path / \
--query 'TargetGroups[0].TargetGroupArn' --output text)
ALB_ARN=$(aws elbv2 create-load-balancer \
--name zero-to-hero-alb \
--subnets $(echo $SUBNET_IDS | tr ',' ' ') \
--security-groups $SG_ID \
--scheme internet-facing \
--query 'LoadBalancers[0].LoadBalancerArn' --output text)
aws elbv2 create-listener \
--load-balancer-arn $ALB_ARN --protocol HTTP --port 80 \
--default-actions Type=forward,TargetGroupArn=$TG_ARN
Step 6 — Create the Auto Scaling Group, wired to the target group
1
2
3
4
5
6
7
aws autoscaling create-auto-scaling-group \
--auto-scaling-group-name zero-to-hero-asg \
--launch-template LaunchTemplateName=zero-to-hero-web,Version='$Latest' \
--min-size 1 --max-size 4 --desired-capacity 2 \
--target-group-arns $TG_ARN \
--vpc-zone-identifier "$SUBNET_IDS" \
--health-check-type ELB --health-check-grace-period 60
Give it 2–3 minutes to launch both instances and pass their health checks.
Step 7 — Prove the load balancer is actually load balancing
1
2
3
4
ALB_DNS=$(aws elbv2 describe-load-balancers --names zero-to-hero-alb \
--query 'LoadBalancers[0].DNSName' --output text)
for i in {1..8}; do curl -s http://$ALB_DNS; echo; done
You should see the response alternate between two different instance IDs. That's the whole theory section from above, made real in one loop.
Step 8 — Watch it actually scale
1
2
aws autoscaling set-desired-capacity \
--auto-scaling-group-name zero-to-hero-asg --desired-capacity 3
Re-run the
describe-load-balancers / target group health check after a minute or two, and you'll see a third instance register itself with the load balancer automatically — no manual step required.Step 9 — Clean up (don't skip this part)
Given the Free Tier reality above, this step is the one that actually matters. Delete things in this order, so nothing gets orphaned and left silently billing:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1. Scale down and delete the ASG (this terminates its instances)
aws autoscaling update-auto-scaling-group \
--auto-scaling-group-name zero-to-hero-asg --min-size 0 --desired-capacity 0
aws autoscaling delete-auto-scaling-group \
--auto-scaling-group-name zero-to-hero-asg --force-delete
# 2. Delete the load balancer, then the target group
aws elbv2 delete-load-balancer --load-balancer-arn $ALB_ARN
sleep 30
aws elbv2 delete-target-group --target-group-arn $TG_ARN
# 3. Delete the launch template
aws ec2 delete-launch-template --launch-template-name zero-to-hero-web
# 4. Double-check nothing's still allocated
aws ec2 describe-addresses
That last command should come back empty. If it doesn't, release whatever Elastic IP shows up — remember, since Week... well, since February 2024 officially, AWS charges for every public IPv4 address whether it's doing anything or not.
If you want to poke around an instance before tearing it down,
aws ssm start-session --target <instance-id> gets you a shell with no open port 22 and no keypair — Session Manager from the theory section, actually used.Putting Week 1 through Week 4 together
That's actually a satisfying loop to close: Week 1 promised elasticity and pay-for-what-you-use as the core economic idea behind cloud. Week 2 and 3 explained the infrastructure and security foundation underneath. This week is the first point where all of that becomes a real, working system — and now, a system you've actually built and torn down yourself, not just read about.
Next week — Part 5: Storage and databases — S3, EBS, RDS, and DynamoDB, and the actual reasoning behind SQL vs. NoSQL rather than just the textbook definitions.
Series: AWS: Zero to Hero (9 articles)
- …
- 4AWS: Zero to Hero — Week 4: Actually Building Something — EC2, Auto Scaling, and Load Balancing This article
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article