AWS for DevOps — EC2 Basics

3 minute read

3.1 — EC2 Basics

EC2 (Elastic Compute Cloud) lets you launch virtual servers — called instances — in minutes. You choose the operating system, CPU, memory, storage, and network configuration.

Instance Types

AWS offers hundreds of instance types, grouped into families:

Family Optimised For Example Use Case
t3/t4g General purpose (burstable) t3.micro, t3.medium Dev servers, small apps
m5/m6i General purpose (fixed) m5.xlarge Production web servers
c5/c6i Compute c5.2xlarge Build agents, batch jobs
r5/r6i Memory r5.large Databases, caching
g4/g5 GPU g4dn.xlarge ML inference, video
i3/i4i Storage I/O i3.large Databases, data lakes

Naming convention: t3.micro

  • t3 = family + generation
  • micro = size (vCPU + memory)

Instance Lifecycle

  pending ──► running ──► stopping ──► stopped
                │                        │
                │                        └──► starting ──► running
                │
                └──► shutting-down ──► terminated
State Billing Data
Running Charged Instance store data persists
Stopped Not charged (EBS still charged) EBS data persists
Terminated Not charged All data lost (unless EBS DeleteOnTermination: false)

Launching an Instance (CLI)

$ aws ec2 run-instances \
    --image-id ami-0abcdef1234567890 \
    --instance-type t3.micro \
    --key-name my-key-pair \
    --security-group-ids sg-0abc123def456 \
    --subnet-id subnet-0abc123 \
    --count 1 \
    --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=web-server-01},{Key=Environment,Value=dev}]'
{
    "Instances": [
        {
            "InstanceId": "i-0abc123def456789",
            "InstanceType": "t3.micro",
            "State": {"Name": "pending"},
            "PrivateIpAddress": "10.0.1.50"
        }
    ]
}

Describing Instances

# List all running instances
$ aws ec2 describe-instances \
    --filters "Name=instance-state-name,Values=running" \
    --query 'Reservations[*].Instances[*].[InstanceId,InstanceType,PublicIpAddress,Tags[?Key==`Name`].Value|[0]]' \
    --output table
------------------------------------------------------
|              DescribeInstances                      |
+-----------------+----------+----------------+-------+
| i-0abc123def456 | t3.micro | 3.110.45.67    | web-1 |
| i-0def456abc789 | t3.small | 3.110.45.68    | web-2 |
+-----------------+----------+----------------+-------+

Connecting to an Instance

# SSH (Linux)
$ ssh -i my-key-pair.pem ec2-user@3.110.45.67

# Or use SSM Session Manager (no SSH key needed, no inbound port)
$ aws ssm start-session --target i-0abc123def456

DevOps preference: Use SSM Session Manager — it requires no open inbound ports, logs all sessions to CloudTrail, and works through private subnets.

Elastic IP Addresses

By default, a public IP changes every time you stop and start an instance. An Elastic IP is a static public IP:

# Allocate an Elastic IP
$ aws ec2 allocate-address --domain vpc
{
    "AllocationId": "eipalloc-0abc123",
    "PublicIp": "52.66.100.200"
}

# Associate it with an instance
$ aws ec2 associate-address \
    --instance-id i-0abc123def456 \
    --allocation-id eipalloc-0abc123

Warning: An unattached Elastic IP costs money. Always release it when not in use.

Pricing Models

Model Discount Commitment Best For
On-Demand None (full price) None Short-term, unpredictable workloads
Reserved Up to 72% 1 or 3 years Steady-state production
Savings Plans Up to 72% 1 or 3 years (flexible) Mixed workloads
Spot Up to 90% None (can be interrupted) Build agents, batch jobs, testing

Spot Instances are perfect for DevOps:

  • CI/CD build agents (job can retry if interrupted)
  • Load testing (short-lived, cost-sensitive)
  • Data processing (checkpointable work)
# Request a spot instance
$ aws ec2 run-instances \
    --image-id ami-0abcdef1234567890 \
    --instance-type c5.large \
    --instance-market-options 'MarketType=spot,SpotOptions={MaxPrice=0.05,SpotInstanceType=one-time}' \
    --count 1

Instance Metadata

From inside an EC2 instance, you can query metadata about itself:

# Get the instance ID (IMDSv2 — recommended)
$ TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
    -H "X-aws-ec2-metadata-token-ttl-seconds: 300")

$ curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
    http://169.254.169.254/latest/meta-data/instance-id
i-0abc123def456789

# Get the IAM role credentials
$ curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
    http://169.254.169.254/latest/meta-data/iam/security-credentials/EC2-S3-ReadRole
{
    "AccessKeyId": "ASIA...",
    "SecretAccessKey": "...",
    "Token": "...",
    "Expiration": "2026-03-30T12:00:00Z"
}

Security note: Always use IMDSv2 (token-required). IMDSv1 is vulnerable to SSRF attacks.

Common Mistakes

Mistake Why It Hurts Fix
Leaving instances running after testing Unexpected bills Use tags + scheduled shutdown
Using t3.micro for production CPU credits run out under load Use m5/m6i for steady workloads
Allowing IMDSv1 SSRF attacks can steal credentials Enforce IMDSv2 with --metadata-options HttpTokens=required
Not tagging instances Can’t tell what anything is for Tag everything: Name, Environment, Owner

Quick Reference

Task Command
Launch instance aws ec2 run-instances --image-id AMI --instance-type TYPE --key-name KEY
List instances aws ec2 describe-instances --filters Name=instance-state-name,Values=running
Stop instance aws ec2 stop-instances --instance-ids ID
Start instance aws ec2 start-instances --instance-ids ID
Terminate instance aws ec2 terminate-instances --instance-ids ID
Get public IP aws ec2 describe-instances --instance-ids ID --query 'Reservations[0].Instances[0].PublicIpAddress'

Next → AMIs & Key Pairs

Updated: