← Back to all topics
$ aws configure --profile devops

AWS Cloud Essentials
Instructor Guide

EC2, S3, IAM, CloudFront, Beanstalk — every AWS service a junior DevOps engineer must know, hands-on.

01
Cloud Computing Fundamentals — IaaS, PaaS, SaaS
Why companies moved from racks to AWS, and the three abstractions you'll meet daily

How to explain to students

Open with the pizza analogy: "Cooking at home = on-prem (you do everything). Take-and-bake = IaaS (kitchen + ingredients are yours, but the dough is provided). Delivery = PaaS (you eat at home, someone else cooks). Dining out = SaaS (you just show up)." Same hunger, very different ownership boundaries.

In AWS terms: EC2 is IaaS (you get a VM, you install everything). Elastic Beanstalk is PaaS (push your code, AWS runs it). Cognito is SaaS (auth-as-a-service, no code). DevOps engineers spend most time in IaaS + PaaS.

cloud-models.txt
# Layered responsibility — what YOU manage vs what AWS manages

ON-PREM IaaS (EC2) PaaS (Beanstalk) SaaS (Cognito)
App code you you you AWS
Runtime you you AWS AWS
OS you you AWS AWS
Hypervisor you AWS AWS AWS
Servers you AWS AWS AWS
Networking you AWS AWS AWS

# Pizza version
On-prem = make your own from scratch
IaaS = take-and-bake kit (you bake)
PaaS = delivery (someone bakes, you serve)
SaaS = restaurant (just eat)
⚙️
IaaS = max control
Bare VMs. You patch the kernel, run nginx, manage backups. Examples: EC2, EBS.
📦
PaaS = ship faster
"Here's my code, run it." AWS handles OS, scaling, balancer. Examples: Beanstalk, App Runner, Lambda.
🍽️
SaaS = pure consumption
No infra at all. You call an API. Examples: Cognito, SES, Slack.
🪜
Move up the stack
Start IaaS for control, graduate to PaaS once patterns are stable.

🎯 Practice Questions

Q1.
Place these AWS services on the IaaS / PaaS / SaaS spectrum: EC2, RDS, Lambda, Cognito, S3, Elastic Beanstalk.
Show Answer
IaaS: EC2 (raw VM), S3 (raw storage). You decide what to run / store on top.
PaaS: RDS (managed database — you choose engine + size, AWS handles backups + patches), Elastic Beanstalk (push code, AWS runs it), Lambda (push functions, AWS runs them).
SaaS: Cognito (auth-as-a-service — call an API, no infra at all).

The lines blur — Lambda is sometimes called Functions as a Service (FaaS), a sub-category of PaaS. The point is: as you move IaaS → SaaS, you give up control to gain speed.
Q2.
A startup wants to ship a Node.js API in 2 days. Should they pick EC2 or Beanstalk? Justify in 2 sentences.
Q3.
Name two trade-offs of moving from IaaS (EC2) to PaaS (Beanstalk) — one win, one loss.
💡 Think about velocity vs. customisation.
02
AWS Core Services — EC2, S3, IAM, VPC
The four services that show up in every AWS architecture diagram

How to explain to students

AWS has 250+ services. You don't need 250. Master four — EC2 (compute), S3 (storage), IAM (permissions), VPC (networking) — and you can read 90% of architecture diagrams. Everything else is built on top of these.

Mental model: "VPC is the building. EC2 is the rooms. S3 is the warehouse next door. IAM is the keycard system."

aws-cli — quick tour
# EC2 — virtual machines
$ aws ec2 run-instances \
  --image-id ami-0abcd1234 \
  --instance-type t3.micro \
  --key-name my-keypair
"InstanceId": "i-0a1b2c3d4e5f", "State": { "Name": "pending" }

# S3 — object storage (think Dropbox for code)
$ aws s3 mb s3://my-portfolio-site
$ aws s3 sync ./dist s3://my-portfolio-site
upload: dist/index.html to s3://my-portfolio-site/index.html ✓

# IAM — who can do what
$ aws iam create-user --user-name deployer
$ aws iam attach-user-policy --user-name deployer \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

# VPC — your private network in the cloud
$ aws ec2 describe-vpcs --query "Vpcs[].[VpcId,CidrBlock]"
[["vpc-default-abc", "172.31.0.0/16"]]
💻
EC2
Virtual machines. Pick AMI + instance type + key pair → SSH in.
🗄️
S3
Object storage. Effectively unlimited, $0.023/GB/month. Static sites live here.
🔑
IAM
Users, groups, roles, policies. The auth-and-authz layer.
🌐
VPC
Your private network. Subnets, route tables, security groups, NAT gateways.

🎯 Practice Questions

Q1.
Match each AWS service to its closest "everyday" analogue: EC2, S3, IAM, VPC. Use exactly one of: keycard system, file cabinet, rented apartment, gated community.
Show Answer
EC2 → rented apartment — you furnish it, you live there. Comes empty.
S3 → file cabinet — drop in objects, retrieve them. No structure beyond keys.
IAM → keycard system — controls who can enter which rooms.
VPC → gated community — the whole neighborhood + which cars can enter, with internal streets (subnets) and gates (security groups).
Q2.
An EC2 instance can't reach the internet. Name three places to look in the VPC layer.
💡 Internet gateway, route table, security group.
Q3.
You see i-0a1b2c3d, vpc-456, and arn:aws:iam::123:user/deploy in a CloudFormation log. What is each, and which AWS service?
Q4.
Without using IAM, can you make an S3 bucket private? Why is using IAM still preferable for app-level access?
03
Account Setup, Free Tier, Regions & Billing Discipline
The "first 30 minutes" that prevents a surprise $400 bill

How to explain to students

More students quit AWS over a surprise bill than for any technical reason. Spend 30 minutes locking down billing before spinning up a single resource: enable MFA on root, create an IAM user for daily work, set a billing alarm at $5, and confirm Free Tier is active. This is non-negotiable.

Pick a region close to you (latency) and to your students (compliance). For Pakistan-based learners, me-central-1 (UAE) or ap-south-1 (Mumbai) are the lowest latency. For free-tier learning, us-east-1 often has the broadest service availability.

first-30-minutes.sh
# 1. Enable MFA on the ROOT account immediately
# Console → Security credentials → Enable MFA (use Authy/1Password)

# 2. Create an IAM user for daily work — never use root for day-to-day
$ aws iam create-user --user-name muzammil
$ aws iam attach-user-policy --user-name muzammil \
  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
$ aws iam create-access-key --user-name muzammil

# 3. Configure CLI with the new user — root key never touches your laptop
$ aws configure
AWS Access Key ID: AKIA...
AWS Secret Access Key: ****
Default region: me-central-1
Default output: json

# 4. Billing alarm at $5 — catches drift before it becomes pain
$ aws cloudwatch put-metric-alarm \
  --alarm-name "billing-5usd" \
  --metric-name EstimatedCharges \
  --namespace AWS/Billing \
  --statistic Maximum --period 21600 \
  --threshold 5 --comparison-operator GreaterThanThreshold \
  --region us-east-1 \
  --alarm-actions arn:aws:sns:us-east-1:123:billing-topic

# 5. Pick the right region for your AZs
$ aws ec2 describe-availability-zones --region me-central-1
"ZoneName": "me-central-1a", "State": "available"
"ZoneName": "me-central-1b", "State": "available"
"ZoneName": "me-central-1c", "State": "available"
🚨
Billing alarm @ $5
First thing you do. Tied to email/SMS via SNS. Has saved every junior I've taught.
🔐
MFA on root
Then never log in as root again. Daily work in an IAM user.
🌍
Pick one region
Resources don't cross regions easily. Pick by latency + compliance.
🆓
Free tier ≠ free
12-month free tier on some services, always-free on others. Read each line.

🎯 Practice Questions

Q1.
A student wakes up to a $217 AWS bill from one weekend. Name three things they almost certainly didn't set up before launching resources.
Show Answer
Most likely missed:
1. A billing alarm via CloudWatch + SNS — would have emailed at $5, $25, $100.
2. Free-tier verification — they ran a non-free-tier instance type (e.g. t3.large instead of t3.micro), or kept resources running 24/7 past the 750-hr/month free quota.
3. A "stop on shutdown" plan — left an EC2 + RDS + NAT gateway running. NAT gateway alone is ~$32/month idle.

Honourable mentions: no AWS Budgets, no aws-nuke teardown script, no daily check-in habit.
Q2.
Why is creating an IAM user for daily work the very first thing you do, instead of just using your root credentials?
Q3.
For a student in Karachi, list two AWS regions you'd choose first and why. List one region you'd avoid for latency reasons.
Q4.
What's an Availability Zone, and why does Amazon recommend deploying production workloads across at least two of them?
💡 An AZ is roughly one physical data centre.
04
IAM Concepts & Writing Least-Privilege Policies
Users, roles, groups, policies — the AWS auth model demystified

How to explain to students

IAM is the most-skipped, most-failed-interview-on AWS topic. The mental model: users are humans, roles are non-humans (an EC2 instance, a Lambda, GitHub Actions). Both can have policies attached — JSON documents listing allowed actions on specific resources. Always grant the minimum permissions to do the job (least privilege).

Anti-pattern: Action: "*", Resource: "*". That's the IAM equivalent of chmod 777 — it works, until it ruins your week.

iam — least-privilege policy
# ❌ The lazy-but-disastrous policy
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "*",
    "Resource": "*"
  }]
}

# ✅ Least-privilege: deploy script can ONLY put objects in ONE bucket
{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "DeployToPortfolio",
    "Effect": "Allow",
    "Action": [
      "s3:PutObject", "s3:DeleteObject",
      "s3:ListBucket"
    ],
    "Resource": [
      "arn:aws:s3:::my-portfolio-site",
      "arn:aws:s3:::my-portfolio-site/*"
    ]
  }]
}

# Roles for non-humans — EC2 instance assumes a role
$ aws iam create-role --role-name ec2-app-role \
  --assume-role-policy-document '{
    "Version":"2012-10-17",
    "Statement":[{"Effect":"Allow",
      "Principal":{"Service":"ec2.amazonaws.com"},
      "Action":"sts:AssumeRole"}]}'
👤
User vs Role
User = human (with password). Role = non-human (assumed temporarily, no long-lived creds).
📜
Policies = JSON
Effect (Allow/Deny), Action, Resource, optional Condition. That's it.
👥
Groups for humans
Attach policies to a group ("developers"), add users. Don't policy each user individually.
📐
Least privilege
Start with nothing, add only what fails. Use Access Analyzer to find unused permissions.

🎯 Practice Questions

Q1.
Write a minimal IAM policy that lets a user upload to only the S3 bucket course-uploads — and nothing else.
Show Answer
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["s3:PutObject", "s3:ListBucket"],
    "Resource": [
      "arn:aws:s3:::course-uploads",
      "arn:aws:s3:::course-uploads/*"
    ]
  }]
}


