☁️ AWS EC2 Hands-On Practicals
EC2, IAM Roles, EBS, AMIs, Snapshots, CLI and Boto3
Part of my AWS hands-on practical journey. This post documents the EC2 labs and practical tasks I completed, including what I built, how I verified it, and the problems I encountered along the way.
📋 Practicals Covered
| # | Practical |
|---|---|
| 1 | EC2 Web Server with User Data |
| 2 | EC2 + S3 Access via IAM Role |
| 3 | EC2 Instance & EBS Volume Resize |
| 4 | Custom AMI Creation & Validation |
| 5 | EBS Snapshot Cross-Region Disaster Recovery |
| 6 | Elastic IP |
| 7 | EC2 Operations using AWS CLI |
| 8 | EC2 Cost Estimation |
| 9 | EC2 Automation with Boto3 |
🧪 Lab 1 — EC2 Web Server with User Data
What I Did
- Launched an Amazon Linux 2023 EC2 instance using
t2.micro. - Created a security group:
- HTTP (80) →
0.0.0.0/0 - SSH (22) → My IP
- HTTP (80) →
- Added a User Data script during launch.
- The script installed Apache (
httpd), started and enabled the service, retrieved the Instance ID and Availability Zone from EC2 metadata, and created a customindex.html. - Opened the instance's public IP in a browser.
Verification
The instance automatically configured itself during first boot and served a custom webpage containing its Instance ID and Availability Zone.
Apache was verified with:
sudo systemctl status httpd
The service was active, enabled, and listening on port 80.
What Failed
The first instance did not work because of setup issues, so it was recreated cleanly. SSH key-access problems were also encountered on Windows.
Troubleshooting Notes
- User Data was added before launching because it normally runs during first boot.
- The
.pemfile needed to be locally accessible and readable by SSH. - SSH keys stored in cloud-synced folders such as OneDrive can cause file-access or permission issues.
- The correct Security Group needed to be attached.
The troubleshooting order that worked was:
Security Group → EC2 status → SSH → Apache → User Data
🧪 Lab 2 — EC2 + S3 Access via IAM Role
What I Did
The goal was to allow EC2 to access S3 without storing AWS access keys on the instance.
Created:
- S3 bucket:
lab-bucket-tejasshinkar - Test object:
test.txt - IAM Role:
EC2-S3-ReadOnly-Role - Trusted entity: AWS Service → EC2
- Permission:
AmazonS3ReadOnlyAccess
The role was attached to the EC2 instance through its IAM Instance Profile.
Verification
Listed the S3 bucket:
aws s3 ls
Downloaded the test object:
aws s3 cp s3://lab-bucket-tejasshinkar/test.txt .
cat test.txt
Both operations succeeded.
Then write access was tested:
aws s3 cp test.txt s3://lab-bucket-tejasshinkar/write-test.txt
Result:
AccessDenied
The error confirmed that s3:PutObject was not allowed.
What This Proved
EC2
↓
Instance Profile
↓
IAM Role
↓
AmazonS3ReadOnlyAccess
↓
S3
The instance could list and read S3 objects, but could not write to S3.
No aws configure was used and no hardcoded AWS access keys were stored on the instance.
🧪 Lab 3 — EC2 Instance & EBS Volume Resize
Part A — EC2 Instance Resize
- Stopped the running
t3.microinstance. - Changed the instance type to
t3.small. - Started the instance again.
- SSH'd into the instance and verified it was operational.
The instance type had to be changed while the instance was stopped.
Part B — EBS Volume Resize
The root EBS volume was increased:
8 GiB → 16 GiB
This was done through:
EC2 → Volumes → Modify Volume
The AWS Console showed 16 GiB, but the operating system initially reported only about 8 GiB.
Checked the disk layout:
lsblk
The result showed:
nvme0n1 16G
└─nvme0n1p1 8G /
The EBS volume had been expanded, but the partition/filesystem had not yet used the additional space.
Because Amazon Linux 2023 was using XFS:
sudo xfs_growfs /
Finally:
df -h
confirmed approximately 16 GB available to the root filesystem.
Important Troubleshooting
Before running disk commands, the actual device name and filesystem type were checked.
EBS Volume
↓
16 GB
↓
Partition / Filesystem
↓
16 GB usable by OS
The EBS volume expansion and filesystem expansion are separate operations.
For filesystem expansion:
XFS → xfs_growfs
ext4 → resize2fs
EBS volume expansion can be performed while the instance is running, unlike changing the EC2 instance type.
🧪 Lab 4 — Custom AMI Creation & Validation
What I Did
- Launched an Amazon Linux 2023 EC2 instance.
- Installed and configured Nginx and Git.
- Created a custom webpage:
echo "<h1>Custom AMI - $(date)</h1>" | sudo tee /usr/share/nginx/html/index.html
- Verified Nginx locally:
curl localhost
- Created
custom-nginx-ami. - Waited for the AMI to become Available.
- Launched a new EC2 instance using the custom AMI.
- SSH'd into the new instance.
- Verified Nginx:
sudo systemctl status nginx
curl localhost
- Opened the new instance's public IP and confirmed the webpage was served.
What I Proved
The new EC2 inherited:
- Nginx installation
- Nginx configuration/service state
- Custom
index.html
No User Data script was required on the new instance.
Configured EC2
↓
Create AMI
↓
Reusable EC2 Template
↓
New EC2
↓
Same configuration
Mistakes & Troubleshooting
1. Browser SSH failed
The Security Group allowed SSH only from my IP, so browser-based EC2 Instance Connect did not work. I used the SSH client from PowerShell instead.
2. Tried SSH as root
Amazon Linux required the default user:
ec2-user
3. Website initially did not load
The problem was using:
https://<public-ip>
instead of:
http://<public-ip>
Nginx was serving HTTP on port 80, not HTTPS on port 443.
🧪 Lab 5 — EBS Snapshot Cross-Region Disaster Recovery
What I Did
The objective was to create an EBS snapshot in Mumbai, copy it to Virginia, restore it as a new EBS volume, and verify the original data.
Source Region:
ap-south-1
Destination Region:
us-east-1
Created test data:
echo "Mumbai DR test - original data" > test-dr.txt
Then:
- Created an EBS Snapshot.
- Copied the snapshot from Mumbai to Virginia.
- Created a new EBS volume from the copied snapshot in
us-east-1. - Launched an EC2 instance in the destination Region.
- Attached the restored EBS volume as a secondary volume.
- Mounted the restored volume.
- Verified the original file:
ls /data
cat /data/test-dr.txt
The original data was successfully restored.
Architecture
Mumbai (ap-south-1)
│
EC2
│
EBS Volume
│
▼
Snapshot
│
│ Cross-Region Copy
▼
Virginia (us-east-1)
│
Copied Snapshot
│
▼
New EBS Volume
│
▼
EC2
│
▼
Original Data
Mistakes & Troubleshooting
- Initially created a snapshot before adding the test file, so another snapshot had to be created with the required data.
- Multiple snapshots and volumes made identification confusing, so Snapshot IDs needed to be checked carefully.
- The destination EC2 already had its own root EBS volume; the restored volume was attached separately as a secondary disk.
- When attaching an EBS volume, the EC2 instance and volume must be in the same Availability Zone.
🔧 Additional Practical Tasks
Practical Task 1 — Elastic IP
What I Did
- Launched an EC2 instance and recorded its initial dynamic public IP.
- Stopped and started the instance.
- Confirmed that the public IP changed.
- Allocated an Elastic IP from EC2 → Elastic IPs.
- Associated the Elastic IP with the instance.
- Stopped and started the instance again.
- Confirmed that the Elastic IP remained unchanged.
- Released the Elastic IP after completing the practical.
Verification
Before Elastic IP
EC2 → Stop → Start → Different Public IP ❌
After Elastic IP
EC2 → Stop → Start → Same Public IP ✅
Practical Task 2 — EC2 Operations Using AWS CLI
The objective was to manage EC2 using only AWS CLI rather than the AWS Console.
AWS CloudShell was used because it provides a pre-configured AWS CLI environment.
Find Running Instances
aws ec2 describe-instances \
--filters "Name=instance-state-name,Values=running" \
--query "Reservations[].Instances[]. [InstanceId,InstanceType,State.Name,PublicIpAddress]" \
--output table
This returned the running instance:
Instance ID: i-0548517fe09f6eeeb
Type: t3.micro
State: running
Stop the Instance
aws ec2 stop-instances --instance-ids i-0548517fe09f6eeeb
The instance state was then verified using describe-instances.
The practical demonstrated the flow:
CloudShell
↓
AWS CLI
↓
EC2 API
↓
Describe / Stop / Start / Snapshot
Practical Task 3 — EC2 Cost Estimation
Used AWS Pricing Calculator to estimate the monthly cost of:
- 1 ×
t3.microEC2 - Linux
ap-south-1- Running 24/7
- 20 GB gp3 EBS
- 100 GB data transfer out
- Comparison with a 1-year Reserved Instance, No Upfront
The configuration was compared as:
EC2 t3.micro
+ 20 GB gp3 EBS
+ 100 GB Data Transfer Out
↓
On-Demand Monthly Cost
VS
1-Year Reserved Instance Monthly Cost
The Reserved Instance estimate was cheaper for the continuous workload.
The EBS and data-transfer costs were considered separately rather than assuming the compute discount applies to the entire AWS bill.
Practical Task 4 — EC2 Automation with Python + Boto3
The task was to list EC2 instances in ap-south-1, display their Instance ID, Instance Type and State, and automatically stop running instances tagged:
Environment: dev
Script
import boto3
ec2 = boto3.client("ec2", region_name="ap-south-1")
response = ec2.describe_instances()
for reservation in response["Reservations"]:
for instance in reservation["Instances"]:
instance_id = instance["InstanceId"]
instance_type = instance["InstanceType"]
state = instance["State"]["Name"]
environment = None
for tag in instance.get("Tags", []):
if tag["Key"] == "Environment":
environment = tag["Value"]
print(
f"ID: {instance_id} | "
f"Type: {instance_type} | "
f"State: {state}"
)
if state == "running" and environment == "dev":
print(f"Stopping dev instance: {instance_id}")
ec2.stop_instances(InstanceIds=[instance_id])
Automation Logic
Boto3
↓
Connect to EC2 (ap-south-1)
↓
List instances
↓
Print ID + Type + State
↓
Check Environment tag
↓
Environment = dev?
↓
Running?
↓
STOP
The important condition was:
if state == "running" and environment == "dev":
So the script does not stop:
- Production instances
- Stopped dev instances
- Testing instances
Only running instances tagged Environment=dev are stopped.
⚡ Practical Summary
EC2 + User Data: Automated first-boot configuration and Apache setup.
EC2 + IAM Role: Accessed S3 without storing AWS credentials on the instance and verified least-privilege read-only access.
EC2 + EBS: Resized compute and storage, including expanding the filesystem after increasing the EBS volume.
Custom AMI: Created a reusable configured EC2 template and launched another instance from it.
EBS Snapshot + Cross-Region Copy: Restored data in another AWS Region as a basic disaster-recovery workflow.
Elastic IP: Verified the difference between dynamic public IPs and a persistent Elastic IP.
AWS CLI: Performed EC2 operations from CloudShell without using the Console.
Boto3: Used Python to inspect EC2 instances and automate stopping only running development instances.
AWS Hands-On Practicals — EC2 | Cloud + DevOps learning journey
Top comments (0)