βοΈ AWS Solutions Architect Associate
12-week project-based course for the SAA-C03 exam β built for people who already run real infrastructure.
Pass the AWS Certified Solutions Architect β Associate (SAA-C03) exam and leave able to design, deploy, and operate real multi-tier infrastructure on AWS β not just answer multiple-choice questions.
This course assumes you already know Docker, basic Linux, and have run your own servers. It treats AWS as a powerful alternative and complement to what you already do.
Secure Architectures
IAM, encryption, network security, least privilege
Resilient Architectures
Multi-AZ, Auto Scaling, decoupling, fault tolerance
High-Performing Architectures
Right compute, storage, databases, caching
Cost-Optimized Architectures
Pricing models, lifecycle policies, right-sizing
- Do one week at a time. Complete the full lab before moving on.
- Every week ends with a hands-on project that builds on previous work.
- Take the quiz at the end of each week (aim for β₯ 80%).
- Write a short reflection: "How is this different from my home server?"
- Check Cost Explorer weekly and shut down anything idle.
- By Week 12 you will have a real multi-tier app + exam readiness.
| Week | Topic | Covers |
|---|---|---|
| 01 | Cloud Foundations & Account Setup | Root MFA Β· IAM Β· Billing alarms Β· AWS CLI |
| 02 | Compute β Amazon EC2 | Instances Β· Security Groups Β· Docker deploy |
| 03 | Networking β VPC | Custom VPC Β· Public/Private subnets Β· Routing |
| 04 | Storage β S3 | Static hosting Β· Lifecycle Β· Presigned URLs |
| 05 | Databases β RDS & DynamoDB | Private Postgres Β· Security groups Β· NoSQL |
| 06 | IAM Deep Dive & Security | Roles Β· Secrets Manager Β· KMS Β· Least privilege |
| 07 | Elasticity β ALB + Auto Scaling | Load balancer Β· Multi-AZ Β· Health checks |
| 08 | Serverless β Lambda + API Gateway | Functions Β· HTTP APIs Β· Event-driven |
| 09 | Containers β ECR + ECS Fargate | Push images Β· Task definitions Β· Services |
| 10 | Monitoring & Decoupling | CloudWatch Β· SQS Β· Alarms Β· Dashboards |
| 11 | Well-Architected & Cost | 6 pillars Β· Cost Explorer Β· Cleanup plan |
| 12 | Capstone + Exam Prep | Full multi-tier app Β· Practice exams Β· Book it |
π Cloud Foundations & Secure AWS Account Setup
Estimated time: 2.5β4 hours Β· Difficulty: BeginnerβIntermediate Β· Exam Domains: Domain 1 (Secure Architectures) + foundational knowledge
By the end of this lab you will be able to:
- Explain the AWS Shared Responsibility Model
- Secure a root account with MFA
- Create a least-privilege IAM administrative user (for learning)
- Set up billing alarms and budgets so you never get surprised
- Install and correctly configure the AWS CLI with a named profile
- Verify your identity from the command line
Shared Responsibility Model
| Area | AWS Responsibility | Your Responsibility |
|---|---|---|
| Physical security of data centers | β | |
| Hypervisor / host OS | β | |
| Network infrastructure | β | |
| Managed services (RDS, S3, Lambda, etc.) | β (with some config) | Configuration, encryption, access |
| Guest OS, applications, data | β | |
| IAM users, roles, policies | β | |
| Network traffic protection (security groups, NACLs) | β | |
| Encryption of data | Shared | You choose and configure |
Key takeaway: AWS secures of the cloud. You secure in the cloud.
Global Infrastructure Hierarchy
- Region = geographic area. Choose one close to you or your users. Data does not leave a Region unless you explicitly move it.
- Availability Zone (AZ) = one or more discrete data centers with redundant power, networking, and connectivity. Designed to be isolated from failures in other AZs.
- Edge Location = CDN points of presence (CloudFront).
For this entire course we will primarily use one Region (recommend us-east-1 or the Region closest to you). Multi-Region comes later if needed.
Steps (AWS Console)
- Go to aws.amazon.com and sign in (or create a new account).
- Immediately go to the account dropdown (top right) β Security credentials.
- Under Multi-factor authentication (MFA), click Assign MFA device.
- Choose Authenticator app (recommended) or Hardware MFA.
- Scan the QR code with your authenticator app (Google Authenticator, Authy, 1Password, Bitwarden, etc.).
- Enter two consecutive MFA codes.
- Download / save the MFA recovery codes and store them offline (password manager + printed copy in a safe place).
Common Pitfalls
- Skipping MFA "just for now" β do it now.
- Losing recovery codes β you can be locked out permanently.
- Using root for daily work β this is one of the fastest ways to fail a security review (and the exam).
We will create a powerful IAM user for learning. Later weeks will teach you to replace long-lived credentials with roles.
Console Steps
- Search for IAM in the top search bar and open the IAM console.
- In the left navigation, click Users β Create user.
- User name:
teq-admin(orsaa-admin, whatever you prefer). - Check Provide user access to the AWS Management Console.
- Choose I want to create an IAM user.
- Set a strong custom password (or auto-generate). Uncheck "Users must create a new password at next sign-in" only if you want convenience for learning (in real life leave it checked).
- Click Next.
- On the permissions page, choose Attach policies directly.
- Search for and select AdministratorAccess.
- Click Next β Review β Create user.
Important Notes
AdministratorAccessis a managed policy that gives full access. Perfect for learning, dangerous in production. We will tighten later.- After creation, download the
.csvcredentials file if offered, or note the console sign-in URL.
Create Access Keys for CLI (for this learning user)
- Click on the new user β Security credentials tab.
- Under Access keys, click Create access key.
- Use case: Command Line Interface (CLI).
- Check the confirmation box and create.
- Download the
.csvor copy Access Key ID + Secret Access Key now. You will not see the secret again.
This is the single most important cost-control step.
1. Enable Billing Alerts (one-time)
- Click your account name (top right) β Billing and Cost Management.
- In the left menu go to Billing preferences.
- Enable Receive Billing Alerts.
- Save.
2. Create CloudWatch Billing Alarms
- Go to CloudWatch console.
- Make sure you are in us-east-1 (billing metrics only appear here).
- Left menu β Alarms β All alarms β Create alarm.
- Click Select metric β Billing β Total Estimated Charge.
- Select the
USDmetric β Select metric. - Threshold type: Static.
- Whenever
EstimatedChargesis Greater than5. - Configure notification: Create a new SNS topic (e.g.
billing-alerts) and subscribe your email. - Confirm the SNS subscription from your email.
- Name the alarm
Billing-Alarm-5-Dollars. - Create the alarm.
Repeat the process for a second alarm at $20.
3. Create an AWS Budget
- Go to Billing and Cost Management β Budgets.
- Create budget.
- Use template: Zero spend budget or customize.
- Better: Choose Customize (advanced).
- Budget type: Cost budget.
- Set amount to something low (e.g. $10 or $20 per month).
- Scope: All AWS services, all linked accounts (or just this one).
- Add alert thresholds at 50%, 80%, 100%.
- Email recipients: your address.
- Create budget.
Verification: You should receive a confirmation email from SNS and see the alarms in CloudWatch as "OK" or "Insufficient data" initially.
Installation (Linux / macOS / WSL)
# Download curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" # Unzip unzip awscliv2.zip # Install sudo ./aws/install # Verify aws --version
(Windows users: download the MSI from the official AWS CLI page.)
Configure a Named Profile
Never use the default profile for important work. Always use named profiles.
aws configure --profile teq-learning
You will be prompted for:
- AWS Access Key ID β paste from the IAM user
- AWS Secret Access Key β paste from the IAM user
- Default region name β e.g.
us-east-1 - Default output format β
json(recommended)
Verify Everything Works
aws sts get-caller-identity --profile teq-learning
Expected output looks like:
{
"UserId": "AIDA..............",
"Account": "123456789012",
"Arn": "arn:aws:iam::123456789012:user/teq-admin"
}
If you get an error, check:
- Correct Access Key / Secret
- No extra spaces when pasting
- System clock is accurate (STS is time-sensitive)
Optional but Recommended: Environment Variable Shortcut
Add to your ~/.bashrc or ~/.zshrc:
export AWS_PROFILE=teq-learning
Then you can simply run aws sts get-caller-identity.
- Go to Billing and Cost Management β Free Tier.
- Explore what is currently available for your account age.
- Note the difference between Always Free and 12-month Free Tier.
Always Free examples (approximate, check current):
- Lambda: 1M requests + 400,000 GB-seconds / month
- DynamoDB: 25 GB storage + read/write capacity
- S3: 5 GB Standard storage
- CloudFront: 1 TB data transfer out
12-month Free Tier examples:
- EC2: 750 hours of t2.micro / t3.micro (or newer equivalents)
- RDS: 750 hours of db.t2.micro / db.t3.micro
- EBS: 30 GB
You are done with Week 1 when all of the following are true:
- Root account has MFA enabled and recovery codes stored safely
- You can log into the console as the IAM user (not root)
- Two billing alarms ($5 and $20) exist and SNS subscription is confirmed
- At least one AWS Budget is active
aws sts get-caller-identity --profile teq-learningreturns your IAM user ARN- You understand the Shared Responsibility Model and Region/AZ hierarchy
- Quiz score β₯ 80%
Write 3β5 sentences answering:
"How does securing an AWS account differ from securing my home server?"
Common themes: no physical access, blast radius of credentials, shared responsibility, global infrastructure, billing as a first-class concern.
π₯οΈ Compute β Amazon EC2
Estimated time: 3β5 hours Β· Exam Domains: Domain 3 (High-Performing) + Domain 1 (Security groups) + Domain 2 (basic resilience) Β· Prerequisites: Week 1 completed
By the end of this lab you will be able to:
- Choose an appropriate Free Tier eligible instance type and AMI
- Create and secure a key pair
- Launch an EC2 instance with the correct networking and storage settings
- Configure a Security Group using least privilege
- Connect via SSH and bootstrap the instance
- Install Docker and deploy a containerized application
- Understand the difference between Security Groups and NACLs
- Use Elastic IPs and understand their cost implications
- Stop, start, and terminate instances correctly
Instance Types
- t3.micro / t4g.micro (and newer Free Tier equivalents): Burstable performance, good for learning and low-traffic apps.
- Always check the current Free Tier page in your account β eligible types change over time.
- vCPU, memory, and network performance matter more than "bigger is better."
AMI (Amazon Machine Image)
- Pre-configured template for the root volume (OS + software).
- Recommended for this course: Amazon Linux 2023 or Ubuntu 24.04 LTS.
Security Groups vs Network ACLs
| Feature | Security Group | Network ACL |
|---|---|---|
| Level | Instance | Subnet |
| Statefulness | Stateful (return traffic automatic) | Stateless (must allow both directions) |
| Rules | Allow only | Allow and Deny |
| Default | Deny all inbound | Allow all |
For almost everything in this course you will work primarily with Security Groups.
Key Pairs
- Public key is stored by AWS; private key (
.pem) stays with you. - Losing the private key = you cannot SSH into the instance (unless you have another way in).
Elastic IP
- Static public IPv4 address.
- Free while associated with a running instance.
- Charged if allocated but not associated (or associated with a stopped instance in some cases).
Console Steps
- Open the EC2 console.
- Make sure you are in your chosen Region (e.g.
us-east-1). - Left menu β Network & Security β Key Pairs.
- Click Create key pair.
- Name:
teq-saa-key - Key pair type: RSA
- Private key file format:
.pem(for OpenSSH on Linux/macOS/WSL) or.ppkif using PuTTY on Windows. - Click Create key pair.
- The private key downloads automatically. Move it to a safe location and never commit it to Git.
Secure the Key File (Linux / macOS / WSL)
chmod 400 ~/path/to/teq-saa-key.pem
Common Pitfalls
- Creating the key pair in the wrong Region (key pairs are Region-specific).
- Losing the
.pemfile. - Setting permissions too open (
chmod 400is required).
- EC2 console β Network & Security β Security Groups.
- Create security group.
- Name:
teq-web-sg - Description:
Web tier - SSH from my IP only, HTTP/HTTPS open - VPC: default VPC for now (we replace this in Week 3).
- Inbound rules:
- Type: SSH | Port: 22 | Source: My IP (the console will auto-fill your current public IP)
- Type: HTTP | Port: 80 | Source:
0.0.0.0/0(temporary for testing β we will tighten later) - Type: HTTPS | Port: 443 | Source:
0.0.0.0/0(optional)
- Outbound rules: leave the default (All traffic).
- Create the security group.
0.0.0.0/0 is one of the most common security mistakes. Always restrict to your IP (or a bastion / SSM later).- EC2 console β Instances β Launch instance.
- Name:
hello-api-ec2 - Application and OS Images (AMI):
- Quick Start β Amazon Linux 2023 (or Ubuntu Server 24.04 LTS)
- Confirm it shows "Free tier eligible"
- Instance type:
t3.microort4g.micro(or whatever is currently Free Tier eligible in your account) - Key pair: Select
teq-saa-key - Network settings:
- VPC: default
- Subnet: No preference (or pick one)
- Auto-assign public IP: Enable
- Firewall (security groups): Select existing β
teq-web-sg
- Configure storage: 8β30 GB gp3 is fine. Free Tier includes 30 GB.
- Advanced details β User data (optional but recommended):
#!/bin/bash # Amazon Linux 2023 dnf update -y dnf install -y docker systemctl enable docker systemctl start docker usermod -aG docker ec2-user # Simple health check page (optional) mkdir -p /var/www/html echo "<h1>hello-api is alive on EC2</h1>" > /var/www/html/index.html
For Ubuntu, replace dnf with apt-get and the user is usually ubuntu.
- Click Launch instance.
Find the Public IP
In the EC2 console, select the instance β copy the Public IPv4 address.
SSH Connection
ssh -i teq-saa-key.pem ec2-user@<PUBLIC_IP>
(Use ubuntu@ if you chose Ubuntu.)
First connection will ask you to confirm the host key fingerprint β type yes.
Verify Docker
docker --version docker ps
If the usermod in user data worked, you may need to log out and back in (or use sudo docker).
Deploy a Simple Container
# Quick test with nginx sudo docker run -d --name web -p 80:80 nginx:alpine # Or if you have your own hello-api image # sudo docker run -d --name hello-api -p 80:3000 your-image:tag
Test from Your Browser / curl
curl http://<PUBLIC_IP>
You should see the nginx welcome page or your application response.
- EC2 β Network & Security β Elastic IPs.
- Allocate Elastic IP address.
- Select the new address β Actions β Associate Elastic IP address.
- Choose your instance and private IP.
- Associate.
Now the public IP will survive stop/start cycles.
- Stop: Powers off the instance. EBS volumes persist. Public IP (if not Elastic) is released. You stop being charged for instance hours (still charged for EBS storage).
- Start: Powers on again. New public IP unless you used an Elastic IP.
- Terminate: Deletes the instance. Root volume is deleted by default (unless you changed the setting). This is permanent.
Practice stopping and starting the instance at least once.
# From your local machine
aws ec2 describe-instances \
--filters "Name=tag:Name,Values=hello-api-ec2" \
--query "Reservations[].Instances[].{ID:InstanceId,State:State.Name,IP:PublicIpAddress}" \
--profile teq-learning
- Instance state is
running - You can SSH using the key pair
- Security group allows SSH only from your IP
- Application responds on port 80
- Docker is installed and running
- Wrong username β
ec2-user(Amazon Linux),ubuntu(Ubuntu),admin(Debian), etc. - Security group missing the application port.
- SSH open to the world (
0.0.0.0/0). - Key pair in the wrong Region.
- Leaving the instance running 24/7 when you are not using it β burns Free Tier hours.
- User data scripts that fail silently β always check
/var/log/cloud-init-output.logon the instance.
You are finished with Week 2 when all of the following are true:
- Key pair created and secured with
chmod 400 - Security group restricts SSH to your IP only
- EC2 instance is running and reachable via SSH
- Docker is installed
- A containerized application (or nginx) responds on port 80
- You have practiced stopping and starting the instance
- Quiz score β₯ 80%
- Instance is stopped when you are not actively using it
Write 4β6 sentences:
"How does running this application on EC2 differ from running the same Docker container on my home server? What is better, what is worse, and what is simply different?"
πΊοΈ Networking β Amazon VPC
Estimated time: 3β5 hours Β· Exam Domains: Domain 1 (Secure Architectures β network security) + Domain 2 (Resilient Architectures) Β· Prerequisites: Week 2 completed
- Design and create a custom VPC with a sensible CIDR plan
- Create public and private subnets across multiple Availability Zones
- Attach an Internet Gateway and configure route tables correctly
- Understand the difference between public and private subnets
- Optionally set up a NAT Gateway or NAT Instance for private subnet outbound access
- Launch (or migrate) an EC2 instance into the custom VPC
- Stop using the default VPC for new resources
CIDR Planning
- VPC: typically
/16(e.g.10.0.0.0/16) gives 65,536 addresses β plenty for learning. - Subnets:
/24(256 addresses) is a common, easy size. - Leave room to grow. Do not use overlapping ranges with other networks you care about.
Public vs Private Subnet
- Public subnet: Has a route table entry for
0.0.0.0/0that points to an Internet Gateway. Resources can have public IPs. - Private subnet: No direct route to the Internet Gateway. Outbound internet access (if needed) goes through a NAT Gateway or NAT Instance in a public subnet.
Core Components
- Internet Gateway (IGW): Horizontally scaled, redundant, and highly available VPC component that allows communication between the VPC and the internet.
- Route Table: Controls where network traffic is directed.
- NAT Gateway: Managed NAT service (costs money). Allows private subnet resources to initiate outbound internet connections while remaining unreachable from the internet.
- NAT Instance: Self-managed alternative (cheaper for pure learning, more operational overhead).
- Open the VPC console.
- Your VPCs β Create VPC.
- Choose VPC only (we will create subnets manually for learning).
- Name tag:
teq-saa-vpc - IPv4 CIDR:
10.0.0.0/16 - Tenancy: Default
- Create VPC.
After creation, note the VPC ID.
Enable DNS hostnames (important for later): Select the VPC β Actions β Edit VPC settings β Enable DNS hostnames β Save.
Create four subnets:
| Name | CIDR | Availability Zone | Type |
|---|---|---|---|
| public-a | 10.0.1.0/24 | us-east-1a (or your AZ a) | Public |
| public-b | 10.0.2.0/24 | us-east-1b | Public |
| private-a | 10.0.11.0/24 | us-east-1a | Private |
| private-b | 10.0.12.0/24 | us-east-1b | Private |
Steps for each:
- VPC console β Subnets β Create subnet.
- Select your VPC.
- Fill in name, AZ, and CIDR.
- Create.
Repeat until all four exist.
- VPC console β Internet gateways β Create internet gateway.
- Name:
teq-saa-igw - Create.
- Select the new IGW β Actions β Attach to VPC β choose
teq-saa-vpc.
- Route tables β Create route table.
- Name:
teq-saa-public-rt - VPC:
teq-saa-vpc - Create.
- Select the route table β Routes tab β Edit routes β Add route:
- Destination:
0.0.0.0/0 - Target: Internet Gateway β select
teq-saa-igw
- Destination:
- Save.
- Subnet associations tab β Edit subnet associations β select
public-aandpublic-bβ Save.
Your public subnets now have a path to the internet.
- Create another route table:
teq-saa-private-rt - Associate it with
private-aandprivate-b.
Option A β No NAT (simplest for pure learning)
Private subnets will have no outbound internet access. This is fine if your private resources do not need to download packages or call external APIs.
Option B β NAT Gateway (realistic but costs money)
- Allocate an Elastic IP.
- Create a NAT Gateway in one of the public subnets and associate the Elastic IP.
- In the private route table, add route
0.0.0.0/0β NAT Gateway. - Remember to delete the NAT Gateway when you are finished testing β it has hourly + data processing charges.
Option C β NAT Instance (cheaper learning alternative)
Launch a small EC2 instance in a public subnet, disable source/destination check, configure IP forwarding and iptables (or use a pre-built NAT AMI), and point the private route table at that instance.
For most people learning SAA-C03, Option A or a temporary NAT Gateway is sufficient.
Recommended: Launch a new instance in the custom VPC
- EC2 β Launch instance.
- Same AMI and instance type as Week 2.
- Network settings:
- VPC:
teq-saa-vpc - Subnet:
public-a(or public-b) - Auto-assign public IP: Enable
- Security group: Create a new one or copy rules from
teq-web-sg(you will need to recreate the security group in the new VPC).
- VPC:
- Key pair: same as before.
- Launch and deploy your application again (Docker + container).
Why recreate the Security Group?
Security groups are VPC-scoped. You cannot use a security group from the default VPC inside your new VPC.
From your local machine:
# Confirm instance is in the correct VPC and subnet
aws ec2 describe-instances \
--filters "Name=tag:Name,Values=hello-api-ec2*" \
--query "Reservations[].Instances[].{ID:InstanceId,VpcId:VpcId,SubnetId:SubnetId,PublicIP:PublicIpAddress,State:State.Name}" \
--profile teq-learning
- Instance has a public IP
- You can SSH to it
- You can reach the application on port 80
- The instance is not in the default VPC
On the instance itself, confirm outbound internet works (if you expect it to):
curl -I https://aws.amazon.com
- Forgetting to attach the Internet Gateway.
- Associating the public route table with private subnets (or vice versa).
- Creating all subnets in the same Availability Zone (defeats multi-AZ design).
- Trying to use a security group from a different VPC.
- Leaving a NAT Gateway running when not needed β unexpected charges.
- Not enabling DNS hostnames on the VPC (causes problems with some AWS services later).
- Custom VPC
teq-saa-vpcexists with CIDR10.0.0.0/16 - At least two public and two private subnets in different AZs
- Internet Gateway attached and public route table configured
- Application instance running inside the custom VPC (public subnet)
- You can SSH and reach the application
- Default VPC is no longer used for new resources
- Quiz β₯ 80%
"How does this VPC design compare to the networking on my home server or mini-PC setup? What concepts transfer directly, and what is completely new?"
ποΈ Storage β Amazon S3
Estimated time: 2.5β4 hours Β· Exam Domains: Domain 3 (High-Performing β storage) + Domain 4 (Cost Optimization) + Domain 1 (bucket policies) Β· Prerequisites: Weeks 1β3 completed
- Create and configure an S3 bucket with appropriate settings
- Host a static website on S3 (or S3 + CloudFront)
- Apply versioning and lifecycle policies
- Generate and use presigned URLs
- Understand bucket policies vs IAM policies
- Choose the correct storage class for different access patterns
- Decide when to use S3 vs EBS vs EFS
Storage Classes (High-Level Decision Framework)
- S3 Standard: Frequent access, low latency. Default.
- S3 Intelligent-Tiering: Automatic cost optimization when access patterns are unknown.
- S3 Standard-IA / One Zone-IA: Infrequent access, lower storage cost, retrieval fee.
- S3 Glacier Instant Retrieval / Flexible Retrieval / Deep Archive: Archival, increasing retrieval times and decreasing cost.
S3 vs EBS vs EFS
| Need | Best Choice |
|---|---|
| Object storage, static assets, backups, data lake | S3 |
| Block storage for a single EC2 instance | EBS |
| Shared file system across multiple instances | EFS |
Bucket Policies vs IAM Policies
- IAM policies: Attached to users, groups, or roles. "Who can do what."
- Bucket policies: Attached to the bucket itself (resource-based). Can grant cross-account access or public access under controlled conditions.
- Open the S3 console.
- Create bucket.
- Bucket name: must be globally unique. Example:
teq-landing-jude-2026orteq-saa-static-YOURNAME. - Region: same as your other resources (e.g. us-east-1).
- Object Ownership: ACLs disabled (recommended).
- Block Public Access: For a static website you will need to adjust this carefully later. For now you can leave it blocked and use CloudFront, or unblock only what is required.
- Versioning: Enable.
- Default encryption: SSE-S3 (AES-256) is fine.
- Create bucket.
Prepare Content
Create a simple index.html:
<!DOCTYPE html>
<html>
<head>
<title>Teq Project Landing</title>
<style>
body { font-family: system-ui; max-width: 800px; margin: 40px auto; padding: 0 20px; }
h1 { color: #1F4E79; }
</style>
</head>
<body>
<h1>Hello from S3</h1>
<p>This static site is hosted on Amazon S3 as part of the SAA-C03 course.</p>
</body>
</html>
You can also upload a real landing page from one of your Teq projects.
Upload
- Open the bucket β Upload β Add files β Upload
index.html(and any CSS/JS/assets).
Enable Static Website Hosting
- Bucket β Properties tab β scroll to Static website hosting.
- Edit β Enable.
- Hosting type: Host a static website.
- Index document:
index.html - Error document:
index.html(or a custom 404 page). - Save.
You will receive a bucket website endpoint URL (looks like http://bucket-name.s3-website-region.amazonaws.com).
Make Objects Public (if using website hosting directly)
- Permissions tab β Block public access β Edit β uncheck the relevant settings (or allow public policies).
- Create a bucket policy that allows
s3:GetObjectforPrincipal: "*"on the objects you want public.
Example bucket policy (adjust the bucket name):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::YOUR-BUCKET-NAME/*"
}
]
}
Test the website endpoint in a browser.
- Bucket β Management tab β Lifecycle rules β Create lifecycle rule.
- Rule name:
expire-old-versionsortransition-to-ia. - Rule scope: Apply to all objects (or use a prefix/filter).
- Example actions:
- Transition current versions to Intelligent-Tiering after 30 days (optional).
- Permanently delete noncurrent versions after 90 days.
- Or simply expire delete markers and incomplete multipart uploads.
- Create the rule.
Presigned URLs give temporary access to private objects without making the bucket public.
Using the AWS CLI
# Upload a private test file echo "secret content" > private-test.txt aws s3 cp private-test.txt s3://YOUR-BUCKET-NAME/private-test.txt --profile teq-learning # Generate a presigned URL valid for 15 minutes (900 seconds) aws s3 presign s3://YOUR-BUCKET-NAME/private-test.txt --expires-in 900 --profile teq-learning
Copy the URL and open it in a browser or with curl. After it expires, access should be denied.
- Open the CloudFront console β Create distribution.
- Origin domain: select your S3 bucket (use the REST endpoint, not the website endpoint, if using Origin Access Control).
- For modern setups, use Origin Access Control (OAC) so the bucket can remain private.
- Default root object:
index.html - Viewer protocol policy: Redirect HTTP to HTTPS.
- Create distribution.
- After the distribution deploys (can take a few minutes), use the CloudFront domain name to access your site over HTTPS.
This pattern (private S3 + CloudFront + OAC) is the preferred production approach and appears frequently on the exam.
- Bucket exists with versioning enabled
- Static content is reachable via the website endpoint or CloudFront
- Lifecycle rule is active
- Presigned URL works and then expires
- You understand the difference between the website endpoint and the REST endpoint
- Bucket name already taken (names are global).
- Forgetting to set the index document.
- Block Public Access still enabled when trying to use the website endpoint directly.
- Using the wrong endpoint type with CloudFront.
- Making the entire bucket public instead of only the necessary objects.
- Leaving versioning disabled and then accidentally overwriting important files.
- Static site is publicly reachable
- Versioning is enabled
- At least one lifecycle rule exists
- Presigned URL demonstrated
- Quiz β₯ 80%
"How does S3 compare to storing static files on my home server or using a traditional web host? What becomes easier and what requires a different mental model?"
ποΈ Databases β Amazon RDS & DynamoDB
Estimated time: 3β5 hours Β· Exam Domains: Domain 2 (Resilient) + Domain 3 (High-Performing) + Domain 1 (network security for data tier) Β· Prerequisites: Custom VPC from Week 3, working application from Week 2/3
- Create a DB subnet group using private subnets
- Launch an Amazon RDS Postgres instance in private subnets only
- Lock down the database security group so only the application tier can connect
- Connect your application to RDS and verify read/write
- Understand Multi-AZ, automated backups, and snapshots
- Know when to choose DynamoDB instead of a relational database
- Map your existing Supabase / self-hosted Postgres usage to AWS options
RDS
- Managed relational database service (Postgres, MySQL, MariaDB, Oracle, SQL Server, Aurora).
- You manage schema, queries, and optimization; AWS manages patching, backups, hardware, Multi-AZ failover, etc.
Multi-AZ
- Synchronous standby in a different Availability Zone.
- Automatic failover on infrastructure failure.
- Primary use case: high availability, not read scaling (use Read Replicas for that).
DynamoDB
- Fully managed NoSQL key-value and document database.
- Single-digit millisecond performance at any scale.
- On-demand or provisioned capacity modes.
- Partition key (required) + optional sort key.
Decision Framework (Simplified)
- Structured data, complex queries, joins, transactions β RDS / Aurora
- Simple key-value or document access patterns, extreme scale, unpredictable traffic β DynamoDB
- Open the RDS console.
- Left menu β Subnet groups β Create DB subnet group.
- Name:
teq-saa-db-subnets - Description: Private subnets for RDS
- VPC:
teq-saa-vpc - Add the two private subnets (
private-aandprivate-b) across different AZs. - Create.
- EC2 console β Security Groups β Create.
- Name:
teq-rds-sg - VPC:
teq-saa-vpc - Inbound rule:
- Type: PostgreSQL
- Port: 5432
- Source: the security group of your application instance (e.g.
teq-web-sg)
- Create.
0.0.0.0/0.- RDS console β Databases β Create database.
- Creation method: Standard create.
- Engine: PostgreSQL (choose a recent version).
- Templates: Free tier (if available) or Dev/Test.
- Settings:
- DB instance identifier:
teq-saa-postgres - Master username:
postgres(or your preferred admin user) - Master password: generate a strong one and store it securely (we will move it to Secrets Manager in Week 6)
- DB instance identifier:
- Instance configuration: Free Tier eligible class (e.g.
db.t3.microor current equivalent). - Storage: gp3 or gp2, 20 GB is fine. Disable storage autoscaling for learning if you want predictable cost.
- Connectivity:
- VPC:
teq-saa-vpc - DB subnet group:
teq-saa-db-subnets - Public access: No
- VPC security group: choose
teq-rds-sg
- VPC:
- Additional configuration:
- Initial database name:
helloapi(or whatever your app expects) - Backup retention: 7 days is fine
- Enable encryption if desired
- Initial database name:
- Create database.
Wait until the status becomes Available (can take 5β10 minutes).
- In the RDS console, select the database and copy the Endpoint.
- SSH into your EC2 application instance.
- Update your application's connection configuration:
# Example environment variables export DB_HOST="<RDS-ENDPOINT>" export DB_PORT="5432" export DB_NAME="helloapi" export DB_USER="postgres" export DB_PASSWORD="<your-password>"
Or update your .env, Docker Compose file, or application config.
- Restart your application / container.
- Run migrations if needed and perform a simple read/write test.
Test Connectivity from the Instance
# Install client if needed (Amazon Linux) sudo dnf install -y postgresql15 psql -h <RDS-ENDPOINT> -U postgres -d helloapi
If you can connect from the EC2 instance but not from your laptop, that is expected and correct (private subnet + security group).
- RDS console β select the database β Actions β Take snapshot.
- Give it a name and wait for completion.
- Explore automated backups (RDS creates them automatically within the retention period).
- Note that restoring from a snapshot creates a new database instance.
- DynamoDB console β Create table.
- Table name:
teq-feature-flagsorteq-sessions. - Partition key:
pk(String). - Settings: Default (on-demand capacity is fine for learning).
- Create.
- Explore putting and getting items via the console or AWS CLI:
aws dynamodb put-item \
--table-name teq-feature-flags \
--item '{"pk": {"S": "dark-mode"}, "enabled": {"BOOL": true}}' \
--profile teq-learning
aws dynamodb get-item \
--table-name teq-feature-flags \
--key '{"pk": {"S": "dark-mode"}}' \
--profile teq-learning
- RDS instance is in
Availablestate - It is in private subnets only (Publicly accessible = No)
- Security group allows 5432 only from the application security group
- Application successfully connects and can read/write
- You have taken at least one manual snapshot
- Making the RDS instance publicly accessible.
- Opening port 5432 to the world.
- Forgetting to create a DB subnet group that spans at least two AZs.
- Using the wrong security group or forgetting to reference the application security group as the source.
- Hard-coding the master password in application code or Docker environment variables that get committed.
- Leaving the RDS instance running 24/7 when not needed (it costs money even when idle).
- RDS Postgres running in private subnets
- Security group correctly restricts access
- Application connected and functional
- At least one snapshot taken
- Quiz β₯ 80%
"How does RDS compare to running Postgres yourself (or using Supabase) on your home server? What do you gain and what do you give up?"
π IAM Deep Dive & Security
Estimated time: 3β4.5 hours Β· Exam Domains: Domain 1 (Design Secure Architectures) β heaviest weighted domain Β· Prerequisites: Working EC2 + RDS from previous weeks
- Create and attach an IAM Role to an EC2 instance (instance profile)
- Remove long-lived access keys from the instance
- Store database credentials and other secrets in AWS Secrets Manager
- Update the application to retrieve secrets at runtime using the AWS SDK or CLI
- Understand identity-based vs resource-based policies and policy evaluation logic
- Enable encryption at rest with KMS where appropriate
- Apply the principle of least privilege in practice
IAM Roles for EC2
- An instance profile is a container for an IAM role that you can attach to an EC2 instance.
- Applications on the instance can obtain temporary credentials automatically via the Instance Metadata Service (IMDS).
- This is strongly preferred over embedding access keys.
Secrets Manager vs Systems Manager Parameter Store
| Feature | Secrets Manager | Parameter Store (Standard) |
|---|---|---|
| Automatic rotation | Yes (for supported services) | No |
| Cost | Per secret + API calls | Free tier available |
| Cross-account | Easier | Possible but more work |
| Typical use | Database credentials, API keys | Config values, feature flags |
For database credentials, Secrets Manager is the better teaching choice.
Policy Evaluation Logic (Critical for the Exam)
- Explicit Deny always wins.
- If no explicit Deny, then at least one explicit Allow is required.
- Otherwise the request is denied (default deny).
- IAM console β Roles β Create role.
- Trusted entity type: AWS service.
- Use case: EC2.
- Click Next.
- Permissions: for now attach
SecretsManagerReadWrite(or a more restrictive custom policy later) and any other permissions your app needs (e.g. S3 read if required). - Role name:
teq-ec2-app-role - Create role.
- EC2 console β select your application instance.
- Actions β Security β Modify IAM role.
- Select
teq-ec2-app-role. - Update IAM role.
Alternatively, you can specify the instance profile at launch time via a Launch Template (preferred for Auto Scaling later).
Verify the Role Is Available on the Instance
SSH into the instance and run:
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/ # Should return the role name curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/teq-ec2-app-role # Should return temporary AccessKeyId, SecretAccessKey, and Token
- Secrets Manager console β Store a new secret.
- Secret type: Credentials for Amazon RDS database (or Other type of secret if you prefer full control).
- If using the RDS helper: select your RDS instance and it can populate the connection details.
- Or choose Other type of secret and enter key-value pairs:
username = postgres password = <your-strong-password> host = <RDS-endpoint> port = 5432 dbname = helloapi
- Secret name:
teq/saa/db-credentials - Leave rotation disabled for now (you can enable it later).
- Store the secret.
If you attached the broad SecretsManagerReadWrite managed policy, you already have access. For better practice:
- Create a custom IAM policy that allows only
secretsmanager:GetSecretValueon the specific secret ARN. - Attach that policy to
teq-ec2-app-role.
Example policy document:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "secretsmanager:GetSecretValue",
"Resource": "arn:aws:secretsmanager:REGION:ACCOUNT:secret:teq/saa/db-credentials-*"
}
]
}
On the EC2 instance, you can retrieve the secret with the AWS CLI (now that the role is attached):
aws secretsmanager get-secret-value \ --secret-id teq/saa/db-credentials \ --query SecretString \ --output text
In application code (Node.js example using AWS SDK v3):
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
const client = new SecretsManagerClient({ region: "us-east-1" });
const response = await client.send(
new GetSecretValueCommand({ SecretId: "teq/saa/db-credentials" })
);
const secret = JSON.parse(response.SecretString);
// Use secret.username, secret.password, etc.
Update your application so it no longer relies on hardcoded environment variables for the database password.
Restart the application and verify it still connects successfully.
- Ensure the application works with the IAM role + Secrets Manager.
- Delete any access key files,
.envfiles containing secrets, or environment variables that held the database password. - Confirm that the instance itself no longer has permanent AWS credentials configured for the application.
- For RDS: when creating or modifying the instance, enable encryption (you may need to create a new encrypted instance from a snapshot if the original was unencrypted).
- For S3: bucket properties β Default encryption β enable SSE-S3 or SSE-KMS.
- Explore customer managed KMS keys if you want more control (key policies, rotation, etc.).
- IAM role is attached to the EC2 instance
- Instance can retrieve the secret via the role (no long-lived keys required)
- Application successfully connects to RDS using the retrieved secret
- No plaintext database password remains on the instance
- Encryption is enabled on sensitive resources where appropriate
- Forgetting that the role must be attached before the application tries to use it.
- Using the older Instance Metadata Service version 1 without considering IMDSv2 (more secure).
- Granting
*on Secrets Manager instead of the specific secret ARN. - Leaving the original access keys active on the IAM user after switching to roles.
- Not handling secret retrieval errors gracefully in application code.
- EC2 instance uses an IAM role (no long-lived keys for the app)
- Database credentials stored in Secrets Manager
- Application retrieves secrets at runtime
- Plaintext secrets removed from the instance
- Quiz β₯ 80%
"How does managing secrets and credentials on AWS differ from how you handle them on your home server or in Docker Compose files?"
βοΈ Elasticity & Resilience β ALB + Auto Scaling
Estimated time: 3.5β5 hours Β· Exam Domains: Domain 2 (Resilient Architectures) β heavily tested Β· Prerequisites: Working application on EC2 inside the custom VPC
- Create a Launch Template from your current configuration
- Create an Auto Scaling Group that spans multiple Availability Zones
- Create an Application Load Balancer and Target Group
- Configure health checks so unhealthy instances are replaced
- Restrict security groups so only the ALB can reach the application instances
- Demonstrate that terminating an instance causes automatic replacement
- Understand the difference between ALB, NLB, and Classic Load Balancer
Elastic Load Balancing Types
- Application Load Balancer (ALB): Layer 7 (HTTP/HTTPS), content-based routing, ideal for most web applications.
- Network Load Balancer (NLB): Layer 4 (TCP/UDP), ultra-high performance, static IPs, preserves source IP.
- Gateway Load Balancer: For virtual appliances (firewalls, etc.).
For this course we use ALB.
Auto Scaling Group (ASG)
- Maintains a desired number of healthy instances.
- Can scale out/in based on metrics (CPU, request count, custom metrics).
- Works with Launch Templates (preferred) or Launch Configurations (legacy).
Health Checks
- EC2 status checks (hypervisor level).
- ELB health checks (application level β you define the path and expected response).
- EC2 console β Launch Templates β Create launch template.
- Name:
teq-app-lt - Template version description:
v1 - initial - AMI: same as your current working instance (or create an AMI from it first for cleanliness).
- Instance type: Free Tier eligible type.
- Key pair: your existing key.
- Network settings: do not specify a subnet here if the ASG will choose subnets. You can leave security groups to be specified later or include a base one.
- Advanced details β IAM instance profile: select
teq-ec2-app-role(from Week 6). - User data: include the same bootstrap script you used before (Docker install, etc.).
- Create launch template.
- EC2 console β Target Groups β Create target group.
- Target type: Instances.
- Name:
teq-app-tg - Protocol: HTTP | Port: 80 (or whatever port your app listens on).
- VPC:
teq-saa-vpc - Health check path:
/or/health(make sure your app returns 200 on this path). - Advanced health check settings: Healthy threshold 2β3, interval 30s is fine for learning.
- Create.
- On the next screen you can register targets later (the ASG will do this automatically).
- EC2 console β Load Balancers β Create load balancer β Application Load Balancer.
- Name:
teq-app-alb - Scheme: Internet-facing.
- IP address type: IPv4.
- Network mapping: select your VPC and at least two public subnets in different AZs.
- Security groups: create a new one (
teq-alb-sg) that allows inbound HTTP (80) and HTTPS (443) from0.0.0.0/0. - Listeners: HTTP on port 80 β forward to
teq-app-tg. - Create load balancer.
Your application instances should no longer accept traffic directly from the internet on the application port.
- Edit the application security group (
teq-web-sgor equivalent):- Remove the inbound rule that allows HTTP/HTTPS from
0.0.0.0/0. - Add an inbound rule: HTTP (or your app port) | Source = the ALB security group (
teq-alb-sg).
- Remove the inbound rule that allows HTTP/HTTPS from
- Keep SSH restricted to your IP if you still need direct access for debugging.
Now only the load balancer can talk to the instances on the application port.
- EC2 console β Auto Scaling Groups β Create Auto Scaling group.
- Name:
teq-app-asg - Launch template: select
teq-app-lt(latest version). - Network: VPC =
teq-saa-vpc. Select the public subnets (or private if you have NAT and want instances private). - Load balancing: Attach to an existing load balancer β choose the target group
teq-app-tg. - Health checks: Enable ELB health checks. Set health check grace period (e.g. 300 seconds) so new instances have time to start before being considered unhealthy.
- Group size: Desired 1, Minimum 1, Maximum 3.
- Scaling policies: for now you can skip or add a simple target tracking policy on Average CPU Utilization (target 50β70%).
- Create.
Wait until the ASG shows 1 healthy instance in service and the target group shows the instance as healthy.
- Note the DNS name of the ALB (e.g.
teq-app-alb-xxxxx.us-east-1.elb.amazonaws.com). - Confirm you can reach your application via the ALB DNS name.
- In the EC2 console, terminate the running instance that belongs to the ASG.
- Watch the Auto Scaling Group activity history β it should launch a replacement instance automatically.
- After a few minutes the new instance should become healthy and start receiving traffic.
This is the core resilience demonstration.
# Check ASG status aws autoscaling describe-auto-scaling-groups \ --auto-scaling-group-names teq-app-asg \ --profile teq-learning # Check target health aws elbv2 describe-target-health \ --target-group-arn <your-tg-arn> \ --profile teq-learning
- ALB DNS name serves the application
- Only the ALB security group can reach the app port on instances
- Terminating an instance causes a replacement to be launched
- Target group shows healthy targets
- Putting the ALB in private subnets (it needs public subnets for an internet-facing load balancer).
- Forgetting to update the instance security group so only the ALB can reach it.
- Health check path that returns something other than 200.
- Health check grace period too short β instances marked unhealthy before they finish starting.
- Launch template missing the IAM role or user data.
- Leaving Desired Capacity > 0 when you are finished for the day β ongoing cost.
- Launch Template created
- Target Group + ALB working
- Auto Scaling Group maintains desired capacity across AZs
- Security groups correctly restrict traffic to ALB β instances
- Instance termination triggers automatic replacement
- Quiz β₯ 80%
"How does Auto Scaling + Load Balancing change the operational model compared to running a single container or VM on my home server?"
β‘ Serverless β Lambda + API Gateway
Estimated time: 2.5β4 hours Β· Exam Domains: Domain 2 (Resilient β decoupling) + Domain 3 (High-Performing) + Domain 4 (Cost) Β· Prerequisites: Basic familiarity with one programming language (Node.js, Python, or Go recommended)
- Create and deploy a Lambda function
- Understand execution roles, environment variables, and basic configuration
- Expose the function via an HTTP API (API Gateway)
- Test the endpoint and observe cold starts
- Know when serverless is a better fit than EC2 or containers for specific workloads
- Understand Lambda limits and pricing model at a high level
Lambda
- Event-driven, fully managed compute.
- You upload code; AWS handles the servers, scaling, and availability.
- Billed for request count + duration (GB-seconds).
- Cold starts occur when a new execution environment is created.
API Gateway
- HTTP APIs: Lower cost, lower latency, simpler β preferred for most new workloads.
- REST APIs: More features (API keys, usage plans, request validation, etc.).
When Serverless Wins for You
- Infrequent or spiky traffic (webhooks, scheduled tasks, lightweight utility endpoints).
- You want zero server management.
- Cost scales to near zero when idle.
- IAM β Roles β Create role.
- Trusted entity: AWS service β Lambda.
- Attach basic permissions:
AWSLambdaBasicExecutionRole(for CloudWatch Logs). - If the function needs other AWS services (S3, DynamoDB, Secrets Manager, etc.), add those permissions now or later.
- Role name:
teq-lambda-role - Create.
- Lambda console β Create function.
- Author from scratch.
- Function name:
teq-helloor something useful from your real apps (e.g. a calculation endpoint). - Runtime: Node.js 20.x, Python 3.12, or your preferred supported runtime.
- Architecture: x86_64 or arm64.
- Execution role: Use the existing role
teq-lambda-role. - Create function.
Example Node.js Handler
exports.handler = async (event) => {
console.log("Event:", JSON.stringify(event));
const name = event.queryStringParameters?.name || "World";
return {
statusCode: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
message: `Hello, ${name}! From Lambda.`,
requestId: event.requestContext?.requestId
})
};
};
Example Python Handler
import json
def lambda_handler(event, context):
print("Event:", json.dumps(event))
name = "World"
if event.get("queryStringParameters") and "name" in event["queryStringParameters"]:
name = event["queryStringParameters"]["name"]
return {
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": json.dumps({
"message": f"Hello, {name}! From Lambda.",
"requestId": context.aws_request_id
})
}
Deploy the code (click Deploy if editing in the console).
- In the Lambda console, go to the Test tab.
- Create a test event (API Gateway AWS Proxy style or a simple event).
- Invoke and confirm the response and logs in CloudWatch.
- API Gateway console β Create API β HTTP API β Build.
- Add integration β Lambda β select your function.
- API name:
teq-http-api - Configure routes:
- Method:
GET - Path:
/hello - Integration: your Lambda function
- Method:
- Configure stages: default
$defaultstage is fine (auto-deploy). - Create.
After creation, note the Invoke URL (looks like https://abcdef123.execute-api.us-east-1.amazonaws.com).
curl "https://YOUR-API-ID.execute-api.us-east-1.amazonaws.com/hello" curl "https://YOUR-API-ID.execute-api.us-east-1.amazonaws.com/hello?name=Jude"
You should receive the JSON response from Lambda.
- Invoke the endpoint after waiting several minutes (or use a new test).
- Check the duration in the response or in CloudWatch Logs.
- CloudWatch β Log groups β
/aws/lambda/your-function-nameβ inspect recent streams.
- Create an API key and usage plan (more common with REST APIs).
- Or add a Lambda authorizer / JWT authorizer for real authentication.
- For learning, the open endpoint is acceptable as long as the function itself is not destructive.
- Lambda function deploys and runs successfully via the test console
- HTTP API endpoint returns the expected response
- Logs appear in CloudWatch
- You understand the difference in operational model vs EC2
- Forgetting the execution role β function cannot write logs or call other services.
- Incorrect handler name or runtime mismatch.
- Returning a non-proxy response format when using Lambda proxy integration.
- Leaving the function with overly broad IAM permissions.
- Not considering cold start latency for user-facing interactive endpoints.
- Lambda function created and tested
- HTTP API endpoint working
- You can explain when you would choose this pattern over EC2/ECS for one of your real workloads
- Quiz β₯ 80%
"For which parts of my existing projects would Lambda + API Gateway be a better fit than an always-on container or EC2 instance?"
π¦ Containers on AWS β ECR + ECS Fargate
Estimated time: 3β5 hours Β· Exam Domains: Domain 2 + Domain 3 Β· Prerequisites: Working Docker image of your application, custom VPC, ALB from previous weeks
- Create an Amazon ECR repository and push a Docker image
- Create an ECS cluster
- Write a Task Definition that maps your Docker Compose mental model
- Run the task as an ECS Service on Fargate
- Register the service with your existing Application Load Balancer
- Understand Fargate vs EC2 launch type
Mental Model Mapping
| Docker / Compose Concept | ECS Equivalent |
|---|---|
docker run / container | Task |
| docker-compose.yml service | Task Definition |
docker compose up -d | ECS Service |
| docker compose scale | Service desired count / ASG |
| Docker Hub / local registry | Amazon ECR |
Fargate vs EC2 Launch Type
- Fargate: Serverless containers β you define CPU/memory, AWS runs the tasks. No EC2 instances to manage.
- EC2 launch type: You manage the underlying EC2 instances (more control, more operational work).
For this course we use Fargate.
- ECR console β Repositories β Create repository.
- Visibility: Private.
- Repository name:
teq/hello-api(or your app name). - Create.
# Authenticate Docker to your account's ECR aws ecr get-login-password --region us-east-1 --profile teq-learning | \ docker login --username AWS --password-stdin <ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com # Build (if needed) and tag docker build -t teq/hello-api . docker tag teq/hello-api:latest <ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/teq/hello-api:latest # Push docker push <ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/teq/hello-api:latest
Replace <ACCOUNT_ID> with your 12-digit AWS account ID.
Verify the image appears in the ECR console.
- ECS console β Clusters β Create cluster.
- Cluster name:
teq-saa-cluster - Infrastructure: AWS Fargate (serverless).
- Create.
- ECS β Task definitions β Create new task definition.
- Task definition family:
teq-hello-api - Launch type: AWS Fargate.
- Operating system/Architecture: Linux / X86_64 (or ARM if your image is multi-arch).
- Task size: 0.25 vCPU / 0.5 GB is enough for many simple apps (adjust as needed).
- Task role: you can use the same role that can read Secrets Manager if needed (
teq-ec2-app-roleor a new one). - Container:
- Name:
hello-api - Image URI: the ECR URI you pushed
- Port mappings: container port 3000 (or whatever your app uses) β protocol TCP
- Environment variables or Secrets (from Secrets Manager / SSM) as needed
- Log configuration: Awslogs, create a new log group or use default
- Name:
- Create.
- Open your cluster β Services β Create.
- Compute options: Fargate.
- Task definition: select the one you just created (latest revision).
- Service name:
teq-hello-service - Desired tasks: 1
- Networking:
- VPC:
teq-saa-vpc - Subnets: public subnets (or private + NAT)
- Security group: one that allows the ALB to reach the container port
- Public IP: Turn on if using public subnets without NAT
- VPC:
- Load balancing:
- Application Load Balancer
- Select your existing ALB
- Choose the existing target group or create a new one that matches the container port
- Create the service.
Wait until the service reaches steady state and the target group shows healthy targets.
# List services aws ecs list-services --cluster teq-saa-cluster --profile teq-learning # Describe the service aws ecs describe-services --cluster teq-saa-cluster --services teq-hello-service --profile teq-learning
- Access the application via the ALB DNS name.
- Confirm tasks are running in the ECS console.
- Check CloudWatch Logs for container output.
- Image URI incorrect or authentication failure when pushing/pulling.
- Container port does not match the target group / port mapping.
- Security group does not allow traffic from the ALB on the container port.
- Task fails health checks because the application takes longer to start than the grace period.
- Not giving the task execution role permission to pull from ECR or write logs.
- Using a target group that still points at old EC2 instances.
- Image successfully pushed to ECR
- ECS Service running on Fargate
- Application reachable via the ALB
- Logs visible in CloudWatch
- Quiz β₯ 80%
"How does the mental model of ECS Task Definitions and Services map to the Docker Compose workflow I already use on my home server?"
π‘ Monitoring, Messaging & Decoupling
Estimated time: 3β4.5 hours Β· Exam Domains: Domain 2 (Resilient β decoupling) + Domain 1 (auditing) + operational excellence Β· Prerequisites: Running application behind ALB or on ECS
- Create meaningful CloudWatch alarms for your application
- Build a simple CloudWatch Dashboard
- Create an SQS queue and understand standard vs FIFO
- Decouple a synchronous process by sending work to a queue
- Create a consumer (Lambda or long-running) that processes the queue
- Understand the role of SNS and Dead Letter Queues
- Know what CloudTrail is used for
CloudWatch
- Metrics: Numerical data points (CPU, request count, custom metrics).
- Alarms: Trigger actions (SNS notifications, scaling, etc.) based on metric thresholds.
- Logs: Application and service logs; Logs Insights for querying.
- Dashboards: Visual overview of key metrics.
SQS (Simple Queue Service)
- Fully managed message queuing.
- Standard queues: At-least-once delivery, best-effort ordering, nearly unlimited throughput.
- FIFO queues: Exactly-once processing, strict ordering, limited throughput.
- Decouples producers from consumers β classic resilience pattern.
SNS vs SQS
- SNS = pub/sub (one message to many subscribers).
- SQS = queue (one message to one consumer, with buffering).
Example: ALB 5xx Errors
- CloudWatch β Alarms β Create alarm.
- Select metric β ApplicationELB β Per AppELB Metrics β
HTTPCode_Target_5XX_Countfor your load balancer. - Statistic: Sum | Period: 1 minute.
- Threshold: greater than 5 (or whatever makes sense for learning).
- Notification: use the existing billing SNS topic or create a new one.
- Name:
teq-alb-5xx-alarm - Create.
Example: Unhealthy Hosts
Create another alarm on UnHealthyHostCount > 0 for your target group.
Example: ASG or ECS CPU
Create an alarm on high CPU utilization if you want to experiment with scaling.
- CloudWatch β Dashboards β Create dashboard.
- Name:
teq-saa-dashboard - Add widgets:
- Number widget or graph for ALB request count
- Graph for target response time
- Graph for healthy/unhealthy host count
- Graph for ASG or ECS CPU/memory
- Log widget pointing at your application log group
- Save the dashboard.
This becomes your at-a-glance operational view.
- SQS console β Create queue.
- Type: Standard (for learning).
- Name:
teq-image-processingorteq-async-tasks - Configuration: leave defaults or set a visibility timeout appropriate for your expected processing time (e.g. 60β300 seconds).
- Dead-letter queue: optional but recommended β create a second queue and configure the main queue to send messages there after 3β5 failed receives.
- Create.
You can send messages from the console, the AWS CLI, or from your application.
aws sqs send-message \
--queue-url https://sqs.us-east-1.amazonaws.com/ACCOUNT/teq-async-tasks \
--message-body '{"task": "resize-image", "id": "12345"}' \
--profile teq-learning
Modify one part of your real application (or create a small test endpoint) so that a heavy or non-critical task is published to the queue instead of being performed synchronously.
Option A β Lambda Consumer (recommended for learning)
- Create a new Lambda function.
- Add an SQS trigger pointing at your queue.
- Give the Lambda execution role permission to read from the queue and delete messages (
sqs:ReceiveMessage,sqs:DeleteMessage,sqs:GetQueueAttributes). - In the function code, process the message body and perform the work.
- Send a test message and confirm the Lambda is invoked and the message is deleted.
Option B β Long-running consumer on EC2/ECS
A simple loop that polls the queue (using the AWS SDK) and processes messages. This mirrors how many background workers operate.
- Open the CloudTrail console.
- Confirm that a trail exists (or create one that logs management events to an S3 bucket).
- Understand that CloudTrail records API calls made in your account β essential for auditing and security investigations.
You do not need a complex multi-Region organization trail for this course, but you should know what the service does.
- At least two CloudWatch alarms exist and are in OK or ALARM state as expected
- Dashboard shows live metrics
- Messages can be sent to the SQS queue
- A consumer processes messages successfully
- You understand the difference between SNS and SQS
- Alarm thresholds that are too sensitive or too loose for the learning environment.
- Forgetting to give the Lambda (or consumer) permission to delete messages β messages reappear after visibility timeout.
- Visibility timeout shorter than the processing time β duplicate processing.
- Not configuring a Dead Letter Queue β poison messages stay in the queue forever.
- Creating FIFO queues when standard queues would be simpler and sufficient.
- CloudWatch alarms active
- Dashboard created
- SQS queue + producer + consumer working
- Quiz β₯ 80%
"Where in my current architecture (home server or AWS) would introducing a queue improve resilience or user experience?"
π° Well-Architected Framework & Cost Optimization
Estimated time: 2.5β4 hours Β· Exam Domains: Domain 4 (Cost-Optimized Architectures) + all pillars of the Well-Architected Framework Β· Prerequisites: Resources from previous weeks still exist (or snapshots of them)
- Apply the six pillars of the AWS Well-Architected Framework to your architecture
- Use Cost Explorer to understand where money is going
- Tag resources for cost allocation and ownership
- Identify idle, oversized, or unnecessary resources
- Create a written keep / stop / delete plan
- Understand Reserved Instances, Savings Plans, and Spot at a high level
- Make deliberate cost-control decisions
The Six Pillars
- Operational Excellence β Run and monitor systems, continually improve processes.
- Security β Protect information, systems, and assets.
- Reliability β Recover from failures, meet demand, mitigate disruptions.
- Performance Efficiency β Use resources efficiently and maintain efficiency as demand changes.
- Cost Optimization β Avoid unnecessary costs.
- Sustainability β Minimize environmental impact of cloud workloads.
The exam frequently presents architecture "story problems" that test whether you can apply these pillars.
Pricing Models (High Level)
- On-Demand: Pay for what you use, no commitment. Highest unit cost, maximum flexibility.
- Savings Plans / Reserved Instances: Commitment for 1 or 3 years in exchange for significant discount.
- Spot Instances: Spare capacity at steep discounts, can be reclaimed with short notice. Great for fault-tolerant workloads.
- Open Billing and Cost Management β Cost Explorer.
- Set the time range to the last 30 or 60 days (or the lifetime of this learning account).
- Group by Service. Identify the top cost drivers (likely EC2, RDS, ALB, NAT Gateway, data transfer, etc.).
- Group by Usage Type or API Operation if you want more detail.
- Look at the Free Tier usage page as well to see how close you are to limits.
Write down the top 3β5 services generating cost.
- Decide on a simple tagging strategy. Example:
Project=SAA-LearningEnvironment=DevOwner= your nameWeek=07(optional)
- Go through major resources (EC2, RDS, ALB, ECS services, S3 buckets, etc.) and apply the tags.
- In Cost Explorer you can later filter or group by these tags (after tags are activated for cost allocation β this can take up to 24 hours).
Tagging is both a cost practice and an operational excellence / security practice.
Ask these questions about every resource:
- Is it running 24/7 even though I only use it a few hours a week?
- Is the instance/database class oversized for the actual load?
- Do I still need that NAT Gateway?
- Are there old EBS volumes or snapshots that are no longer required?
- Is the ALB still needed right now?
- Are there unused Elastic IPs?
List every resource that can be stopped, downsized, snapshotted, or deleted.
Create a short document (even a text file or a section in your notes) with three sections:
Keep (needed for Capstone or ongoing learning)
- List the resources and why.
Stop / Snapshot (preserve state but stop billing for compute)
- EC2 instances β stop
- RDS β stop (or final snapshot + delete)
- ASG desired capacity β 0
Delete (no longer needed)
- Old test load balancers
- Unused NAT Gateways
- Temporary security groups
- Old AMIs / snapshots
- Test SQS queues, Lambda functions, etc.
Execute the plan. Confirm in Cost Explorer over the following days that spend has dropped dramatically.
Walk through your current architecture and ask one question per pillar:
- Operational Excellence: Can I observe the system? Do I have runbooks or at least notes for common tasks?
- Security: Are secrets in Secrets Manager? Are security groups least-privilege? Is encryption enabled?
- Reliability: Multi-AZ? Auto Scaling? Health checks? Decoupling?
- Performance Efficiency: Right-sized compute and storage? Caching where appropriate?
- Cost Optimization: What did the Cost Explorer review reveal? What did you shut down?
- Sustainability: Are you running resources only when needed? Using efficient instance families?
Note any gaps. You do not need a formal Well-Architected Framework Review tool for this course, but the thinking process is what the exam tests.
- Cost Explorer reviewed and top cost drivers identified
- Major resources tagged
- Written keep / stop / delete plan exists
- Unnecessary resources have been stopped or deleted
- You can articulate at least one improvement under each of the six pillars
- Leaving NAT Gateways or ALBs running when not actively testing.
- Forgetting that stopped RDS instances still incur storage costs.
- Not tagging resources early β it becomes painful later.
- Treating Free Tier as "completely free forever" instead of monitoring usage.
- Optimizing for cost before the architecture actually works (premature optimization).
- Cost Explorer review completed
- Resources tagged
- Keep / Stop / Delete plan written and executed
- Quiz β₯ 80%
- Account spend is near zero or clearly understood and controlled
"What surprised me most about the cost of the resources I built, and what will I do differently on the Capstone and in future projects?"
π Capstone Project + Exam Preparation
Estimated time: 8β20+ hours (spread over several days) Β· Exam Domains: All four domains integrated Β· Prerequisites: Weeks 1β11 completed
- Design a complete multi-tier architecture that meets real requirements
- Deploy the architecture end-to-end on AWS
- Document the design decisions and how they map to the Well-Architected Framework
- Complete multiple full-length practice exams
- Identify and remediate weak areas
- Reach a state of readiness to book and pass the SAA-C03 exam
Design and deploy one complete, small-scale but production-style multi-tier application on AWS.
Choose something you actually care about β a real side project, a utility service, an API for one of your own apps, or any real workload that benefits from a proper AWS architecture.
Minimum Required Architecture
| Component | Requirement |
|---|---|
| Networking | Custom VPC, public + private subnets, multi-AZ |
| Compute / App Tier | ALB + Auto Scaling Group or ECS Fargate |
| Data Tier | RDS (private subnets) or DynamoDB |
| Object Storage | S3 (static assets, uploads, or both) |
| Security | IAM roles (no long-lived keys on compute), Security Groups least privilege, Secrets Manager |
| Observability | CloudWatch alarms + basic dashboard |
| Stretch goals (optional) | CloudFront, WAF, SQS decoupling, multiple environments, CI/CD |
- Write the requirements (even if short):
- What does the application do?
- Expected traffic pattern?
- Data storage needs?
- Availability requirements?
- Budget constraints?
- Draw the architecture (Excalidraw, draw.io, or even paper):
- Show public vs private subnets
- Show security group relationships
- Show which services talk to which
- Implement using the skills from previous weeks:
- Reuse Launch Templates, roles, and patterns you already built.
- Prefer infrastructure that can be torn down cleanly.
- Document:
- Architecture diagram
- Key design decisions and why you made them
- How the architecture maps to the six pillars
- Estimated monthly cost if left running 24/7
- Cleanup / teardown instructions
- Test:
- End-to-end functionality
- Failure scenarios (terminate an instance, force a health check failure)
- Basic load if possible
1. Official Materials
- Read the official SAA-C03 Exam Guide (domains and task statements).
- Review the list of in-scope services.
2. Practice Exams
Take at least two full-length practice exams under realistic conditions (timed, no notes).
Recommended sources (widely regarded as close to the real exam style): Tutorials Dojo, Neal Davis / Digital Cloud Training, or the official AWS practice exam (if available).
For every incorrect question: write down why the correct answer is correct, note which domain it belongs to, and identify the concept you missed.
3. Weak Area Remediation
Common weak areas for many candidates:
- IAM policy evaluation and permission boundaries
- VPC networking edge cases (NAT, endpoints, connectivity)
- Storage class and lifecycle decision making
- Cost optimization scenario questions
- When to choose one service over another (RDS vs DynamoDB, ALB vs NLB, etc.)
Revisit the corresponding week labs and re-read the AWS documentation for those topics.
4. Final Readiness Criteria
You are ready to book the exam when:
- Practice exam scores are consistently β₯ 80%
- You can explain why the correct answers are correct
- You feel comfortable with the four domain weightings and where your remaining gaps are
- Architecture diagram exists and is clear
- Custom VPC with multi-AZ public and private subnets
- Application is highly available (ALB + ASG or Fargate across AZs)
- Database is in private subnets with restricted security groups
- Secrets are in Secrets Manager; compute uses IAM roles
- CloudWatch alarms and a dashboard exist
- End-to-end functionality works
- Design decisions are documented
- Cost estimate and cleanup plan exist
These pull multiple weeks together β think through each before checking the quiz below:
- How would you change your Capstone architecture if the application needed to serve users on multiple continents with low latency?
- Walk through the path of an HTTP request from a user on the internet all the way to a row in your private RDS database.
- Which of the six Well-Architected pillars is currently the weakest in your Capstone, and what would you improve first?
- Capstone application is deployed and functional
- Architecture and decisions are documented
- At least two full practice exams completed with scores tracked
- Weak areas identified and reviewed
- You are ready to book the SAA-C03 exam (or have already booked it)
"What can I now design and deploy on AWS that I could not confidently do 12 weeks ago? What is still unclear or needs more practice?"
Twelve Weeks, One Real Architecture
You have completed a full project-based SAA-C03 course that goes far beyond multiple-choice preparation. You now have hands-on experience with the core services that appear on the exam, a real multi-tier application you designed and deployed, a systematic way of thinking about security, resilience, performance, and cost, and a portfolio piece and documentation you can refer to later.
Book the exam when your practice scores are solid, and treat the real exam as another architecture decision exercise. Good luck.