Notice two ARNs: the bucket itself (for ListBucket) and /* for the objects inside (for PutObject). Forgetting ListBucket on the bucket ARN is the most common bug.
Q2.
Why give an EC2 instance a role instead of baking long-lived AWS credentials into the instance via env vars?
Q3.
A teammate's policy uses "Action": "*". Suggest two specific replacements for two different real-world scenarios (e.g. a CI deploy role, a read-only audit user).
Q4.
What is an assume-role trust policy, and how is it different from the regular permission policies you attach?
💡 Trust policy = "who can assume this role?" Permission policy = "what can the role do once assumed?"
05
Static vs Dynamic Application Deployment
The single decision that determines half your AWS architecture

How to explain to students

Static = pre-built HTML/CSS/JS files. No server required. Render once, serve forever (until rebuild). Examples: portfolios, marketing sites, React/Vue SPAs after build. AWS path: S3 + CloudFront ($1–2/month).

Dynamic = a server runs code on every request. Examples: REST APIs, server-rendered apps, anything with a database. AWS path: Elastic Beanstalk, ECS, or EC2 ($10+/month).

Many students push static sites onto EC2 because they don't realise S3 + CloudFront is faster, cheaper, and infinitely more scalable. Always ask: "Does this need a runtime, or just files?"

decision-tree
# Does your app need a server runtime?

┌─ "Just HTML/CSS/JS files?"
│ → STATIC: S3 + CloudFront
│ Cost: $1–2/mo
│ Scale: ∞ (CDN edge)
Your app ───┤
│ "Code runs on every request?"
│ "Talks to a database?"
└──→ DYNAMIC: Beanstalk / ECS / EC2 / Lambda
Cost: $10+/mo
Scale: pay per instance/request

# Examples
Portfolio site → STATIC (S3 + CloudFront)
Marketing landing page → STATIC (S3 + CloudFront)
React SPA (built dist) → STATIC (S3 + CloudFront)
Node API + Postgres → DYNAMIC (Beanstalk / ECS)
Next.js (SSR) → DYNAMIC (Beanstalk / Lambda@Edge)
Login form + form data → DYNAMIC (Lambda + DynamoDB)

🎯 Practice Questions

Q1.
Classify these as static or dynamic, and pick the AWS path: (a) a Markdown blog generated by Astro, (b) a Node.js auth service, (c) a Vue SPA + REST backend, (d) a static résumé.
Q2.
Why is putting a portfolio site on EC2 (rather than S3 + CloudFront) usually the wrong choice — even if it "works"?
Show Answer
Three reasons:
1. Cost: EC2 t3.micro = ~$8/mo running 24/7. S3 + CloudFront for a low-traffic portfolio = under $1/mo.
2. Operational burden: EC2 needs OS patches, restarts, certificate management, security-group tuning. S3 + CloudFront is fully managed — zero ops.
3. Performance + scale: EC2 serves from one region. CloudFront caches at 400+ edge locations globally — your portfolio loads in 50ms in Karachi and Toronto.

The only reason to use EC2 for a static site is if your hosting platform requires it (rare). Otherwise, S3 + CloudFront wins on every axis.
Q3.
Next.js can run as a fully-static export (SSG) or as a server-rendered app (SSR). Which AWS path fits each?
💡 SSG = pre-built HTML files. SSR = needs a runtime per request.
06
Static Site Hosting — S3 + CloudFront + Route 53
The cheapest, fastest, most reliable way to put HTML on the internet

How to explain to students

The architecture is simple: S3 stores your files. CloudFront is the CDN that caches them at 400+ edge locations and adds HTTPS. Route 53 points your custom domain at CloudFront. Total cost for a typical portfolio: under $2/month.

Common mistake: students enable "S3 static website hosting" directly and skip CloudFront. That gives you HTTP-only and no CDN. Always front S3 with CloudFront — it's free up to 1 TB/month outbound on most regions.

deploy-static.sh — full setup
# 1. Create + sync to S3
$ aws s3 mb s3://muzammil-portfolio --region me-central-1
$ aws s3 sync ./dist s3://muzammil-portfolio \
  --delete --cache-control "max-age=86400"

# 2. Block public bucket access — CloudFront will read via OAC
$ aws s3api put-public-access-block --bucket muzammil-portfolio \
  --public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true

# 3. Request a free TLS cert in us-east-1 (CloudFront requirement)
$ aws acm request-certificate --domain-name muzammilbilwani.com \
  --validation-method DNS --region us-east-1

# 4. Create CloudFront distribution pointing at S3
$ aws cloudfront create-distribution \
  --distribution-config file://cf-config.json
"Id": "E1A2B3C4D5", "DomainName": "d1234.cloudfront.net"

# 5. Point the domain at CloudFront via Route 53 alias record
$ aws route53 change-resource-record-sets --hosted-zone-id Z123 \
  --change-batch '{ "Changes": [{
    "Action": "UPSERT",
    "ResourceRecordSet": {
      "Name": "muzammilbilwani.com",
      "Type": "A",
      "AliasTarget": {
        "HostedZoneId": "Z2FDTNDATAQYW2",
        "DNSName": "d1234.cloudfront.net",
        "EvaluateTargetHealth": false
      }}}]}'

# 6. After every redeploy, invalidate CloudFront cache
$ aws cloudfront create-invalidation \
  --distribution-id E1A2B3C4D5 --paths "/*"
S3 CloudFront Route 53 ACM OAC cache invalidation

🎯 Practice Questions

Q1.
Why must the ACM certificate for CloudFront be requested in us-east-1 (N. Virginia), even if your S3 bucket is in me-central-1?
Show Answer
CloudFront is a global service with no notion of region for itself, but its certificate-validation backend lives in us-east-1 by historical AWS design. CloudFront only reads ACM certificates from us-east-1.

Your S3 bucket can live in any region — CloudFront pulls from it via signed requests regardless. But if you request your cert in me-central-1, CloudFront simply won't see it. This is one of the most common day-1 AWS gotchas.
Q2.
After deploying a new index.html via aws s3 sync, users still see the old version. What command makes CloudFront fetch the new file?
Q3.
What's an Origin Access Control (OAC), and why use one instead of making the S3 bucket publicly readable?
💡 With OAC, the bucket stays private; only CloudFront can read.
Q4.
A Route 53 A record usually points at an IP address, but for CloudFront/Beanstalk you use an alias A record. Why?
07
Dynamic Apps — EC2, ECS, and Elastic Beanstalk
The three paths to running server code on AWS, with a concrete recommendation

How to explain to students

EC2: raw VM. You SSH in, install Node, run pm2 start. Maximum control, maximum maintenance.
ECS Fargate: "run my container, don't bother me with VMs". You write a task definition, AWS runs it.
Elastic Beanstalk: "run my code, handle everything". You upload a zip or a Docker image, AWS does the rest.

For first deploys, Beanstalk is the right answer. For containerised apps, ECS Fargate. For learning + customization, EC2. Anything that says "Kubernetes" can wait — you don't need K8s before your first 100 customers.

three-paths.sh
# ─── PATH 1: EC2 (rent a VM) ─────────────────
$ aws ec2 run-instances --image-id ami-0abcd --instance-type t3.micro \
  --key-name my-key --security-group-ids sg-123
$ ssh -i my-key.pem ubuntu@<ec2-ip>
$ sudo apt update && sudo apt install -y nodejs npm
$ git clone <repo> && cd <repo> && npm ci && npm run build
$ pm2 start dist/server.js && pm2 save

# ─── PATH 2: ECS Fargate (containers, no VMs) ─
$ aws ecs register-task-definition \
  --family myapp --requires-compatibilities FARGATE \
  --cpu 256 --memory 512 \
  --container-definitions '[{
    "name":"web","image":"123.dkr.ecr.../myapp:1.0",
    "portMappings":[{"containerPort":3000}]
  }]'
$ aws ecs create-service --cluster prod --service-name myapp \
  --task-definition myapp:1 --desired-count 2

# ─── PATH 3: Elastic Beanstalk (push + go) ───
$ eb init -p node.js-20 myapp --region me-central-1
$ eb create myapp-prod --instance-type t3.small
Creating environment "myapp-prod"...
Application available at myapp-prod.me-central-1.elasticbeanstalk.com
$ eb deploy
Environment update completed. Status: Ready
🛠️
EC2 = control
Use when you need custom OS-level setup, GPUs, or to learn how things work.
📦
ECS Fargate = containers
Best fit if you've already Dockerised the app. No EC2 fleet to manage.
🚀
Beanstalk = velocity
Push code, get HTTPS + load balancer + autoscaling. First-deploy default.
🚫
Skip K8s for now
Eks/k8s is powerful but overkill until you have multiple teams + services.

🎯 Practice Questions

Q1.
A 2-engineer startup has a Dockerised Express app and needs to deploy by Friday. EC2, ECS Fargate, or Beanstalk? Justify in 3 bullets.
Show Answer
ECS Fargate is the best fit, narrowly over Beanstalk:
1. App is already containerised → Fargate runs the same Dockerfile from local dev with no changes.
2. No EC2 fleet to manage — Fargate handles the underlying compute.
3. Per-task pricing scales smoothly from 1 task to 100 without infra rework.

Beanstalk's Docker single-container platform is also fine and slightly simpler to set up — pick it if the team has zero AWS experience and "5 commands to running" is the priority.
Q2.
Beanstalk gives you a domain like myapp-prod.eu-west-1.elasticbeanstalk.com. How do you put it behind app.muzammilbilwani.com with HTTPS?
Q3.
An EC2 instance you SSH'd into is suddenly unreachable. List four things to check before assuming the instance is dead.
💡 Security group, public IP, instance state, ACM cert? (one of those is wrong here.)
08
Environment Variables, Secrets & Deployment Strategies
Twelve-factor config, AWS Parameter Store, and rolling vs blue-green on AWS

How to explain to students

Rule 1: "Code goes in git, config goes in env vars." Anything that changes between dev and prod (database URLs, API keys, log levels) is environment configuration. Inject at runtime, never hard-code.

For non-secret config: env vars or AWS Parameter Store (free, encrypted-at-rest). For secrets: AWS Secrets Manager (~$0.40/secret/month, supports automatic rotation). Beanstalk and ECS can pull these directly into the running app.

For deployment strategies, AWS gives you rolling by default on Beanstalk and ECS, and blue-green via CodeDeploy or "Swap Environment URLs" on Beanstalk.

config + secrets
# Beanstalk env vars (safe for non-secrets)
$ eb setenv NODE_ENV=production LOG_LEVEL=info

# Parameter Store — free, encrypted at rest
$ aws ssm put-parameter \
  --name "/myapp/prod/DB_URL" \
  --type SecureString \
  --value "postgres://user:pass@db.amazonaws.com:5432/app"

# Secrets Manager — rotation-friendly
$ aws secretsmanager create-secret \
  --name myapp/stripe-key \
  --secret-string '{"key":"sk_live_..."}'

# Read in Node.js — fetched once at startup
const ssm = new SSMClient({});
const { Parameter } = await ssm.send(new GetParameterCommand({
  Name: '/myapp/prod/DB_URL', WithDecryption: true
}));
const dbUrl = Parameter.Value;

# Beanstalk rolling deploy (default)
[v1][v1][v1][v1] → [v2][v1][v1][v1] → ... → [v2][v2][v2][v2]

# Beanstalk blue-green via Swap Environment URLs
$ eb create myapp-prod-green --version v2
$ eb swap myapp-prod-blue --destination-name myapp-prod-green
Successfully swapped CNAMEs. Old "blue" is still hot for instant rollback.

🎯 Practice Questions

Q1.
Why is hard-coding DB_URL = "postgres://..." in your application source code a bug, even if you only deploy to one environment?
Q2.
Compare AWS Parameter Store and Secrets Manager. Which is right for a database password that must be rotated every 30 days?
Show Answer
Secrets Manager. It supports automatic rotation — you wire it to a Lambda that updates the database password and the secret in lockstep. Apps re-fetch the secret transparently.

Parameter Store is free + encrypted, but rotation is manual. Use it for non-rotating config (feature flags, log levels, public URLs).

Practical default: "Use Parameter Store for everything; promote to Secrets Manager only when you need rotation or cross-account access."
Q3.
Sketch the Beanstalk "blue-green" flow using eb swap in 4 steps. What happens to the old environment after a successful swap?
Q4.
Your app reads process.env.DB_URL on startup. The value is set in Beanstalk env vars. After a deploy, DB_URL is empty inside the container. List two likely causes.
💡 Container env vars vs. process env vars; Beanstalk env passing.
09
Process & Log Management — CloudWatch, systemd, PM2
Where logs go, who restarts your app when it crashes, and how to find a 3am bug

How to explain to students

A deployed app is only as good as its recovery story. Two questions to answer for every deploy: (1) when the process crashes, who restarts it? (2) when something breaks, where do I read the logs?

On EC2: systemd or PM2 manage the process. Logs flow to journald or PM2's log files, then forwarded to CloudWatch Logs. On Beanstalk + ECS: AWS handles process management; logs go to CloudWatch automatically. Your job is to make the app log to stdout, not files — AWS captures stdout.

systemd unit + CloudWatch agent
# /etc/systemd/system/myapp.service
[Unit]
Description=My Node API
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/srv/myapp
ExecStart=/usr/bin/node dist/server.js
Restart=always  # systemd auto-restarts on crash
RestartSec=5
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

$ sudo systemctl enable --now myapp
$ journalctl -u myapp -f  # tail the logs

# Stream system + app logs to CloudWatch via the agent
$ sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
  -a fetch-config -m ec2 -c file:cwagent.json -s

# Query logs in CloudWatch Insights
$ aws logs start-query --log-group-name /aws/myapp \
  --start-time $(date -d '1 hour ago' +%s) \
  --end-time $(date +%s) \
  --query-string 'fields @timestamp,@message | filter @message like /ERROR/ | sort @timestamp desc | limit 20'
🔁
Restart=always
systemd brings the app back if it crashes. Don't write your own babysitter.
📤
Log to stdout
Twelve-factor: stdout/stderr only. AWS captures it; don't write to files.
🔍
CloudWatch Insights
SQL-like queries over logs. Faster than grepping by hand.
📦
PM2 alternative
Lighter than systemd for Node-only setups. pm2 startup + pm2 save.

🎯 Practice Questions

Q1.
Why is "log to stdout" the modern best practice instead of "log to /var/log/myapp.log"?
Show Answer
Three reasons:
1. Containers are ephemeral. Files inside a container vanish when the container restarts. stdout is captured by the container runtime (Docker / ECS / Beanstalk) and forwarded to a durable log service (CloudWatch).
2. Twelve-factor app: the app shouldn't decide where logs go — that's the platform's job. Decoupling lets you swap log sinks (CloudWatch → Loki → Datadog) without touching code.
3. Multiple instances: 10 EC2 instances writing to 10 separate /var/log files is a debugging nightmare. stdout → CloudWatch unifies them.
Q2.
Write a CloudWatch Insights query to find all log lines containing "ERROR" in the last 30 minutes, sorted newest-first, top 50.
Q3.
A Node app crashes silently. systemd restarts it (because Restart=always). How would you detect that this is happening and alert on it?
💡 CloudWatch metric on systemd unit restarts, or alarm on app uptime.
10
Using AI for AWS — Debugging Errors & Writing IAM Policies
AI is excellent at IAM JSON, AWS error messages, and CloudFormation YAML. Verify before you ship.

How to explain to students

AWS error messages are notorious — "User: arn:aws:iam::… is not authorized to perform … because no identity-based policy allows the … action" is a paragraph saying "you forgot a permission." AI is great at translating these into a fix. It's also great at writing the first draft of an IAM policy from a plain-English description.

The trap: AI confidently invents IAM action names that don't exist (s3:GetAllObjects isn't real). Always verify against the AWS docs before merging. And never paste real ARNs that contain account IDs into public AI tools — even though account IDs are technically not secret, they're useful for an attacker.

AI prompts for AWS
# ❌ Weak
"Write me an IAM policy"

# ✅ Strong
"Write a least-privilege IAM policy for a CI deployer that needs to:
- Push images to ONE specific ECR repo: 'myapp' in eu-west-1
- Update ONE specific ECS service: 'myapp-prod' in cluster 'prod'
- Read parameters under /myapp/prod/* in Parameter Store
- Nothing else.
Use specific ARNs (replace ACCOUNT_ID with the placeholder).
After the policy, list 2 things that could still go wrong."

# Debug-by-AI: paste the EXACT error
User: arn:aws:iam::123:user/deploy is not authorized to perform: s3:GetObject on resource: "arn:aws:s3:::my-bucket/key" because no identity-based policy allows the s3:GetObject action
→ AI prompt: "Translate this IAM error into the missing policy I need to add."

# Verify AI-generated policies in the simulator
$ aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123:user/deploy \
  --action-names s3:PutObject \
  --resource-arns arn:aws:s3:::my-bucket/file.txt
"EvalDecision": "allowed"
ChatGPT Claude Q Developer Verify ARNs simulate-principal-policy

🎯 Practice Questions

Q1.
Take the vague prompt "give me an IAM policy for my Lambda" and rewrite it as a 5-bullet detailed prompt that produces a least-privilege policy.
Q2.
An AI-generated policy uses s3:GetAllObjects. You apply it and the deploy fails. Why?
Show Answer
s3:GetAllObjects doesn't exist. AI hallucinated a plausible-sounding action name. The real action is s3:GetObject (singular), and to "read everything" you broaden the Resource ARN to arn:aws:s3:::bucket/*, not the action.

Verification routine: any unfamiliar IAM action name → search the AWS Service Authorization Reference. If it's not there, the AI invented it.
Q3.
Use the aws iam simulate-principal-policy command to verify whether user "deploy" can ecs:UpdateService on cluster "prod". Sketch the command.
11
Project: Deploy a Portfolio Site to S3 + Route 53 + CloudFront
The capstone — your résumé live at your own domain, under $2/month, with HTTPS and CDN

How to explain to students

Walk through this end-to-end on screen. By the end of this project, every student should have a personal https://<name>.com serving a real portfolio. This is the artefact they list on their CV. Insist on a custom domain — cloudfront.net URLs don't impress recruiters.

deploy.sh — full pipeline
#!/bin/bash
set -euo pipefail

BUCKET="muzammil-portfolio"
DIST_ID="E1A2B3C4D5"

echo "→ Building site"
npm run build

echo "→ Syncing to S3 (with cache headers)"
aws s3 sync ./dist "s3://$BUCKET" \
  --delete \
  --cache-control "public,max-age=31536000,immutable" \
  --exclude "index.html"

# index.html gets short cache so deploys are visible quickly
aws s3 cp ./dist/index.html "s3://$BUCKET/index.html" \
  --cache-control "public,max-age=60"

echo "→ Invalidating CloudFront cache"
aws cloudfront create-invalidation \
  --distribution-id "$DIST_ID" \
  --paths "/index.html"

echo "✅ Deployed to https://muzammilbilwani.com"
🪙
Smart cache headers
Long cache for hashed assets, short cache for index.html — fast loads + visible deploys.
🔒
Bucket private + OAC
Only CloudFront can read — no public bucket policy.
🌍
Custom domain + HTTPS
Route 53 alias + ACM cert in us-east-1.
Targeted invalidation
Only invalidate /index.html, not /* — saves on the free tier limit.
12
Quiz: AWS Services + Deployment Strategies
5 MCQs + 2 fill-in-the-command questions

Sample quiz questions (interactive)

Q1. Which AWS service lets you serve a static portfolio site with a CDN and HTTPS?
A
EC2 + nginx
B
S3 + CloudFront
C
Lambda + API Gateway
D
RDS + ELB
Q2. The CloudFront ACM certificate must be created in which region?
A
us-east-1
B
The same region as the S3 bucket
C
Any region
D
me-central-1
Q3. Which IAM construct is used by an EC2 instance to access AWS APIs without storing long-lived keys?
A
User
B
Group
C
Role (assumed via EC2 instance profile)
D
Federated identity
Q4. The first thing you set up in a new AWS account is...
A
A VPC
B
Root MFA, an IAM user, and a billing alarm
C
An EC2 instance
D
An RDS database
Q5. Which service is the easiest first deploy for a Node.js API with a database?
A
Raw EC2 + manual setup
B
EKS
C
Elastic Beanstalk
D
CloudFormation alone

Fill-in-the-command

Fill 1: Sync the local ./dist folder to bucket my-site while deleting orphaned files.
Fill 2: Invalidate every path in CloudFront distribution E123ABC.
13
Assignment: Deploy a Dynamic App to Elastic Beanstalk
Take a Node.js (or Python/Go) API + Postgres and ship it to a public URL with HTTPS

How to explain to students

Frame this as a real internship task: "Your manager: 'We need this API live behind a custom domain by Friday. Use Beanstalk. Make sure it has HTTPS, env vars for the database, and a working healthcheck.'" This forces them to integrate everything from the prior modules into one shipping artefact.

📋 Assignment Requirements

  • Pick (or write) a Node.js/Python/Go REST API with at least 3 endpoints, including /healthz
  • Provision a free-tier Postgres on AWS RDS (or Aurora Serverless v2 for zero-cost-at-rest)
  • Deploy the app via Elastic Beanstalk on a t3.small (one instance is fine)
  • Pass DB_URL via Beanstalk env vars or Parameter Store — never hard-coded
  • Configure healthcheck path = /healthz in Beanstalk
  • Attach a custom subdomain (api.yourname.com) via Route 53 alias to Beanstalk's CNAME
  • Provision a free TLS cert via ACM and attach to the Beanstalk load balancer (HTTPS only, redirect HTTP → HTTPS)
  • Logs from the app must appear in CloudWatch Logs
  • The app must restart automatically when it crashes (Beanstalk default)
  • Document the deploy in a 1-page README — architecture diagram + cost breakdown + how to redeploy
  • Bonus: GitHub Actions workflow that runs eb deploy on push to main via OIDC (no long-lived AWS keys)
  • Bonus: Tear-down script that removes every resource you created
expected acceptance test
$ curl -i https://api.muzammilbilwani.com/healthz
HTTP/2 200
content-type: application/json
x-served-by: aws-beanstalk
{"status":"ok","db":"connected","commit":"a3f1c2"}

$ aws logs tail /aws/elasticbeanstalk/myapp-prod/var/log/web.stdout.log --since 5m
[2025-04-29T10:24:01Z] GET /healthz 200 12ms
[2025-04-29T10:24:08Z] POST /items 201 86ms
📊
Grading rubric
App live on HTTPS: 30. DB connected via Parameter Store: 20. Healthcheck wired: 15. Logs in CloudWatch: 15. README + diagram: 10. Code quality: 10.
🎯
Common mistakes
ACM cert in wrong region, security group blocks RDS, env var typo, healthcheck returns 404, Beanstalk role missing CloudWatch permission.
💡
Stretch goal
Make it blue-green: deploy a "green" environment, smoke-test it, swap CNAMEs, retire "blue".