COURSE · 8 LESSONS · 100% FREE
☁️

Cloud Computing

AWS, GCP, Azure & serverless — 8 comprehensive lessons covering infrastructure, containers, IaC, and cloud security with real CLI examples.

0Lessons
0Commands
BasicIT Knowledge
0%
You've completed 0 of 8 lessons
J/ Next
K/ Prev
Esc Collapse
/ Search
Foundations

Service Models

Cloud services are categorized into three tiers based on the level of abstraction from raw infrastructure:

IaaS vs PaaS vs SaaS
IaaS — You manage: OS, runtime, apps, data
       Provider manages: Virtualization, servers, storage, networking
       Example: AWS EC2, Azure VMs, GCP Compute Engine

PaaS — You manage: Applications, data
       Provider manages: OS, runtime, middleware, servers
       Example: AWS Elastic Beanstalk, Heroku, Google App Engine

SaaS — You manage: Nothing (just use it)
       Provider manages: Everything
       Example: Gmail, Salesforce, Slack

Regions & Availability Zones

A region is a geographic area. An Availability Zone (AZ) is one or more isolated data centers within a region, connected by low-latency links.

AWS Region/AZ Mapping
us-east-1 (N. Virginia)
  ├── us-east-1a  (AZ 1)
  ├── us-east-1b  (AZ 2)
  ├── us-east-1c  (AZ 3)
  ├── us-east-1d  (AZ 4)
  └── us-east-1f  (AZ 6)

us-west-2 (Oregon)
  ├── us-west-2a
  ├── us-west-2b
  └── us-west-2c

Pricing Models

Cloud Pricing Comparison
On-Demand   — Pay per second/minute/hour, no commitment
              Best for: dev/test, unpredictable workloads

Reserved    — 1-3 year commitment, 30-75% discount
              Best for: steady-state, predictable usage

Spot        — Bid for unused capacity, up to 90% discount
              Best for: batch jobs, fault-tolerant workloads
              Risk: can be reclaimed with 2-min notice

Dedicated   — Single-tenant physical server
              Best for: compliance, licensing requirements

Shared Responsibility Model

Who Manages What
                 IaaS          PaaS          SaaS
                ──────        ──────        ──────
Your Data        ✅            ✅            ✅
Your Apps        ✅            ✅            ❌
Your Runtime     ✅            ❌            ❌
Your OS          ✅            ❌            ❌
Your Network     ✅            ❌            ❌
Virtualization   ❌            ❌            ❌
Physical Hosts   ❌            ❌            ❌
Physical Network ❌            ❌            ❌
Physical Storage ❌            ❌            ❌

Quick Comparison of Major Providers

Provider Cheat Sheet
AWS    — Largest market share, broadest service catalog
         Billing: per-second (EC2), per-hour (others)
         33 regions, 105 AZs

GCP    — Strong in data/analytics/AI, Kubernetes-native
         Billing: per-second (VMs), sustained-use discounts
         40 regions, 121 zones

Azure  — Best enterprise integration (Active Directory, Office 365)
         Billing: per-minute (VMs), reserved instances
         60+ regions, 190+ DCs
Rule of thumb Start with On-Demand for experimentation. Move to Reserved Instances or Committed Use Discounts once you understand your steady-state baseline.
🧪 Quick Check
Which service model requires the LEAST management from the customer?

EC2 — Elastic Compute Cloud

Virtual servers in the cloud. You pick the instance type (CPU, RAM, storage, network) and pay by the second.

Launch an EC2 Instance (AWS CLI)
# Create a key pair
aws ec2 create-key-pair \
  --key-name my-key \
  --query 'KeyMaterial' \
  --output text > my-key.pem

# Launch a t3.micro instance (2 vCPU, 1 GB RAM)
aws ec2 run-instances \
  --image-id ami-0c55b159cbfafe1f0 \
  --instance-type t3.micro \
  --key-name my-key \
  --security-group-ids sg-0abc1234def567890 \
  --subnet-id subnet-0abc1234 \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=web-server}]'

# List instances
aws ec2 describe-instances \
  --query 'Reservations[*].Instances[*].[InstanceId,State.Name,PublicIpAddress]' \
  --output table

# Stop / Start / Terminate
aws ec2 stop-instances --instance-ids i-0abc1234def567890
aws ec2 start-instances --instance-ids i-0abc1234def567890
aws ec2 terminate-instances --instance-ids i-0abc1234def567890

S3 — Simple Storage Service

Object storage with 11 nines of durability. Unlimited storage, pay per GB.

