EC2 Fundamentals
EC2 Fundamentals Amazon EC2 (Elastic Compute Cloud) provides resizable compute capacity in the cloud. Understanding the instance lifecycle, metadata service, an…
EC2 Fundamentals
Amazon EC2 (Elastic Compute Cloud) provides resizable compute capacity in the cloud. Understanding the instance lifecycle, metadata service, and user data scripts is the baseline for working with EC2 programmatically.
Instance Lifecycle & AWS CLI Launch
# Instance states:
# pending → running → stopping → stopped → terminated
# running → rebooting → running (OS reboot, instance stays on same host)
# Stopped instances do NOT incur compute charges but EBS volumes still cost money
# Launch an instance with AWS CLI
aws ec2 run-instances \
--image-id ami-0c02fb55956c7d316 \
--instance-type t3.micro \
--key-name my-key-pair \
--security-group-ids sg-0a1b2c3d4e5f67890 \
--subnet-id subnet-0bb1c79de3EXAMPLE \
--associate-public-ip-address \
--tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=web-server},{Key=Env,Value=prod}]" \
--count 1
# Start / Stop / Reboot / Terminate
aws ec2 start-instances --instance-ids i-1234567890abcdef0
aws ec2 stop-instances --instance-ids i-1234567890abcdef0
aws ec2 reboot-instances --instance-ids i-1234567890abcdef0
aws ec2 terminate-instances --instance-ids i-1234567890abcdef0
# Describe running instances with filter
aws ec2 describe-instances \
--filters "Name=instance-state-name,Values=running" "Name=tag:Env,Values=prod" \
--query "Reservations[*].Instances[*].{ID:InstanceId,IP:PublicIpAddress,Type:InstanceType}"
# Connect via EC2 Instance Connect (no key pair needed, uses IAM)
aws ec2-instance-connect send-ssh-public-key \
--instance-id i-1234567890abcdef0 \
--instance-os-user ec2-user \
--ssh-public-key file://~/.ssh/id_rsa.pub
ssh ec2-user@<public-ip>Instance Metadata Service (IMDS)
Every EC2 instance can query its own metadata from 169.254.169.254. IMDSv2 (token-based) is the secure default — IMDSv1 allows SSRF attacks.
# IMDSv2 — get a session token first (TTL in seconds)
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
# Then use the token in subsequent requests
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/
# Useful metadata endpoints (prefix all with http://169.254.169.254/latest/meta-data/)
# instance-id — i-1234567890abcdef0
# instance-type — t3.micro
# local-ipv4 — private IP
# public-ipv4 — public IP (absent if no public IP)
# public-hostname — ec2-x-x-x-x.compute-1.amazonaws.com
# placement/availability-zone — us-east-1a
# iam/security-credentials/ — temporary credentials from instance role
# ami-id — the AMI used to launch
# Get IAM role credentials from instance (for use in scripts)
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/MyRoleName
# Enforce IMDSv2 only (disable IMDSv1) — do this on all instances
aws ec2 modify-instance-metadata-options \
--instance-id i-1234567890abcdef0 \
--http-tokens required \
--http-put-response-hop-limit 1User Data Scripts
# User data runs as root on first boot only (re-run on every boot with cloud-init config)
# Pass as a script when launching:
aws ec2 run-instances \
--image-id ami-0c02fb55956c7d316 \
--instance-type t3.micro \
--key-name my-key \
--user-data file://bootstrap.sh
# bootstrap.sh — typical web server setup
#!/bin/bash
set -e
yum update -y
yum install -y nginx
systemctl enable nginx
systemctl start nginx
# Install Node.js 20 via nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
export NVM_DIR="/root/.nvm"
source "$NVM_DIR/nvm.sh"
nvm install 20
nvm use 20
# Log user data output
exec > >(tee /var/log/user-data.log | logger -t user-data -s 2>/dev/console) 2>&1
# View user data logs on the instance
cat /var/log/cloud-init-output.log
tail -f /var/log/user-data.log
# View user data that was passed to an instance
aws ec2 describe-instance-attribute \
--instance-id i-1234567890abcdef0 \
--attribute userData \
--query "UserData.Value" --output text | base64 --decode