AWS is the leading cloud platform with 200+ services. For most applications, master these six: EC2 (virtual machines), S3 (object storage), Lambda (serverless compute), RDS (managed databases), CloudFront (CDN), and IAM (identity/access control). Use the AWS Free Tier for learning, set billing alerts immediately, and use IAM roles instead of long-lived access keys.
Amazon Web Services (AWS) is the world's most comprehensive cloud platform with over 200 fully featured services spanning compute, storage, networking, databases, AI/ML, security, and more. Launched in 2006 with just S3 and EC2, AWS now powers millions of applications from startups to Fortune 500 enterprises. Whether you are running a personal project or architecting a globally distributed system, understanding the essential AWS services is a critical skill for modern developers. This guide covers the most important services you will use day-to-day, with real CLI commands, IAM policy examples, architecture patterns, and cost optimization strategies.
- IAM roles are always safer than IAM users with access keys — use them for EC2 instances, Lambda functions, and CI/CD pipelines.
- S3 + CloudFront is the cheapest and most scalable way to host static websites and serve large files globally.
- Lambda cold starts affect Node.js/Python less than Java/C# — keep functions warm with provisioned concurrency for latency-sensitive workloads.
- RDS Multi-AZ provides automatic failover; Read Replicas provide read scalability — they are separate features, use both if needed.
- Always tag every AWS resource with at minimum: Project, Environment, Owner — it makes cost allocation and cleanup vastly easier.
- Use AWS Cost Explorer and set billing alarms before you start — unexpected bills are the most common AWS beginner mistake.
AWS Core Concepts: Regions, Availability Zones, and IAM
Before diving into specific services, you need to understand how AWS organizes its global infrastructure and controls access.
Regions and Availability Zones
AWS operates in 33+ geographic Regions, each containing 3+ Availability Zones (AZs). A Region is an isolated geographic area (e.g., us-east-1, eu-west-1, ap-southeast-1). An AZ is a physically separate data center within a region with independent power, cooling, and networking. Deploying across multiple AZs protects against data center failures. Some services (IAM, CloudFront, Route 53) are global; most (EC2, RDS, Lambda) are regional.
# List all regions
aws ec2 describe-regions --output table
# Set a default region in environment
export AWS_DEFAULT_REGION=us-east-1
# Set region in AWS CLI profile
aws configure set region us-east-1
# List AZs in current region
aws ec2 describe-availability-zones \
--query "AvailabilityZones[].ZoneName" \
--output textIAM: Identity and Access Management
IAM is the backbone of AWS security. It controls who can access AWS services and what actions they can perform. The core components are: Users (people or apps with long-term credentials), Groups (collections of users), Roles (assumed by AWS services or external identities — no static credentials), and Policies (JSON documents defining permissions). The golden rule: always use roles over access keys, and apply the principle of least privilege.
# Example IAM policy — least privilege for S3 read on specific bucket
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadSpecificBucket",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-app-bucket",
"arn:aws:s3:::my-app-bucket/*"
]
}
]
}
# Create an IAM role for EC2 (trust policy)
aws iam create-role \
--role-name MyEC2Role \
--assume-role-policy-document file://ec2-trust-policy.json
# Attach a managed policy
aws iam attach-role-policy \
--role-name MyEC2Role \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
# Create instance profile and add role
aws iam create-instance-profile --instance-profile-name MyEC2Profile
aws iam add-role-to-instance-profile \
--instance-profile-name MyEC2Profile \
--role-name MyEC2RoleUnderstanding AWS Pricing
AWS uses a pay-as-you-go model. Key pricing concepts: On-Demand (pay per second/hour, no commitment), Reserved Instances (1-3 year commitment, up to 72% savings), Spot Instances (unused capacity, up to 90% savings but can be interrupted), Savings Plans (flexible commitment across instance families), and Free Tier (12 months free for new accounts on select services). Always use the AWS Pricing Calculator before deploying production workloads.
EC2: Elastic Compute Cloud
EC2 provides resizable virtual machines (called instances) in the cloud. It is the foundation of many AWS architectures. You choose the operating system, instance type, storage, and networking configuration.
EC2 Instance Type Families
AWS offers hundreds of instance types organized into families. The name format is: [family][generation].[size] (e.g., t3.micro, m5.xlarge, c6i.2xlarge).
| Family | Purpose | Examples | Use Cases |
|---|---|---|---|
t3/t4g | Burstable General Purpose | t3.micro, t3.small, t4g.medium | Dev/test, low-traffic web apps, small DBs |
m5/m6i | General Purpose | m5.large, m6i.xlarge, m6a.2xlarge | Web apps, app servers, small-medium DBs |
c5/c6i | Compute Optimized | c5.large, c6i.2xlarge, c6g.4xlarge | Batch processing, ML inference, gaming |
r5/r6i | Memory Optimized | r5.large, r6i.2xlarge, x2idn.32xlarge | In-memory DBs, Redis, Elasticsearch, SAP |
i3/i4i | Storage Optimized | i3.large, i4i.xlarge, d3en.2xlarge | NoSQL DBs, data warehousing, Kafka |
p3/p4d | GPU Instances | p3.2xlarge, p4d.24xlarge, g5.xlarge | ML training, GPU rendering, HPC |
Security Groups
Security groups act as virtual firewalls for your EC2 instances. They control inbound and outbound traffic at the instance level. Unlike NACLs, security groups are stateful: if you allow inbound traffic, the response is automatically allowed.
# Create a security group
aws ec2 create-security-group \
--group-name my-web-sg \
--description "Web server security group" \
--vpc-id vpc-0123456789abcdef0
# Allow HTTP from anywhere
aws ec2 authorize-security-group-ingress \
--group-id sg-0123456789abcdef0 \
--protocol tcp --port 80 --cidr 0.0.0.0/0
# Allow HTTPS from anywhere
aws ec2 authorize-security-group-ingress \
--group-id sg-0123456789abcdef0 \
--protocol tcp --port 443 --cidr 0.0.0.0/0
# Allow SSH from specific IP only
aws ec2 authorize-security-group-ingress \
--group-id sg-0123456789abcdef0 \
--protocol tcp --port 22 --cidr 203.0.113.0/32
# Allow traffic from another security group (e.g., ALB → EC2)
aws ec2 authorize-security-group-ingress \
--group-id sg-ec2 \
--protocol tcp --port 8080 \
--source-group sg-albUser Data Scripts
User data scripts run on instance launch and are perfect for bootstrapping: installing software, pulling configs, or registering the instance with a service.
#!/bin/bash
# EC2 User Data — runs as root on first boot
set -euo pipefail
# Update packages
yum update -y
# Install Node.js 20 via NVM
export HOME=/root
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
source ~/.nvm/nvm.sh
nvm install 20
nvm use 20
# Install PM2 globally
npm install -g pm2
# Clone application from S3
aws s3 cp s3://my-app-bucket/app.tar.gz /tmp/app.tar.gz
mkdir -p /opt/app
tar -xzf /tmp/app.tar.gz -C /opt/app
cd /opt/app && npm install --production
# Start the application
pm2 start server.js --name my-app
pm2 startup
pm2 save
# Signal CloudFormation that instance is ready
# (if using cfn-init)
# /opt/aws/bin/cfn-signal -e $? ...# Launch an EC2 instance with user data
aws ec2 run-instances \
--image-id ami-0abcdef1234567890 \
--instance-type t3.small \
--key-name my-key-pair \
--security-group-ids sg-0123456789abcdef0 \
--subnet-id subnet-0123456789abcdef0 \
--iam-instance-profile Name=MyEC2Profile \
--user-data file://user-data.sh \
--tag-specifications \
"ResourceType=instance,Tags=[{Key=Name,Value=my-server},{Key=Environment,Value=production}]" \
--count 1S3: Simple Storage Service
S3 is AWS's object storage service. It stores files (objects) in containers (buckets). S3 offers 11 nines (99.999999999%) of durability, lifecycle management, versioning, server-side encryption, and static website hosting.
S3 Storage Classes
S3 offers several storage classes optimized for different access patterns and cost requirements.
| Storage Class | Retrieval | Min Duration | Cost | Use Case |
|---|---|---|---|---|
| S3 Standard | Instant | None | $$ | Frequently accessed data |
| S3 Intelligent-Tiering | Instant | None | $-$$ | Unknown/changing patterns |
| S3 Standard-IA | Instant | 30 days | $ | Infrequent access, rapid when needed |
| S3 One Zone-IA | Instant | 30 days | $ | Non-critical, reproductible data |
| S3 Glacier Instant | Instant | 90 days | ¢ | Archive with instant retrieval |
| S3 Glacier Flexible | Minutes-hours | 90 days | ¢ | Long-term archive, rare access |
| S3 Glacier Deep Archive | 12 hours | 180 days | ¢¢ | Regulatory/compliance archive |
# Create a bucket
aws s3api create-bucket \
--bucket my-app-bucket-2026 \
--region us-east-1
# For regions other than us-east-1, add LocationConstraint
aws s3api create-bucket \
--bucket my-app-bucket-2026 \
--region eu-west-1 \
--create-bucket-configuration LocationConstraint=eu-west-1
# Enable versioning
aws s3api put-bucket-versioning \
--bucket my-app-bucket-2026 \
--versioning-configuration Status=Enabled
# Enable server-side encryption (SSE-S3)
aws s3api put-bucket-encryption \
--bucket my-app-bucket-2026 \
--server-side-encryption-configuration \
"{\"Rules\":[{\"ApplyServerSideEncryptionByDefault\":{\"SSEAlgorithm\":\"AES256\"}}]}"
# Block all public access (recommended default)
aws s3api put-public-access-block \
--bucket my-app-bucket-2026 \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
# Upload a file
aws s3 cp ./index.html s3://my-app-bucket-2026/
# Sync a local directory to S3 (like rsync)
aws s3 sync ./dist s3://my-app-bucket-2026/ --delete
# Set cache headers for static assets
aws s3 sync ./dist s3://my-app-bucket-2026/ \
--cache-control "max-age=31536000,public" \
--exclude "*.html" \
--delete
# Upload HTML files with no-cache
aws s3 sync ./dist s3://my-app-bucket-2026/ \
--include "*.html" \
--cache-control "no-cache,no-store,must-revalidate"Static Website Hosting
S3 can host static websites directly without any servers. Combined with CloudFront, this is the cheapest way to serve static content globally.
# Enable static website hosting
aws s3api put-bucket-website \
--bucket my-app-bucket-2026 \
--website-configuration \
"{\"IndexDocument\":{\"Suffix\":\"index.html\"},\"ErrorDocument\":{\"Key\":\"404.html\"}}""
# Set bucket policy for public read (required for static hosting)
cat > bucket-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-app-bucket-2026/*"
}]
}
EOF
aws s3api put-bucket-policy \
--bucket my-app-bucket-2026 \
--policy file://bucket-policy.json
# Your website is now at:
# http://my-app-bucket-2026.s3-website-us-east-1.amazonaws.com
# Point CloudFront at this origin for HTTPS + CDNPresigned URLs
Presigned URLs allow temporary, authenticated access to private S3 objects without exposing your credentials. They are essential for user file uploads and secure downloads.
# CLI: Generate presigned GET URL (valid 1 hour)
aws s3 presign s3://my-app-bucket-2026/private-file.pdf \
--expires-in 3600
# Node.js SDK v3: Presigned PUT (for user uploads)
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const s3 = new S3Client({ region: "us-east-1" });
async function getUploadUrl(key: string, contentType: string) {
const command = new PutObjectCommand({
Bucket: "my-app-bucket-2026",
Key: key,
ContentType: contentType,
});
// URL expires in 15 minutes
const url = await getSignedUrl(s3, command, { expiresIn: 900 });
return url;
}
// Client uploads directly to S3 (no server bandwidth used)
const url = await getUploadUrl("uploads/avatar.jpg", "image/jpeg");
await fetch(url, {
method: "PUT",
body: file,
headers: { "Content-Type": "image/jpeg" },
});Lambda: Serverless Functions
Lambda runs your code without provisioning or managing servers. You pay only for the compute time you consume. Lambda supports Node.js, Python, Go, Java, Ruby, .NET, and custom runtimes.
Lambda Triggers
Lambda functions can be triggered by dozens of AWS services and events.
| Trigger Source | Invocation Type | Common Use Case |
|---|---|---|
| API Gateway / Function URL | Synchronous | REST APIs, webhooks |
| S3 (ObjectCreated, ObjectRemoved) | Asynchronous | Image processing, file transformations |
| DynamoDB Streams | Stream | Change data capture, event sourcing |
| SQS | Poll-based | Message processing, job queues |
| SNS | Asynchronous | Fan-out notifications |
| EventBridge (CloudWatch Events) | Asynchronous | Scheduled tasks, event routing |
| Kinesis Data Streams | Stream | Real-time analytics, log processing |
| Cognito | Synchronous | Custom auth flows, user pool triggers |
| CloudFront (Lambda@Edge) | Synchronous | Request/response manipulation at edge |
// Lambda handler best practices — Node.js 20
import { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb";
import { marshall } from "@aws-sdk/util-dynamodb";
// Initialize clients OUTSIDE the handler (reused across warm invocations)
const dynamo = new DynamoDBClient({ region: process.env.AWS_REGION });
// Type the event — import from @types/aws-lambda
export const handler = async (event: AWSLambda.APIGatewayProxyEventV2) => {
// Always log structured JSON for CloudWatch Insights queries
console.log(JSON.stringify({ event: "request", path: event.rawPath }));
try {
const body = JSON.parse(event.body || "{}") as { name: string };
await dynamo.send(new PutItemCommand({
TableName: process.env.TABLE_NAME!,
Item: marshall({
pk: "USER#" + Date.now(),
name: body.name,
createdAt: new Date().toISOString(),
}),
}));
return {
statusCode: 201,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ success: true }),
};
} catch (err) {
console.error(JSON.stringify({ error: String(err) }));
return { statusCode: 500, body: JSON.stringify({ error: "Internal error" }) };
}
};
# Deploy Lambda via AWS CLI
# 1. Create deployment package
npm run build && zip -r function.zip dist/ node_modules/
# 2. Create the function
aws lambda create-function \
--function-name my-api-handler \
--runtime nodejs20.x \
--role arn:aws:iam::123456789012:role/LambdaExecutionRole \
--handler dist/index.handler \
--zip-file fileb://function.zip \
--timeout 30 \
--memory-size 256 \
--environment "Variables={TABLE_NAME=my-table,LOG_LEVEL=info}"
# 3. Update function code (redeploy)
aws lambda update-function-code \
--function-name my-api-handler \
--zip-file fileb://function.zip
# 4. Enable Provisioned Concurrency (eliminate cold starts)
aws lambda put-provisioned-concurrency-config \
--function-name my-api-handler \
--qualifier prod \
--provisioned-concurrent-executions 5Understanding Cold Starts
A cold start occurs when Lambda has to initialize a new execution environment for your function. This adds latency: 100ms-1s for Node.js/Python, 1-5s for Java/C#. Strategies to minimize cold starts include keeping functions small, using Provisioned Concurrency for latency-sensitive functions, initializing SDK clients outside the handler, and using lighter runtimes (Node.js over Java).
Lambda Layers
Layers let you share code and dependencies across multiple Lambda functions. A layer is a ZIP archive containing libraries, custom runtimes, or data. Functions can use up to 5 layers, and layers can be versioned.
# Create a Lambda layer (e.g., shared utilities + node_modules)
# Layer structure: nodejs/node_modules/...
mkdir -p layer/nodejs
cp -r node_modules layer/nodejs/
cd layer && zip -r ../shared-deps-layer.zip nodejs/
# Publish the layer
aws lambda publish-layer-version \
--layer-name shared-dependencies \
--description "Shared node_modules for production" \
--zip-file fileb://shared-deps-layer.zip \
--compatible-runtimes nodejs18.x nodejs20.x
# Attach layer to a function
aws lambda update-function-configuration \
--function-name my-api-handler \
--layers arn:aws:lambda:us-east-1:123456789012:layer:shared-dependencies:3API Gateway: REST and HTTP APIs
API Gateway is the front door for your Lambda functions and backend services. It handles authentication, throttling, caching, CORS, and request/response transformations.
REST API vs HTTP API
API Gateway offers two main API types. REST APIs are more feature-rich (request validation, usage plans, caching, WAF integration) but cost more. HTTP APIs are simpler, faster (lower latency), and cost 70% less. Use HTTP APIs for Lambda integrations and REST APIs when you need advanced features.
| Feature | REST API | HTTP API |
|---|---|---|
| Pricing (per million) | $3.50 | $1.00 |
| Latency | Higher | Lower (~60% faster) |
| Request Validation | Yes | No |
| WAF Integration | Yes | No |
| Usage Plans / API Keys | Yes | No |
| Response Caching | Yes | No |
| Lambda Proxy Integration | Yes | Yes (optimized) |
| JWT Authorizers | Via Lambda | Native |
| CORS | Manual | Auto-configure |
| Private API (VPC) | Yes | No |
# Create an HTTP API (recommended for Lambda)
aws apigatewayv2 create-api \
--name my-http-api \
--protocol-type HTTP \
--cors-configuration \
"AllowOrigins=[https://myapp.com],AllowMethods=[GET,POST,PUT,DELETE],AllowHeaders=[Content-Type,Authorization]"
# Create Lambda integration
aws apigatewayv2 create-integration \
--api-id abc123 \
--integration-type AWS_PROXY \
--integration-uri arn:aws:lambda:us-east-1:123456789012:function:my-api-handler \
--payload-format-version 2.0
# Create a route
aws apigatewayv2 create-route \
--api-id abc123 \
--route-key "POST /users" \
--target integrations/xyz789
# Add a JWT authorizer
aws apigatewayv2 create-authorizer \
--api-id abc123 \
--name CognitoJWT \
--authorizer-type JWT \
--identity-source "$request.header.Authorization" \
--jwt-configuration \
"Issuer=https://cognito-idp.us-east-1.amazonaws.com/us-east-1_abc123,Audience=[my-app-client-id]"RDS: Relational Database Service
RDS provides managed relational databases: MySQL, PostgreSQL, MariaDB, Oracle, SQL Server, and Aurora. AWS handles backups, patching, Multi-AZ failover, and read replicas.
Multi-AZ Deployments
Multi-AZ creates a synchronous standby replica in a different AZ. In case of primary failure, RDS automatically fails over to the standby (typically 1-2 minutes). Multi-AZ is for high availability, NOT for read scaling. The standby is not readable — use Read Replicas for that.
# Create an RDS PostgreSQL instance with Multi-AZ
aws rds create-db-instance \
--db-instance-identifier my-postgres-prod \
--db-instance-class db.t3.medium \
--engine postgres \
--engine-version 15.4 \
--master-username admin \
--master-user-password "$(openssl rand -base64 32)" \
--allocated-storage 100 \
--storage-type gp3 \
--multi-az \
--backup-retention-period 7 \
--deletion-protection \
--enable-performance-insights \
--db-subnet-group-name my-db-subnet-group \
--vpc-security-group-ids sg-0db123456789
# Create a Read Replica in same region
aws rds create-db-instance-read-replica \
--db-instance-identifier my-postgres-replica \
--source-db-instance-identifier my-postgres-prod \
--db-instance-class db.t3.medium
# Promote replica to standalone (e.g., for region failover)
aws rds promote-read-replica \
--db-instance-identifier my-postgres-replica
# Enable IAM database authentication
aws rds modify-db-instance \
--db-instance-identifier my-postgres-prod \
--enable-iam-database-authentication \
--apply-immediatelySnapshots and Backups
RDS supports automated backups (1-35 day retention) and manual snapshots that persist until deleted. You can restore to any point-in-time within the retention period or create a new instance from a snapshot.
# Create a manual snapshot
aws rds create-db-snapshot \
--db-instance-identifier my-postgres-prod \
--db-snapshot-identifier my-postgres-backup-2026-02-27
# Restore from snapshot (creates a new instance)
aws rds restore-db-instance-from-db-snapshot \
--db-instance-identifier my-postgres-restored \
--db-snapshot-identifier my-postgres-backup-2026-02-27 \
--db-instance-class db.t3.medium
# Restore to a specific point in time
aws rds restore-db-instance-to-point-in-time \
--source-db-instance-identifier my-postgres-prod \
--target-db-instance-identifier my-postgres-pitr \
--restore-time 2026-02-26T12:00:00ZDynamoDB: NoSQL at Scale
DynamoDB is a fully managed NoSQL database with single-digit millisecond latency at any scale. It is schemaless, serverless, and automatically handles partitioning. It is ideal for high-traffic applications where read/write patterns are predictable.
Indexes: GSI and LSI
DynamoDB has two types of secondary indexes. Local Secondary Indexes (LSI) share the partition key but have a different sort key — they must be created at table creation. Global Secondary Indexes (GSI) can have completely different partition and sort keys — they can be created at any time and have their own read/write capacity.
# Create a DynamoDB table with GSI
aws dynamodb create-table \
--table-name Orders \
--attribute-definitions \
AttributeName=PK,AttributeType=S \
AttributeName=SK,AttributeType=S \
AttributeName=GSI1PK,AttributeType=S \
AttributeName=GSI1SK,AttributeType=S \
--key-schema \
AttributeName=PK,KeyType=HASH \
AttributeName=SK,KeyType=RANGE \
--global-secondary-indexes \
"[{\"IndexName\":\"GSI1\",\"KeySchema\":[{\"AttributeName\":\"GSI1PK\",\"KeyType\":\"HASH\"},{\"AttributeName\":\"GSI1SK\",\"KeyType\":\"RANGE\"}],\"Projection\":{\"ProjectionType\":\"ALL\"}}]" \
--billing-mode PAY_PER_REQUEST
# Single-table design: PK/SK patterns
# User: PK=USER#userId SK=PROFILE
# Order: PK=USER#userId SK=ORDER#orderId
# GSI1 query: PK=ORDER#status SK=createdAt (all orders by status)
# Query orders for a user
aws dynamodb query \
--table-name Orders \
--key-condition-expression "PK = :pk AND begins_with(SK, :skPrefix)" \
--expression-attribute-values \
"{\":pk\":{\"S\":\"USER#user123\"},\":skPrefix\":{\"S\":\"ORDER#\"}}"
# Query via GSI (all PENDING orders)
aws dynamodb query \
--table-name Orders \
--index-name GSI1 \
--key-condition-expression "GSI1PK = :status" \
--expression-attribute-values "{\":status\":{\"S\":\"ORDER#PENDING\"}}"DynamoDB Streams
Streams capture a time-ordered sequence of every change (INSERT, MODIFY, REMOVE) made to items in a table. Each record is available for 24 hours. Streams can trigger Lambda functions for event-driven architectures.
CloudFront: Global Content Delivery Network
CloudFront is AWS's CDN with 450+ edge locations globally. It caches content close to users, reducing latency and origin load. CloudFront supports S3, EC2, ALB, API Gateway, and custom origins.
Cache Behaviors
Behaviors define how CloudFront handles requests for different URL patterns. You can configure different origins, TTLs, headers to forward, and Lambda@Edge functions for each behavior. The default behavior matches all requests (*) and is your fallback.
# Create a CloudFront distribution (S3 origin)
aws cloudfront create-distribution --distribution-config file://cf-config.json
# cf-config.json (simplified)
{
"Origins": {
"Quantity": 1,
"Items": [{
"Id": "S3Origin",
"DomainName": "my-app-bucket.s3.amazonaws.com",
"S3OriginConfig": { "OriginAccessIdentity": "" }
}]
},
"DefaultCacheBehavior": {
"TargetOriginId": "S3Origin",
"ViewerProtocolPolicy": "redirect-to-https",
"CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
"Compress": true
},
"CacheBehaviors": {
"Quantity": 1,
"Items": [{
"PathPattern": "/api/*",
"TargetOriginId": "APIGatewayOrigin",
"CachePolicyId": "4135ea2d-6df8-44a3-9df3-4b5a84be39ad",
"ViewerProtocolPolicy": "https-only"
}]
},
"PriceClass": "PriceClass_100",
"HttpVersion": "http2and3",
"IsIPV6Enabled": true
}Cache Invalidation
When you update content, you may need to invalidate the CloudFront cache. The first 1,000 invalidation paths per month are free; subsequent ones cost $0.005 each. Use versioned file names (app.v2.js) instead of invalidations for better cache control.
# Invalidate specific files
aws cloudfront create-invalidation \
--distribution-id EDFDVBD6EXAMPLE \
--paths "/index.html" "/app.js"
# Invalidate all files (expensive if > 1000 paths/month)
aws cloudfront create-invalidation \
--distribution-id EDFDVBD6EXAMPLE \
--paths "/*"
# Better: Use versioned asset names (no invalidation needed)
# app.abc123.js <- content hash in filename
# styles.def456.css
# Only invalidate index.html (the entry point)VPC: Virtual Private Cloud
VPC is your private, isolated network within AWS. Every AWS account comes with a default VPC. For production workloads, create custom VPCs with proper subnet segmentation.
Public vs Private Subnets
Public subnets have a route to the Internet Gateway and are used for resources that need direct internet access (load balancers, bastion hosts). Private subnets have no direct internet route and are used for databases, application servers, and Lambda functions. Private subnets can access the internet via a NAT Gateway (for IPv4) in a public subnet.
# Create a VPC
aws ec2 create-vpc --cidr-block 10.0.0.0/16
# Create public subnets (one per AZ)
aws ec2 create-subnet \
--vpc-id vpc-0abc123 \
--cidr-block 10.0.1.0/24 \
--availability-zone us-east-1a
aws ec2 create-subnet \
--vpc-id vpc-0abc123 \
--cidr-block 10.0.2.0/24 \
--availability-zone us-east-1b
# Create private subnets
aws ec2 create-subnet \
--vpc-id vpc-0abc123 \
--cidr-block 10.0.11.0/24 \
--availability-zone us-east-1a
# Create Internet Gateway (for public subnets)
aws ec2 create-internet-gateway
aws ec2 attach-internet-gateway \
--vpc-id vpc-0abc123 \
--internet-gateway-id igw-0abc123
# Create NAT Gateway in public subnet (for private subnet outbound)
# First, allocate an Elastic IP
aws ec2 allocate-address --domain vpc
aws ec2 create-nat-gateway \
--subnet-id subnet-public-1a \
--allocation-id eipalloc-0abc123
# Add default route to private route table via NAT Gateway
aws ec2 create-route \
--route-table-id rtb-private \
--destination-cidr-block 0.0.0.0/0 \
--nat-gateway-id nat-0abc123NACLs vs Security Groups
Network ACLs operate at the subnet level and are stateless: you must explicitly allow both inbound and outbound rules. Security groups operate at the instance level and are stateful. Use security groups as your primary control; use NACLs for additional subnet-level protection when needed.
CloudFormation: Infrastructure as Code
CloudFormation lets you provision AWS resources using JSON or YAML templates. It manages resource dependencies, supports rollback on failure, and enables infrastructure versioning. It is the AWS-native IaC tool; alternatives include CDK (TypeScript/Python), Terraform, and Pulumi.
# CloudFormation template: S3 bucket + Lambda + API Gateway
AWSTemplateFormatVersion: "2010-09-09"
Description: "Serverless API stack"
Parameters:
Environment:
Type: String
AllowedValues: [dev, staging, production]
Default: dev
Resources:
# S3 bucket for uploads
UploadsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "uploads-${Environment}-${AWS::AccountId}"
VersioningConfiguration:
Status: Enabled
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
# Lambda execution role
LambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal: { Service: lambda.amazonaws.com }
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: S3Access
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action: [s3:GetObject, s3:PutObject]
Resource: !Sub "${UploadsBucket.Arn}/*"
# Lambda function
ApiFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Sub "api-handler-${Environment}"
Runtime: nodejs20.x
Handler: dist/index.handler
Role: !GetAtt LambdaRole.Arn
Timeout: 30
MemorySize: 256
Environment:
Variables:
BUCKET_NAME: !Ref UploadsBucket
ENV: !Ref Environment
Outputs:
BucketName:
Value: !Ref UploadsBucket
FunctionArn:
Value: !GetAtt ApiFunction.Arn
# Deploy the stack
aws cloudformation deploy \
--template-file template.yml \
--stack-name my-serverless-api \
--parameter-overrides Environment=production \
--capabilities CAPABILITY_IAMAWS CLI: Key Commands and Profiles
The AWS CLI is essential for automation, scripting, and day-to-day operations. Install it with pip or a package manager, configure profiles, and use it in scripts and CI/CD pipelines.
# Install AWS CLI v2
# macOS
brew install awscli
# Linux
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip && sudo ./aws/install
# --- Configure profiles ---
# Interactive setup (asks for key ID, secret, region, output format)
aws configure
# Configure a named profile
aws configure --profile production
# ~/.aws/credentials
# [default]
# aws_access_key_id = AKIAIOSFODNN7EXAMPLE
# aws_secret_access_key = wJalrXUtnFEMI/K7MDENG...
# [production]
# aws_access_key_id = AKIAI...
# aws_secret_access_key = ...
# ~/.aws/config
# [default]
# region = us-east-1
# output = json
# [profile production]
# region = us-east-1
# role_arn = arn:aws:iam::123456789012:role/ProductionRole
# source_profile = default
# Use a specific profile
aws s3 ls --profile production
AWS_PROFILE=production aws ec2 describe-instances
# --- Essential commands ---
# EC2
aws ec2 describe-instances --query "Reservations[].Instances[].[InstanceId,State.Name,Tags[?Key==Name].Value|[0]]" --output table
aws ec2 start-instances --instance-ids i-0abc123
aws ec2 stop-instances --instance-ids i-0abc123
# S3
aws s3 ls s3://my-bucket/ --recursive --human-readable --summarize
aws s3 rm s3://my-bucket/old-file.txt
aws s3 mv s3://src/file.txt s3://dest/file.txt
# Lambda
aws lambda list-functions --query "Functions[].[FunctionName,Runtime,LastModified]" --output table
aws lambda invoke --function-name my-fn --payload "{\"test\":true}" response.json
# CloudWatch Logs
aws logs tail /aws/lambda/my-fn --follow
aws logs filter-log-events \
--log-group-name /aws/lambda/my-fn \
--filter-pattern "ERROR"
# SSM Parameter Store (store secrets securely)
aws ssm put-parameter \
--name "/myapp/prod/db-password" \
--value "supersecret" \
--type SecureString
aws ssm get-parameter \
--name "/myapp/prod/db-password" \
--with-decryption \
--query "Parameter.Value" --output textAWS vs GCP vs Azure: Service Comparison
Here is a comparison of equivalent services across the three major cloud providers.
| Service Category | AWS | Google Cloud (GCP) | Microsoft Azure |
|---|---|---|---|
| Virtual Machines | EC2 | Compute Engine | Virtual Machines |
| Object Storage | S3 | Cloud Storage (GCS) | Azure Blob Storage |
| Serverless Functions | Lambda | Cloud Functions / Cloud Run | Azure Functions |
| Managed Kubernetes | EKS | GKE | AKS |
| Managed MySQL/Postgres | RDS Aurora | Cloud SQL | Azure Database for PostgreSQL |
| NoSQL Database | DynamoDB | Firestore / Bigtable | Cosmos DB |
| CDN | CloudFront | Cloud CDN | Azure CDN / Front Door |
| DNS | Route 53 | Cloud DNS | Azure DNS |
| Message Queue | SQS | Cloud Pub/Sub | Azure Service Bus |
| Event Streaming | Kinesis | Pub/Sub Lite / Dataflow | Azure Event Hubs |
| Container Registry | ECR | Artifact Registry (GCR) | Azure Container Registry |
| Identity / Auth | Cognito | Firebase Auth / Identity Platform | Azure Active Directory B2C |
| API Gateway | API Gateway | Apigee / Cloud Endpoints | Azure API Management |
| Infrastructure as Code | CloudFormation / CDK | Deployment Manager / Config Connector | ARM / Bicep |
| Monitoring | CloudWatch | Cloud Monitoring + Logging | Azure Monitor |
| Secret Management | Secrets Manager | Secret Manager | Azure Key Vault |
| ML/AI Platform | SageMaker | Vertex AI | Azure Machine Learning |
| Data Warehouse | Redshift | BigQuery | Azure Synapse Analytics |
| VPN / Private Network | VPC + Direct Connect | VPC + Dedicated Interconnect | VNet + ExpressRoute |
Cost Optimization Tips
AWS costs can spiral quickly without careful management. These strategies will help you control your bill.
- Right-size EC2 instances: use AWS Compute Optimizer recommendations. Oversized instances are the most common source of waste.
- Use Savings Plans or Reserved Instances for predictable workloads: 1-year no-upfront saves 30-40%, 3-year saves up to 72%.
- Enable S3 Intelligent Tiering for objects accessed unpredictably — it automatically moves data to the cheapest tier.
- Use Spot Instances for fault-tolerant workloads (batch processing, CI/CD, ML training) — up to 90% savings.
- Set up AWS Budgets and billing alerts in CloudWatch immediately. A forgotten EC2 instance can cost hundreds per month.
- Delete unused Elastic IP addresses, unattached EBS volumes, and idle load balancers — these cost money even when unused.
- Use CloudFront to reduce data transfer costs from EC2/S3 — CloudFront to internet is cheaper than EC2 to internet.
- Enable RDS Auto Stop for development databases — automatically stops them after a period of inactivity to avoid idle charges.
# Set up a billing alert at $50
aws cloudwatch put-metric-alarm \
--alarm-name "AWS-Billing-50-USD" \
--alarm-description "Alert when estimated charges exceed $50" \
--namespace "AWS/Billing" \
--metric-name "EstimatedCharges" \
--statistic Maximum \
--dimensions Name=Currency,Value=USD \
--period 86400 \
--evaluation-periods 1 \
--threshold 50 \
--comparison-operator GreaterThanOrEqualToThreshold \
--alarm-actions arn:aws:sns:us-east-1:123456789012:billing-alerts
# Note: Billing metrics only exist in us-east-1!
# aws cloudwatch put-metric-alarm --region us-east-1 ...
# Find idle/unused EC2 instances (< 5% CPU over 7 days)
aws cloudwatch get-metric-statistics \
--namespace AWS/EC2 \
--metric-name CPUUtilization \
--dimensions Name=InstanceId,Value=i-0abc123 \
--start-time 2026-02-20T00:00:00Z \
--end-time 2026-02-27T00:00:00Z \
--period 86400 \
--statistics Average
# List unattached EBS volumes (potential waste)
aws ec2 describe-volumes \
--filters Name=status,Values=available \
--query "Volumes[].[VolumeId,Size,CreateTime]" \
--output table
# List unused Elastic IPs (cost $0.005/hour if unattached)
aws ec2 describe-addresses \
--query "Addresses[?AssociationId==null].[AllocationId,PublicIp]" \
--output tableFrequently Asked Questions
What is the difference between EC2 and Lambda?
EC2 provides virtual machines you manage (OS, dependencies, scaling). Lambda is serverless: you only write code, AWS handles the infrastructure. EC2 is better for long-running processes, stateful workloads, and when you need specific system configuration. Lambda is better for event-driven, short-lived tasks, and when you want zero infrastructure management and pay-per-use billing.
When should I use DynamoDB vs RDS?
Use DynamoDB when you need: massive scale (millions of requests/second), single-digit millisecond latency, a flexible schema, or when your access patterns are predictable and can be modeled with a partition key + sort key. Use RDS when you need: complex queries and joins, ACID transactions across multiple tables, a familiar SQL interface, or when your query patterns are complex and ad-hoc.
What is the cheapest way to run a static website on AWS?
S3 static website hosting + CloudFront CDN. Upload your HTML/CSS/JS to an S3 bucket, enable static hosting, create a CloudFront distribution pointing to S3, and use Route 53 for your domain. This setup costs pennies for low-traffic sites and scales to millions of users without any servers. The S3 Free Tier includes 5GB storage and 20,000 GET requests per month.
How do IAM roles work for EC2 instances?
When you attach an IAM role to an EC2 instance, the instance metadata service (IMDS) provides temporary credentials to applications running on the instance. The AWS SDK automatically reads these credentials from http://169.254.169.254/latest/meta-data/iam/security-credentials/ — you do not need to set any environment variables or config files. The credentials are automatically rotated. Never put AWS access keys in your application code or EC2 user data.
What is the difference between CloudFront and a load balancer?
CloudFront is a Content Delivery Network (CDN) that caches content at edge locations globally, reducing latency for geographically distributed users and reducing origin load. An Application Load Balancer (ALB) distributes traffic across multiple backend servers within a region for availability and scalability. Use both together: CloudFront in front of an ALB for global caching + regional load balancing. CloudFront is not a replacement for a load balancer.
How do S3 presigned URLs work?
A presigned URL is a URL with authentication parameters embedded in the query string (signature, expiration, access key ID). When a client requests this URL, S3 validates the signature and expiration without requiring any AWS credentials from the client. This is the standard pattern for: user file uploads (presigned PUT), secure file downloads (presigned GET), and third-party access to private objects without making the bucket public. Presigned URLs expire after a configured duration (15 minutes to 7 days).
What is VPC peering and when do I need it?
VPC peering creates a private network connection between two VPCs, allowing resources in either VPC to communicate using private IP addresses as if they were in the same network. You need it when you have resources in separate VPCs that need to communicate privately — for example, a shared services VPC (with databases, monitoring) connected to multiple application VPCs. Peering does not support transitive routing: if A peers with B and B peers with C, A cannot reach C through B.
What is the AWS Free Tier and what are its limits?
The AWS Free Tier has three types: 12-month free (for new accounts: 750 hours/month t2.micro EC2, 5GB S3, 25GB DynamoDB, 750 hours RDS db.t2.micro), Always free (Lambda: 1M requests/month, DynamoDB: 25GB, CloudFront: 1TB data transfer out, SNS: 1M publishes), and Trials (limited-time free trials for newer services). Be careful: the Free Tier does NOT cover all services, and exceeding limits results in charges. Always set up billing alerts.