AWS EC2 Mastery: Deploy and Scale Virtual Servers in the Cloud
AWS EC2 Mastery: Deploy and Scale Virtual Servers in the Cloud
Introduction
Your application is growing. What started as a simple Rails app on a single server now handles thousands of requests per minute during peak hours. Your current hosting can’t keep up—page loads are slow, requests are timing out, and manual server provisioning takes days. You need infrastructure that scales with demand, deploys in minutes not days, and only charges you for what you use.
Amazon Elastic Compute Cloud (EC2) is AWS’s virtual server service that revolutionizes how we think about compute resources. Instead of purchasing physical servers, racking them in data centers, and managing hardware lifecycles, you launch virtual machines in minutes, scale capacity up or down based on demand, and pay only for the hours you consume.
But EC2 isn’t just about launching instances. It’s about understanding instance types for your workload, choosing the right AMI, configuring auto-scaling for traffic spikes, optimizing costs with reserved instances and spot pricing, and building resilient architectures across availability zones.
In this guide, we’ll master EC2 from the ground up: launching instances, understanding instance families, configuring storage, implementing auto-scaling, and building production-grade architectures that handle millions of requests.
What Is Amazon EC2?
EC2 provides resizable compute capacity in the cloud—virtual servers you can launch in minutes and terminate when no longer needed.
Key EC2 Characteristics
| Feature | Description |
|---|---|
| Virtual Servers | Launch instances from Amazon Machine Images (AMIs) |
| Instance Types | Choose CPU, memory, storage, network capacity |
| Elastic | Scale capacity up/down within minutes |
| Pay-as-you-go | Pay only for running instances (per second billing) |
| Global | Launch in any AWS region and availability zone |
Why EC2 Over Traditional Hosting?
| Aspect | Traditional Servers | EC2 |
|---|---|---|
| Provisioning | Days to weeks | Minutes |
| Scaling | Buy more hardware | Launch more instances |
| Cost Model | Fixed (CapEx) | Variable (OpEx) |
| Maintenance | Your responsibility | AWS manages hypervisor layer |
| Global Reach | Expensive | Launch in 30+ regions instantly |
EC2 Instance Types
EC2 offers dozens of instance types optimized for different workloads.
Instance Family Naming Convention
Example: t3.medium
1
2
3
4
t3.medium
│└─┴────────── Size (nano, micro, small, medium, large, xlarge, etc.)
└──────────── Generation (t3 = 3rd gen of t family)
└────────────── Family (t = burstable)
Major Instance Families
| Family | Name | Use Case | vCPU:RAM Ratio |
|---|---|---|---|
| T3/T3a | Burstable | Web servers, dev environments | 1:2 (1 vCPU : 2GB RAM) |
| M5/M6i | General Purpose | Balanced workloads, app servers | 1:4 |
| C5/C6i | Compute Optimized | CPU-intensive, batch processing | 1:2 |
| R5/R6i | Memory Optimized | Databases, caching, analytics | 1:8 |
| X1/X2 | Memory Intensive | SAP HANA, in-memory databases | 1:16+ |
| P3/P4 | GPU Instances | Machine learning, video encoding | GPU-based |
| G4 | Graphics | Gaming, graphics workloads | GPU-based |
| I3/I4i | Storage Optimized | NoSQL databases, data warehouses | NVMe SSD |
Choosing the Right Instance Type
Web application (Rails/Django):
- Start:
t3.medium(2 vCPU, 4GB RAM) - Production:
m5.large(2 vCPU, 8GB RAM)
Database (PostgreSQL):
- Development:
t3.medium - Production:
r5.xlarge(4 vCPU, 32GB RAM)
Background worker (Sidekiq/Celery):
c5.large(2 vCPU, 4GB RAM)
Machine learning training:
p3.2xlarge(8 vCPU, 61GB RAM, Tesla V100 GPU)
Launching Your First EC2 Instance
Let’s launch a Rails application server.
Prerequisites
- AWS CLI configured
- VPC with public subnet (from VPC guide)
- Key pair for SSH access
Step 1: Create Key Pair
1
2
3
4
5
6
7
8
# Create SSH key pair
aws ec2 create-key-pair \
--key-name my-app-key \
--query 'KeyMaterial' \
--output text > my-app-key.pem
# Secure the key
chmod 400 my-app-key.pem
Step 2: Create Security Group
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Create security group in your VPC
aws ec2 create-security-group \
--group-name rails-app-sg \
--description "Security group for Rails application" \
--vpc-id vpc-0abc123def456789
# Allow SSH from your IP
aws ec2 authorize-security-group-ingress \
--group-id sg-0abc123 \
--protocol tcp \
--port 22 \
--cidr 203.0.113.0/24 # Your office IP
# Allow HTTP from anywhere
aws ec2 authorize-security-group-ingress \
--group-id sg-0abc123 \
--protocol tcp \
--port 80 \
--cidr 0.0.0.0/0
# Allow HTTPS from anywhere
aws ec2 authorize-security-group-ingress \
--group-id sg-0abc123 \
--protocol tcp \
--port 443 \
--cidr 0.0.0.0/0
Step 3: Launch Instance
1
2
3
4
5
6
7
8
9
# Launch Ubuntu 22.04 LTS instance
aws ec2 run-instances \
--image-id ami-0c55b159cbfafe1f0 \ # Ubuntu 22.04 AMI (region-specific)
--instance-type t3.medium \
--key-name my-app-key \
--security-group-ids sg-0abc123 \
--subnet-id subnet-0abc123 \ # Public subnet
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=rails-app-server}]' \
--user-data file://user-data.sh
user-data.sh (bootstrap script):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/bin/bash
# Update packages
apt-get update
apt-get upgrade -y
# Install Ruby dependencies
apt-get install -y git curl libssl-dev libreadline-dev zlib1g-dev \
autoconf bison build-essential libyaml-dev libreadline-dev \
libncurses5-dev libffi-dev libgdbm-dev
# Install rbenv
git clone https://github.com/rbenv/rbenv.git /home/ubuntu/.rbenv
git clone https://github.com/rbenv/ruby-build.git /home/ubuntu/.rbenv/plugins/ruby-build
# Install Ruby
/home/ubuntu/.rbenv/bin/rbenv install 3.2.2
/home/ubuntu/.rbenv/bin/rbenv global 3.2.2
# Install Node.js (for asset pipeline)
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt-get install -y nodejs
# Install Yarn
npm install -g yarn
echo "Bootstrap complete!" > /home/ubuntu/bootstrap.log
Output:
1
2
3
4
5
6
7
8
9
{
"Instances": [{
"InstanceId": "i-0abc123def456789",
"InstanceType": "t3.medium",
"State": {"Name": "pending"},
"PublicDnsName": "",
"PrivateIpAddress": "10.0.1.50"
}]
}
Step 4: Wait for Instance to Run
1
2
3
4
5
6
7
8
# Wait for instance to be running
aws ec2 wait instance-running --instance-ids i-0abc123def456789
# Get public IP
aws ec2 describe-instances \
--instance-ids i-0abc123def456789 \
--query 'Reservations[0].Instances[0].PublicIpAddress' \
--output text
Step 5: Connect via SSH
1
2
3
4
ssh -i my-app-key.pem ubuntu@<public-ip>
# Verify bootstrap
cat /home/ubuntu/bootstrap.log
Amazon Machine Images (AMIs)
An AMI is a template containing the OS, application server, and applications needed to launch an instance.
AMI Types
| Type | Description | Use Case |
|---|---|---|
| AWS Official | Amazon Linux, Ubuntu, Windows | Standard OS deployments |
| Marketplace | Pre-configured (LAMP, WordPress, etc.) | Quick setup |
| Community | User-contributed | Specific configurations |
| Custom | Your own images | Repeatable deployments |
Creating a Custom AMI
After configuring your instance with application dependencies:
1
2
3
4
5
6
7
8
9
10
11
# Stop instance (optional but recommended)
aws ec2 stop-instances --instance-ids i-0abc123def456789
# Create AMI
aws ec2 create-image \
--instance-id i-0abc123def456789 \
--name "rails-app-v1.0" \
--description "Rails 7.1 with Ruby 3.2.2, PostgreSQL client, Redis"
# Wait for AMI creation
aws ec2 wait image-available --image-ids ami-0newimage123
Benefits:
- Launch identical instances in seconds
- Version your infrastructure
- Disaster recovery (restore from known-good state)
- Multi-region deployment (copy AMI to other regions)
Launching from Custom AMI
1
2
3
4
5
6
aws ec2 run-instances \
--image-id ami-0newimage123 \
--instance-type t3.medium \
--key-name my-app-key \
--security-group-ids sg-0abc123 \
--subnet-id subnet-0abc123
Result: Instance boots with all dependencies pre-installed.
EC2 Storage Options
EC2 instances can attach multiple storage types.
Elastic Block Store (EBS)
EBS provides persistent block storage that persists independently of instance lifecycle.
| Volume Type | Use Case | IOPS | Throughput | Cost |
|---|---|---|---|---|
| gp3 | General purpose SSD | 3,000-16,000 | 125-1,000 MB/s | $0.08/GB-month |
| io2 | High-performance SSD | Up to 64,000 | Up to 1,000 MB/s | $0.125/GB-month + IOPS cost |
| st1 | Throughput-optimized HDD | N/A | Up to 500 MB/s | $0.045/GB-month |
| sc1 | Cold HDD (archives) | N/A | Up to 250 MB/s | $0.015/GB-month |
Attach EBS volume:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Create 100GB gp3 volume
aws ec2 create-volume \
--availability-zone us-east-1a \
--size 100 \
--volume-type gp3 \
--tag-specifications 'ResourceType=volume,Tags=[{Key=Name,Value=app-data}]'
# Attach to instance
aws ec2 attach-volume \
--volume-id vol-0abc123 \
--instance-id i-0abc123 \
--device /dev/sdf
# On instance: Format and mount
sudo mkfs -t ext4 /dev/sdf
sudo mkdir /data
sudo mount /dev/sdf /data
Instance Store (Ephemeral Storage)
Instance store provides temporary storage physically attached to the host.
Characteristics:
- ✅ High performance: Low latency, high IOPS
- ✅ No cost: Included with instance type
- ❌ Ephemeral: Data lost on stop/terminate/hardware failure
Use cases:
- Temporary files
- Caches
- Buffers
- Stateless applications
Auto Scaling
Auto Scaling automatically adjusts EC2 capacity based on demand.
Components
- Launch Template: Defines instance configuration
- Auto Scaling Group: Manages instance fleet
- Scaling Policies: Rules for scaling up/down
Step 1: Create Launch Template
1
2
3
4
5
6
7
8
9
10
11
aws ec2 create-launch-template \
--launch-template-name rails-app-template \
--version-description "Rails app v1.0" \
--launch-template-data '{
"ImageId": "ami-0abc123",
"InstanceType": "t3.medium",
"KeyName": "my-app-key",
"SecurityGroupIds": ["sg-0abc123"],
"IamInstanceProfile": {"Name": "EC2-S3-Access-Profile"},
"UserData": "IyEvYmluL2Jhc2gKL29wdC9hcHAvc3RhcnQuc2g="
}'
Step 2: Create Auto Scaling Group
1
2
3
4
5
6
7
8
9
10
aws autoscaling create-auto-scaling-group \
--auto-scaling-group-name rails-app-asg \
--launch-template LaunchTemplateName=rails-app-template,Version='$Latest' \
--min-size 2 \
--max-size 10 \
--desired-capacity 3 \
--vpc-zone-identifier "subnet-0abc123,subnet-0def456" \
--health-check-type ELB \
--health-check-grace-period 300 \
--tags Key=Name,Value=rails-app-server,PropagateAtLaunch=true
Parameters:
min-size: Minimum instances (always running)max-size: Maximum instances (cap for scaling)desired-capacity: Initial instance count
Step 3: Configure Scaling Policies
Target Tracking Scaling (recommended):
1
2
3
4
5
6
7
8
9
10
aws autoscaling put-scaling-policy \
--auto-scaling-group-name rails-app-asg \
--policy-name cpu-target-tracking \
--policy-type TargetTrackingScaling \
--target-tracking-configuration '{
"PredefinedMetricSpecification": {
"PredefinedMetricType": "ASGAverageCPUUtilization"
},
"TargetValue": 70.0
}'
Result: Auto Scaling maintains average CPU utilization at 70%
- If CPU > 70%: Launch instances
- If CPU < 70%: Terminate instances
Step Scaling (more control):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Scale up when CPU > 80%
aws autoscaling put-scaling-policy \
--auto-scaling-group-name rails-app-asg \
--policy-name scale-up-policy \
--scaling-adjustment 2 \
--adjustment-type ChangeInCapacity
# Create CloudWatch alarm
aws cloudwatch put-metric-alarm \
--alarm-name cpu-high \
--comparison-operator GreaterThanThreshold \
--evaluation-periods 2 \
--metric-name CPUUtilization \
--namespace AWS/EC2 \
--period 120 \
--statistic Average \
--threshold 80.0 \
--alarm-actions arn:aws:autoscaling:region:account:scalingPolicy:policy-id
Elastic Load Balancer Integration
Integrate Auto Scaling with Application Load Balancer for traffic distribution:
1
2
3
4
# Attach target group to Auto Scaling Group
aws autoscaling attach-load-balancer-target-groups \
--auto-scaling-group-name rails-app-asg \
--target-group-arns arn:aws:elasticloadbalancing:region:account:targetgroup/my-targets/abc123
Traffic flow:
1
Internet → ALB → Target Group → Auto Scaling Group (2-10 instances)
EC2 Pricing Models
1. On-Demand Instances
Pay per second for instances you launch.
- Cost:
t3.medium= $0.0416/hour (~$30/month) - Use case: Unpredictable workloads, testing
- No commitment: Launch and terminate anytime
2. Reserved Instances
Commit to 1-3 years for significant discounts.
| Term | Payment | Discount |
|---|---|---|
| 1 year, No upfront | Monthly | ~30% |
| 1 year, All upfront | Upfront | ~40% |
| 3 years, All upfront | Upfront | ~60% |
Example: t3.medium reserved for 3 years = $0.0166/hour (~$12/month)
Use case: Steady-state workloads (databases, always-on app servers)
3. Spot Instances
Bid on unused EC2 capacity for up to 90% discount.
- Cost:
t3.mediumspot = $0.0125/hour (~$9/month) - Catch: AWS can terminate with 2-minute notice
- Use case: Batch processing, fault-tolerant workloads, CI/CD workers
Launch spot instance:
1
2
3
4
aws ec2 request-spot-instances \
--spot-price "0.05" \
--instance-count 3 \
--launch-specification file://spot-spec.json
4. Savings Plans
Flexible pricing model committing to consistent usage ($/hour) for 1-3 years.
- Discount: Up to 72% vs on-demand
- Flexibility: Applies across instance family, size, region, OS
Use case: Flexible workloads that change instance types frequently
Real-World Architecture: High-Availability Rails Application
Requirements
- Handle 10,000 requests/minute
- Zero downtime deployments
- Auto-scale during traffic spikes
- Multi-AZ for high availability
Architecture
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Internet
│
▼
Application Load Balancer (us-east-1a, us-east-1b)
│
├─────────────┬─────────────┐
│ │ │
▼ ▼ ▼
Auto Scaling Group (2-10 instances)
├── t3.medium (us-east-1a)
├── t3.medium (us-east-1a)
├── t3.medium (us-east-1b)
└── t3.medium (us-east-1b)
│
▼
RDS PostgreSQL (Multi-AZ)
ElastiCache Redis (Cluster Mode)
Implementation
1. Launch Template with IAM role:
1
2
3
4
5
6
7
8
9
aws ec2 create-launch-template \
--launch-template-name rails-production \
--launch-template-data '{
"ImageId": "ami-rails-app",
"InstanceType": "t3.medium",
"IamInstanceProfile": {"Name": "EC2-Production-Role"},
"SecurityGroupIds": ["sg-app-servers"],
"UserData": "base64-encoded-startup-script"
}'
2. Auto Scaling Group (multi-AZ):
1
2
3
4
5
6
7
8
9
10
aws autoscaling create-auto-scaling-group \
--auto-scaling-group-name production-asg \
--launch-template LaunchTemplateName=rails-production \
--min-size 2 \
--max-size 10 \
--desired-capacity 4 \
--vpc-zone-identifier "subnet-1a,subnet-1b" \
--target-group-arns arn:aws:elasticloadbalancing:...:targetgroup/rails-tg \
--health-check-type ELB \
--health-check-grace-period 300
3. Scaling policy (target tracking):
1
2
3
4
5
6
7
8
9
10
11
aws autoscaling put-scaling-policy \
--auto-scaling-group-name production-asg \
--policy-name request-count-tracking \
--policy-type TargetTrackingScaling \
--target-tracking-configuration '{
"PredefinedMetricSpecification": {
"PredefinedMetricType": "ALBRequestCountPerTarget",
"ResourceLabel": "app/my-alb/abc123/targetgroup/rails-tg/def456"
},
"TargetValue": 1000.0
}'
Result: System automatically scales to maintain ~1000 requests/target.
Monitoring and Troubleshooting
CloudWatch Metrics
Monitor key EC2 metrics:
1
2
3
4
5
6
7
8
9
# Get CPU utilization
aws cloudwatch get-metric-statistics \
--namespace AWS/EC2 \
--metric-name CPUUtilization \
--dimensions Name=InstanceId,Value=i-0abc123 \
--start-time 2024-01-01T00:00:00Z \
--end-time 2024-01-01T23:59:59Z \
--period 3600 \
--statistics Average
Key metrics:
CPUUtilization: CPU usage percentageNetworkIn/Out: Network trafficDiskReadOps/WriteOps: Disk I/OStatusCheckFailed: Instance/system health
Instance Metadata
Access instance information from within the instance:
1
2
3
4
5
6
7
8
# Get instance ID
curl http://169.254.169.254/latest/meta-data/instance-id
# Get IAM role credentials
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/EC2-Role
# Get user data
curl http://169.254.169.254/latest/user-data
SSH via Systems Manager
Avoid opening port 22 by using AWS Systems Manager Session Manager:
1
2
3
4
# Install SSM agent (included in Amazon Linux, Ubuntu 16.04+)
# Start session (no key pair needed!)
aws ssm start-session --target i-0abc123def456789
Benefits:
- No inbound firewall rules
- Centralized audit logging
- No key management
Security Best Practices
1. Use IAM Roles, Not Access Keys
❌ Bad: Hardcoded credentials on EC2
1
2
export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/...
✅ Good: IAM instance profile
1
2
3
aws ec2 run-instances \
--iam-instance-profile Name=EC2-S3-Access-Profile \
...
2. Restrict Security Groups
❌ Bad: Open to world
1
2
3
4
# Allow SSH from anywhere
aws ec2 authorize-security-group-ingress \
--group-id sg-abc123 \
--protocol tcp --port 22 --cidr 0.0.0.0/0
✅ Good: Restrict to known IPs
1
2
3
4
# Allow SSH from office only
aws ec2 authorize-security-group-ingress \
--group-id sg-abc123 \
--protocol tcp --port 22 --cidr 203.0.113.0/24
3. Enable EBS Encryption
1
2
3
4
5
6
7
8
9
10
# Enable default EBS encryption
aws ec2 enable-ebs-encryption-by-default --region us-east-1
# Launch with encrypted root volume
aws ec2 run-instances \
--block-device-mappings '[{
"DeviceName":"/dev/xvda",
"Ebs":{"Encrypted":true,"VolumeSize":20}
}]' \
...
4. Regular AMI Updates
1
2
# Automate AMI creation with Lambda
# Monthly AMI builds with latest security patches
Conclusion
AWS EC2 transforms computing from a capital-intensive, slow-to-provision fixed resource into an on-demand, elastic utility. By mastering instance types, AMIs, auto-scaling, and pricing models, you architect infrastructure that scales with your business, not ahead of it or behind it.
The shift from manual server provisioning to launch templates, from fixed capacity to auto-scaling groups, and from guessing capacity needs to metrics-driven scaling transforms infrastructure from a constraint into an enabler.
Start simple: launch a single instance, SSH in, deploy your application. Then evolve: create custom AMIs, implement auto-scaling, integrate with load balancers, optimize costs with reserved instances. Every iteration makes your infrastructure more resilient, more scalable, and more cost-effective.
Master EC2, and you master cloud computing.
Suggested Reading
- AWS EC2 Official Documentation
- EC2 Instance Types
- Auto Scaling Documentation
- EC2 Pricing
- AMI Best Practices
- EC2 Systems Manager