S3 Operations
# Create a bucket
aws s3 mb s3://my-unique-bucket-name

# Upload a file
aws s3 cp ./index.html s3://my-bucket/index.html

# Sync a directory
aws s3 sync ./dist s3://my-bucket/static/ --delete

# List bucket contents
aws s3 ls s3://my-bucket/

# Set bucket policy (public read)
aws s3api put-bucket-policy \
  --bucket my-bucket \
  --policy '{
    "Version": "2012-10-17",
    "Statement": [{
      "Sid": "PublicRead",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-bucket/*"
    }]
  }'

# Enable versioning
aws s3api put-bucket-versioning \
  --bucket my-bucket \
  --versioning-configuration Status=Enabled

IAM — Identity and Access Management

IAM Policies & Users
# Create an IAM user
aws iam create-user --user-name deploy-bot

# Create access key for programmatic access
aws iam create-access-key --user-name deploy-bot

# Attach a managed policy
aws iam attach-user-policy \
  --user-name deploy-bot \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

# Create a custom policy (least privilege)
aws iam put-user-policy \
  --user-name deploy-bot \
  --policy-name S3BucketAccess \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject"],
      "Resource": "arn:aws:s3:::my-bucket/*"
    }]
  }'

# Create an IAM role for EC2
aws iam create-role \
  --role-name EC2S3Access \
  --assume-role-policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": {"Service": "ec2.amazonaws.com"},
      "Action": "sts:AssumeRole"
    }]
  }'

VPC & Security Groups

VPC Setup
# Create a VPC
aws ec2 create-vpc \
  --cidr-block 10.0.0.0/16 \
  --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=my-vpc}]'

# Create a subnet
aws ec2 create-subnet \
  --vpc-id vpc-0abc1234 \
  --cidr-block 10.0.1.0/24 \
  --availability-zone us-east-1a

# Create an Internet Gateway and attach
aws ec2 create-internet-gateway
aws ec2 attach-internet-gateway \
  --internet-gateway-id igw-0abc1234 \
  --vpc-id vpc-0abc1234

# Create a Security Group (web server rules)
aws ec2 create-security-group \
  --group-name web-sg \
  --description "Web server SG" \
  --vpc-id vpc-0abc1234

# Allow HTTP
aws ec2 authorize-security-group-ingress \
  --group-id sg-0abc1234 \
  --protocol tcp \
  --port 80 \
  --cidr 0.0.0.0/0

# Allow SSH (restrict to your IP)
aws ec2 authorize-security-group-ingress \
  --group-id sg-0abc1234 \
  --protocol tcp \
  --port 22 \
  --cidr 203.0.113.50/32

# Allow HTTPS
aws ec2 authorize-security-group-ingress \
  --group-id sg-0abc1234 \
  --protocol tcp \
  --port 443 \
  --cidr 0.0.0.0/0
💡
Key pair tip Always use --query 'KeyMaterial' --output text when creating key pairs — AWS only shows the private key once. Store it in ~/.ssh/ and set permissions to chmod 400.
← PrevCloud Fundamentals

AWS Lambda

Run code without provisioning servers. Pay only for compute time used (rounded to the nearest millisecond).

Lambda Function (Python 3.12)
import json
import boto3

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Visitors')

def lambda_handler(event, context):
    # Parse request
    body = json.loads(event.get('body', '{}'))
    path = event.get('path', '/')

    if event['httpMethod'] == 'GET' and path == '/count':
        response = table.get_item(Key={'id': 'counter'})
        count = response.get('Item', {}).get('count', 0)
        return {
            'statusCode': 200,
            'headers': {'Content-Type': 'application/json'},
            'body': json.dumps({'count': count})
        }

    if event['httpMethod'] == 'POST' and path == '/increment':
        response = table.update_item(
            Key={'id': 'counter'},
            UpdateExpression='SET #c = if_not_exists(#c, :zero) + :inc',
            ExpressionAttributeNames={'#c': 'count'},
            ExpressionAttributeValues={':inc': 1, ':zero': 0},
            ReturnValues='ALL_NEW'
        )
        return {
            'statusCode': 200,
            'body': json.dumps({'count': response['Attributes']['count']})
        }

    return {'statusCode': 404, 'body': json.dumps({'error': 'Not found'})}
Deploy Lambda (CLI)
# Create the function
aws lambda create-function \
  --function-name visitor-counter \
  --runtime python3.12 \
  --role arn:aws:iam::123456789012:role/LambdaDynamoRole \
  --handler index.lambda_handler \
  --zip-file fileb://function.zip \
  --timeout 10 \
  --memory-size 128

# Update function code
aws lambda update-function-code \
  --function-name visitor-counter \
  --zip-file fileb://function.zip

# Invoke locally for testing
aws lambda invoke \
  --function-name visitor-counter \
  --payload '{"httpMethod":"GET","path":"/count"}' \
  output.json
cat output.json

API Gateway

API Gateway + Lambda Integration
# Create a REST API
aws apigateway create-rest-api \
  --name 'VisitorAPI' \
  --description 'Simple visitor counter API'

# Create resource (/count)
aws apigateway create-resource \
  --rest-api-id abc123 \
  --parent-id  \
  --path-part 'count'

# Create GET method
aws apigateway put-method \
  --rest-api-id abc123 \
  --resource-id def456 \
  --http-method GET \
  --authorization-type NONE

# Set Lambda integration
aws apigateway put-integration \
  --rest-api-id abc123 \
  --resource-id def456 \
  --http-method GET \
  --type AWS_PROXY \
  --integration-http-method POST \
  --uri arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789012:function:visitor-counter/invocations

# Deploy to stage
aws apigateway create-deployment \
  --rest-api-id abc123 \
  --stage-name prod

# Grant API Gateway permission to invoke Lambda
aws lambda add-permission \
  --function-name visitor-counter \
  --statement-id apigateway-invoke \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-arn "arn:aws:execute-api:us-east-1:123456789012:abc123/*"

DynamoDB

DynamoDB Table Operations
# Create a table
aws dynamodb create-table \
  --table-name Visitors \
  --attribute-definitions AttributeName=id,AttributeType=S \
  --key-schema AttributeName=id,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST \
  --table-class STANDARD

# Put an item
aws dynamodb put-item \
  --table-name Visitors \
  --item '{"id": {"S": "counter"}, "count": {"N": "0"}}'

# Get an item
aws dynamodb get-item \
  --table-name Visitors \
  --key '{"id": {"S": "counter"}}'

# Query with conditions
aws dynamodb query \
  --table-name Visitors \
  --key-condition-expression "id = :id" \
  --expression-attribute-values '{":id": {"S": "counter"}}'

# Scan all items
aws dynamodb scan --table-name Visitors

Event-Driven Architecture Pattern

S3 → Lambda → DynamoDB Pipeline
# S3 bucket notification triggers Lambda on upload
aws s3api put-bucket-notification-configuration \
  --bucket my-upload-bucket \
  --notification-configuration '{
    "LambdaFunctionConfigurations": [{
      "Events": ["s3:ObjectCreated:*"],
      "LambdaFunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:process-upload"
    }]
  }'

# The Lambda function processes the S3 event:
# {
#   "Records": [{
#     "eventSource": "aws:s3",
#     "s3": {
#       "bucket": {"name": "my-upload-bucket"},
#       "object": {"key": "uploads/data.csv", "size": 1024}
#     }
#   }]
# }
⚠️
Lambda limits Max 15 min execution time, 10 GB memory, 10 GB ephemeral storage. For longer jobs, use Step Functions to orchestrate multiple Lambda invocations.
← PrevAWS Core
Intermediate

Container Basics (Docker → Cloud)

Dockerfile for a Web App
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
EXPOSE 3000
USER node
CMD ["node", "dist/server.js"]

Amazon ECR — Container Registry

Push Image to ECR
# Login to ECR
aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com

# Create repository
aws ecr create-repository --repository-name my-app

# Build, tag, push
docker build -t my-app .
docker tag my-app:latest 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:latest
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:latest

Amazon ECS — Elastic Container Service

ECS Fargate Deployment
# Create ECS cluster
aws ecs create-cluster --cluster-name my-cluster

# Register task definition (Fargate)
aws ecs register-task-definition \
  --family my-app-task \
  --network-mode awsvpc \
  --requires-compatibilities FARGATE \
  --cpu '256' \
  --memory '512' \
  --execution-role-arn arn:aws:iam::123456789012:role/ecsTaskExecutionRole \
  --container-definitions '[
    {
      "name": "my-app",
      "image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:latest",
      "portMappings": [{"containerPort": 3000, "protocol": "tcp"}],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/my-app",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "ecs"
        }
      }
    }
  ]'

# Run the service
aws ecs run-task \
  --cluster my-cluster \
  --task-definition my-app-task \
  --launch-type FARGATE \
  --network-configuration '{
    "awsvpcConfiguration": {
      "subnets": ["subnet-0abc1234"],
      "securityGroups": ["sg-0abc1234"],
      "assignPublicIp": "ENABLED"
    }
  }'

# Create a service (keeps desired count running)
aws ecs create-service \
  --cluster my-cluster \
  --service-name my-app-service \
  --task-definition my-app-task \
  --desired-count 2 \
  --launch-type FARGATE \
  --network-configuration '{
    "awsvpcConfiguration": {
      "subnets": ["subnet-0abc1234"],
      "securityGroups": ["sg-0abc1234"],
      "assignPublicIp": "ENABLED"
    }
  }'

Google Cloud Run

Deploy to Cloud Run
# Build and deploy directly from source
gcloud run deploy my-app \
  --source . \
  --platform managed \
  --region us-central1 \
  --allow-unauthenticated \
  --memory 512Mi \
  --cpu 1 \
  --min-instances 0 \
  --max-instances 10

# Deploy a pre-built container image
gcloud run deploy my-app \
  --image gcr.io/my-project/my-app:latest \
  --platform managed \
  --region us-central1 \
  --port 3000

# Set environment variables
gcloud run services update my-app \
  --set-env-vars="DATABASE_URL=postgres://...,NODE_ENV=production"

# View logs
gcloud run services logs read my-app --limit 50

Auto-Scaling Comparison

Scaling Behavior
ECS Fargate:
  min/max per service
  Target tracking: CPU > 70% → scale up
  Scale-out: ~60-90 seconds
  Scale-in: ~5-15 minutes (cooldown)

Cloud Run:
  min-instances: 0 (scale to zero!)
  max-instances: 1000
  Scale-out: ~100ms per instance
  Concurrency: 1-1000 requests per instance

EKS (Kubernetes HPA):
  minReplicas / maxReplicas
  Metrics: CPU, memory, custom (Prometheus)
  Scale-out: ~30 seconds (depends on pods pending)
When to choose which Cloud Run for simple HTTP workloads that need scale-to-zero. ECS Fargate for long-running services, background workers, or when you need VPC integration. EKS when you need full Kubernetes control.
🧪 Quick Check
Which container service can scale down to zero instances when idle?
← PrevServerless

Compute Engine — VMs

GCE Instance Management
# Create a VM
gcloud compute instances create my-vm \
  --zone=us-central1-a \
  --machine-type=e2-micro \
  --image-family=debian-12 \
  --image-project=debian-cloud \
  --boot-disk-size=20GB \
  --tags=http-server,https-server

# SSH into the VM
gcloud compute ssh my-vm --zone=us-central1-a

# List all instances
gcloud compute instances list

# Stop / Start / Delete
gcloud compute instances stop my-vm --zone=us-central1-a
gcloud compute instances start my-vm --zone=us-central1-a
gcloud compute instances delete my-vm --zone=us-central1-a

# Create a disk and attach
gcloud compute disks create data-disk \
  --size=100GB \
  --type=pd-balanced \
  --zone=us-central1-a

gcloud compute instances attach-disk my-vm \
  --disk=data-disk \
  --zone=us-central1-a

Cloud Storage — Object Storage

GCS Bucket Operations
# Create a bucket (globally unique name)
gsutil mb -l us-central1 gs://my-unique-bucket-name

# Upload files
gsutil cp ./index.html gs://my-bucket/
gsutil -m cp -r ./static/* gs://my-bucket/static/

# Make publicly readable
gsutil iam ch allUsers:objectViewer gs://my-bucket

# Set lifecycle rule (delete after 90 days)
gsutil lifecycle set lifecycle.json gs://my-bucket

# lifecycle.json content:
# {
#   "rule": [{
#     "action": {"type": "Delete"},
#     "condition": {"age": 90}
#   }]
# }

# Generate a signed URL (temporary access)
gsutil signurl -d 1h service-account-key.json gs://my-bucket/file.pdf

Cloud Functions — Serverless

Deploy a Cloud Function
# Deploy HTTP-triggered function (Gen 2)
gcloud functions deploy hello-http \
  --gen2 \
  --runtime=nodejs20 \
  --region=us-central1 \
  --source=./function-source \
  --entry-point=helloHttp \
  --trigger-http \
  --allow-unauthenticated \
  --memory=256MB

# Deploy event-triggered function
gcloud functions deploy process-upload \
  --gen2 \
  --runtime=nodejs20 \
  --region=us-central1 \
  --source=./function-source \
  --entry-point=processUpload \
  --trigger-event-filters="type=google.cloud.storage.object.v1.finalized" \
  --trigger-event-filters="bucket=my-upload-bucket"

# View logs
gcloud functions logs read hello-http --limit 20

BigQuery — Data Analytics

BigQuery SQL Operations
# Query a public dataset
bq query --use_legacy_sql=false '
SELECT
  name,
  SUM(number) AS total,
  MIN(year) AS first_appeared,
  MAX(year) AS last_appeared
FROM `bigquery-public-data.usa_names.usa_1910_current`
WHERE state = "TX"
GROUP BY name
ORDER BY total DESC
LIMIT 10
'

# Create a dataset
bq mk --dataset my-project:analytics

# Load CSV into a table
bq load \
  --source_format=CSV \
  --autodetect \
  analytics.sales_data \
  ./sales.csv

# Export to GCS
bq extract \
  'my-project:analytics.sales_data' \
  'gs://my-bucket/exports/sales_*.csv'

Networking — Firewall Rules

GCP Firewall Rules
# Allow HTTP traffic
gcloud compute firewall-rules create allow-http \
  --allow=tcp:80 \
  --source-ranges=0.0.0.0/0 \
  --target-tags=http-server

# Allow SSH from your IP
gcloud compute firewall-rules create allow-ssh \
  --allow=tcp:22 \
  --source-ranges=203.0.113.50/32 \
  --target-tags=ssh-allowed

# List firewall rules
gcloud compute firewall-rules list
ℹ️
GCP sustained-use discount If you run a VM for more than 25% of a month, GCP automatically discounts the remaining time (up to 30% for running 24/7). No upfront commitment needed.
← PrevContainers in Cloud
Advanced

Azure Virtual Machines

VM Lifecycle (Azure CLI)
# Create a resource group
az group create --name myResourceGroup --location eastus

# Create a Linux VM
az vm create \
  --resource-group myResourceGroup \
  --name myVM \
  --image Ubuntu2204 \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-value ~/.ssh/id_rsa.pub \
  --public-ip-sku Standard

# List VMs
az vm list --output table

# Start / Stop / Restart
az vm start --resource-group myResourceGroup --name myVM
az vm stop --resource-group myResourceGroup --name myVM
az vm restart --resource-group myResourceGroup --name myVM

# Delete the VM
az vm delete --resource-group myResourceGroup --name myVM --yes

# Create a data disk
az vm disk attach \
  --resource-group myResourceGroup \
  --vm-name myVM \
  --name data-disk \
  --size-gb 100 \
  --new

Blob Storage

Azure Blob Operations
# Create a storage account
az storage account create \
  --name mystorageaccount123 \
  --resource-group myResourceGroup \
  --location eastus \
  --sku Standard_LRS

# Create a container
az storage container create \
  --name mycontainer \
  --account-name mystorageaccount123

# Upload a blob
az storage blob upload \
  --account-name mystorageaccount123 \
  --container-name mycontainer \
  --name ./index.html \
  --file ./index.html

# List blobs
az storage blob list \
  --account-name mystorageaccount123 \
  --container-name mycontainer \
  --output table

# Generate a SAS URL (shared access signature)
az storage blob generate-sas \
  --account-name mystorageaccount123 \
  --container-name mycontainer \
  --name index.html \
  --permissions r \
  --expiry 2026-12-31 \
  --full-uri

Azure Functions — Serverless

Deploy Azure Function
# Create a Function App
az functionapp create \
  --resource-group myResourceGroup \
  --consumption-plan-location eastus \
  --runtime node \
  --runtime-version 20 \
  --functions-version 4 \
  --name myFunctionApp123 \
  --storage-account mystorageaccount123

# Deploy from local directory
az functionapp deployment source config-zip \
  --resource-group myResourceGroup \
  --name myFunctionApp123 \
  --src ./function-app.zip

# Configure app settings
az functionapp config appsettings set \
  --resource-group myResourceGroup \
  --name myFunctionApp123 \
  --settings "DATABASE_URL=postgres://..." "API_KEY=secret123"

# View logs
az functionapp logs tail \
  --resource-group myResourceGroup \
  --name myFunctionApp123

Azure AD (Entra ID) — Identity

Azure AD Operations
# Create a service principal
az ad sp create-for-rbac \
  --name myServicePrincipal \
  --role Contributor \
  --scopes /subscriptions/{subscription-id}

# Output:
# {
#   "appId": "xxxxxxxx-xxxx-xxxx-xxxx",
#   "displayName": "myServicePrincipal",
#   "password": "xxxx-xxxx",
#   "tenant": "xxxxxxxx-xxxx-xxxx-xxxx"
# }

# List service principals
az ad sp list --display-name myServicePrincipal

# Grant a role assignment
az role assignment create \
  --assignee {app-id} \
  --role "Storage Blob Data Contributor" \
  --scope /subscriptions/{sub-id}/resourceGroups/myRG/providers/Microsoft.Storage/storageAccounts/mystorage

Network Security Group

Network Security Group
# Create an NSG
az network nsg create \
  --resource-group myResourceGroup \
  --name myNSG

# Allow HTTP
az network nsg rule create \
  --resource-group myResourceGroup \
  --nsg-name myNSG \
  --name AllowHTTP \
  --priority 100 \
  --destination-port-ranges 80 \
  --access Allow \
  --protocol Tcp

# Allow SSH (restrict to IP)
az network nsg rule create \
  --resource-group myResourceGroup \
  --nsg-name myNSG \
  --name AllowSSH \
  --priority 110 \
  --destination-port-ranges 22 \
  --access Allow \
  --protocol Tcp \
  --source-address-prefixes 203.0.113.50/32

# Associate NSG with a subnet
az network vnet subnet update \
  --resource-group myResourceGroup \
  --vnet-name myVNet \
  --name mySubnet \
  --network-security-group myNSG
💡
Azure free tier You get $200 credit for 30 days + 750 hours of B1S VMs + 5 GB Blob storage + 1M Lambda executions free for 12 months.
← PrevGCP Essentials

Terraform — HCL (HashiCorp Configuration Language)

Terraform: AWS VPC + EC2
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/vpc.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

provider "aws" {
  region = "us-east-1"
}

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "main-vpc"
  }
}

resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = "us-east-1a"
  map_public_ip_on_launch = true

  tags = {
    Name = "public-subnet"
  }
}

resource "aws_security_group" "web" {
  name        = "web-sg"
  description = "Allow HTTP and SSH"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["203.0.113.50/32"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_instance" "web" {
  ami                    = "ami-0c55b159cbfafe1f0"
  instance_type          = "t3.micro"
  key_name               = "my-key"
  subnet_id              = aws_subnet.public.id
  vpc_security_group_ids = [aws_security_group.web.id]

  tags = {
    Name = "web-server"
  }
}

Terraform Workflow

Terraform Workflow
# Initialize (download providers)
terraform init

# Format config files
terraform fmt

# Validate syntax
terraform validate

# Preview changes
terraform plan

# Apply changes
terraform apply

# Destroy all resources
terraform destroy

# Show current state
terraform show

# List all resources in state
terraform state list

# Import existing resource
terraform import aws_instance.existing i-0abc1234def567890

AWS CloudFormation — JSON/YAML Templates

CloudFormation Template
AWSTemplateFormatVersion: '2010-09-09'
Description: Simple EC2 Instance

Parameters:
  InstanceType:
    Type: String
    Default: t3.micro
    AllowedValues:
      - t3.micro
      - t3.small
      - t3.medium

Resources:
  WebServer:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      ImageId: ami-0c55b159cbfafe1f0
      KeyName: my-key
      SecurityGroups:
        - !Ref WebSecurityGroup
      Tags:
        - Key: Name
          Value: WebServer

  WebSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow HTTP and SSH
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 203.0.113.50/32

Outputs:
  PublicIp:
    Value: !Ref WebServer
    Description: Public IP of the web server

CloudFormation CLI

CloudFormation CLI
# Deploy a stack
aws cloudformation deploy \
  --template-file template.yaml \
  --stack-name my-web-stack \
  --parameter-overrides InstanceType=t3.small

# Update a stack
aws cloudformation update-stack \
  --template-file template.yaml \
  --stack-name my-web-stack

# Describe stack events (for debugging)
aws cloudformation describe-stack-events \
  --stack-name my-web-stack

# Delete a stack
aws cloudformation delete-stack --stack-name my-web-stack

Pulumi — Real Programming Languages

Pulumi: AWS VPC (TypeScript)
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const vpc = new aws.ec2.Vpc("main", {
    cidrBlock: "10.0.0.0/16",
    enableDnsHostnames: true,
    enableDnsSupport: true,
    tags: { Name: "main-vpc" },
});

const subnet = new aws.ec2.Subnet("public", {
    vpcId: vpc.id,
    cidrBlock: "10.0.1.0/24",
    availabilityZone: "us-east-1a",
    mapPublicIpOnLaunch: true,
    tags: { Name: "public-subnet" },
});

const webSg = new aws.ec2.SecurityGroup("web", {
    description: "Allow HTTP and SSH",
    vpcId: vpc.id,
    ingress: [
        { protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] },
        { protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["203.0.113.50/32"] },
    ],
    egress: [
        { protocol: "-1", fromPort: 0, toPort: 0, cidrBlocks: ["0.0.0.0/0"] },
    ],
});

const web = new aws.ec2.Instance("web", {
    ami: "ami-0c55b159cbfafe1f0",
    instanceType: "t3.micro",
    keyName: "my-key",
    subnetId: subnet.id,
    vpcSecurityGroupIds: [webSg.id],
    tags: { Name: "web-server" },
});

export const publicIp = web.publicIp;

State Management

Remote State Setup (Terraform)
# Create S3 bucket for state
aws s3api create-bucket \
  --bucket my-terraform-state \
  --region us-east-1

# Enable versioning
aws s3api put-bucket-versioning \
  --bucket my-terraform-state \
  --versioning-configuration Status=Enabled

# Enable encryption
aws s3api put-bucket-encryption \
  --bucket my-terraform-state \
  --server-side-encryption-configuration '{
    "Rules": [{"ApplyServerSideEncryptionByDefault": {"SSEAlgorithm": "AES256"}}]
  }'

# Create DynamoDB table for state locking
aws dynamodb create-table \
  --table-name terraform-locks \
  --attribute-definitions AttributeName=LockID,AttributeType=S \
  --key-schema AttributeName=LockID,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST
🚫
IaC golden rules Never edit state files manually. Always use remote state with locking. Use workspaces for dev/staging/prod isolation. Run plan before every apply.
← PrevAzure Essentials

IAM Best Practices

Least Privilege Principle
# BAD: AdminAccess for everyone
# aws iam attach-user-policy --user-name dev --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

# GOOD: Scoped policy per role
aws iam create-policy \
  --policy-name S3DeployPolicy \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Sid": "AllowS3Bucket",
        "Effect": "Allow",
        "Action": ["s3:PutObject", "s3:GetObject", "s3:ListBucket"],
        "Resource": [
          "arn:aws:s3:::my-app-bucket",
          "arn:aws:s3:::my-app-bucket/*"
        ]
      },
      {
        "Sid": "AllowCloudFrontInvalidation",
        "Effect": "Allow",
        "Action": "cloudfront:CreateInvalidation",
        "Resource": "arn:aws:cloudfront::123456789012:distribution/E1234567890"
      }
    ]
  }'

# Use IAM Access Analyzer
aws accessanalyzer list-findings \
  --analyzer-arn arn:aws:access-analyzer:us-east-1:123456789012:analyzer/ConsoleAnalyzer-xxx

Secrets Management

AWS Secrets Manager
# Store a secret
aws secretsmanager create-secret \
  --name prod/database/credentials \
  --secret-string '{
    "username": "admin",
    "password": "S3cureP@ss!",
    "engine": "postgres",
    "host": "mydb.abc123.us-east-1.rds.amazonaws.com",
    "port": 5432
  }'

# Retrieve a secret
aws secretsmanager get-secret-value \
  --secret-id prod/database/credentials

# Rotate a secret (auto-rotation)
aws secretsmanager rotate-secret \
  --secret-id prod/database/credentials \
  --rotation-lambda-arn arn:aws:lambda:us-east-1:123456789012:function:rotate-db-password \
  --rotation-rules '{"AutomaticallyAfterDays": 30}'

SSM Parameter Store

AWS SSM Parameter Store
# Store parameters
aws ssm put-parameter \
  --name "/prod/app/api-url" \
  --value "https://api.myapp.com" \
  --type "String"

aws ssm put-parameter \
  --name "/prod/app/db-password" \
  --value "S3cureP@ss!" \
  --type "SecureString"

# Retrieve parameters
aws ssm get-parameter --name "/prod/app/api-url"
aws ssm get-parameter --name "/prod/app/db-password" --with-decryption

# Get all parameters under a path
aws ssm get-parameters-by-path \
  --path "/prod/app" \
  --recursive \
  --with-decryption

CI/CD Pipelines

GitHub Actions: Deploy to AWS
# .github/workflows/deploy.yml
name: Deploy to AWS
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Build
        run: npm run build

      - name: Sync to S3
        run: aws s3 sync ./dist s3://my-bucket/static/ --delete

      - name: Invalidate CloudFront cache
        run: |
          aws cloudfront create-invalidation \
            --distribution-id E1234567890 \
            --paths "/*"

AWS CodePipeline

AWS CodePipeline
# Create a pipeline
aws codepipeline create-pipeline \
  --pipeline '{
    "name": "my-pipeline",
    "roleArn": "arn:aws:iam::123456789012:role/CodePipelineRole",
    "artifactStore": {
      "type": "S3",
      "location": "my-pipeline-artifacts"
    },
    "stages": [
      {
        "name": "Source",
        "actions": [{
          "name": "GitHub",
          "actionTypeId": {"category": "Source", "owner": "ThirdParty", "provider": "GitHub", "version": "1"},
          "configuration": {"Owner": "mayank-dev-15", "Repo": "my-app", "Branch": "main", "OAuthToken": "{{secrets:github-token}}"}
        }]
      },
      {
        "name": "Build",
        "actions": [{
          "name": "CodeBuild",
          "actionTypeId": {"category": "Build", "owner": "AWS", "provider": "CodeBuild", "version": "1"},
          "configuration": {"ProjectName": "my-build-project"}
        }]
      },
      {
        "name": "Deploy",
        "actions": [{
          "name": "Deploy",
          "actionTypeId": {"category": "Deploy", "owner": "AWS", "provider": "ECS", "version": "1"},
          "configuration": {"ClusterName": "my-cluster", "ServiceName": "my-service"}
        }]
      }
    ]
  }'

Monitoring & Observability

CloudWatch & Alarms
# Put a custom metric
aws cloudwatch put-metric-data \
  --namespace "MyApp" \
  --metric-name "RequestCount" \
  --value 42 \
  --dimensions "Service=api"

# Create an alarm for high CPU
aws cloudwatch put-metric-alarm \
  --alarm-name "high-cpu" \
  --metric-name "CPUUtilization" \
  --namespace "AWS/EC2" \
  --statistic Average \
  --period 300 \
  --threshold 80 \
  --comparison-operator GreaterThanThreshold \
  --evaluation-periods 2 \
  --dimensions "Name=InstanceId,Value=i-0abc1234" \
  --alarm-actions "arn:aws:sns:us-east-1:123456789012:alerts"

# Get logs
aws logs get-log-events \
  --log-group-name "/ecs/my-app" \
  --log-stream-name "ecs/my-app/abc123"

# Create a dashboard
aws cloudwatch put-dashboard \
  --dashboard-name "MyApp" \
  --dashboard-body '{
    "widgets": [{
      "type": "metric",
      "x": 0, "y": 0, "width": 12, "height": 6,
      "properties": {
        "metrics": [["AWS/EC2", "CPUUtilization"]],
        "period": 300,
        "stat": "Average",
        "title": "EC2 CPU"
      }
    }]
  }'

Infrastructure Monitoring Checklist

Monitoring Checklist
✅ Compute
  - CPU/Memory utilization (per instance/container)
  - Disk I/O and free space
  - Network in/out bytes

✅ Storage
  - S3 bucket sizes and request counts
  - EBS volume read/write ops and queue depth

✅ Database
  - RDS connections, query latency, free storage
  - DynamoDB read/write capacity and throttled requests

✅ Application
  - Request latency (p50, p95, p99)
  - Error rates (4xx, 5xx)
  - Active connections

✅ Security
  - CloudTrail API call logging
  - GuardDuty threat findings
  - IAM credential rotation status
  - Unusual login patterns
⚠️
Security baseline Enable CloudTrail in all regions. Use AWS Config for compliance rules. GuardDuty for threat detection. Rotate access keys every 90 days. Use MFA on all human accounts — no exceptions.
← PrevInfrastructure as Code

📚 Resources & Further Learning

📖
AWS Documentation
Official AWS docs — services, pricing, architecture, and best practices.
docs.aws.amazon.com →
📘
GCP Documentation
Google Cloud docs — guides, tutorials, and API references.
cloud.google.com →
🎯
Azure Documentation
Microsoft Azure docs — architecture, quickstarts, and samples.
learn.microsoft.com →
🧪
Terraform Docs
HashiCorp Terraform — language docs, providers, and tutorials.
hashicorp.com →
Cloud Resume Challenge
Hands-on project to learn cloud — build a resume website on AWS/GCP/Azure.
cloudresumechallenge.dev →
📦
Docker Documentation
Container fundamentals — Dockerfile, Compose, and Docker Hub.
docs.docker.com →
AI
Cloud Tutor
ZenMux · GLM 4.7 Flash
Ask me anything about Cloud Computing! I can help with AWS/GCP/Azure CLI commands, infrastructure design, serverless patterns, or explain any concept from the lessons above.