Cheat sheets
Each lesson ends with its essentials on one card. They're all here, in course order. Print one card with its button, or print this page for all of them.
The roadmap
00 – Start here
| Idea | In one line |
|---|---|
| DevOps | Getting changes to users safely and often, and keeping them running |
| This course | One app, QuickCart, from a laptop to millions of users |
| Every lesson | Problem, idea, see it, do it, check yourself, cheat sheet |
| Every tool | The answer to one problem at one size. Meet the problem first |
| Jumping in | Read the lessons listed under Read first, then start anywhere |
uname -sm | Your system and chip: x86_64 for Intel or AMD, arm64 for ARM |
df -h / | Free disk space. Look at Avail. You want 20 GB or more |
| Cost | Every lab has a badge. The paid ones have a clean-up step |
How this course works
00 – Start here
| Idea | In one line |
|---|---|
| The six parts | Problem, idea, see it, do it, check yourself, cheat sheet |
| Root user | The sign-up email. MFA on, no access keys, almost never used |
| IAM Identity Center | Where your everyday admin login lives. Credentials expire after hours |
| Access portal | The awsapps.com/start address you sign in from. Bookmark it |
| Permission set | A named bundle of permissions, like AdministratorAccess |
| Budget | Emails you at 85% and 100%. It does not stop anything |
| Free badge | Nothing costs money |
| Pennies badge | A few cents at most |
| Costs money badge | Charges every hour until the clean-up step |
| Region | Always us-east-1 in this course |
| Free tier | Depends on when the account was made. Check Billing → Free Tier |
Set up your computer
00 – Start here
| Command | What it does |
|---|---|
brew install NAME | Install a terminal tool |
brew install --cask NAME | Install a desktop app (macOS only) |
brew upgrade | Update everything Homebrew installed |
aws configure sso | Set up a profile that signs in through IAM Identity Center |
aws sso login --profile quickcart | Sign in again when the session expires |
aws sso logout | Sign out now |
aws sts get-caller-identity | Who am I, and in which account? |
aws configure list | Which profile, keys and region are in use, and where from |
export AWS_PROFILE=quickcart | Use this profile in this terminal |
~/.bashrc or ~/.zprofile | Settings your terminal reads when it opens: Linux and WSL, or macOS |
wsl --install | Windows: install WSL 2 with Ubuntu, from an administrator PowerShell |
| Credential order | --profile, then key variables, then AWS_PROFILE, then default |
The DevOps lifecycle
01 – How software ships
| Stage | In one line |
|---|---|
| Plan | Decide what to build next, using what monitoring showed |
| Code | Write it, and have it reviewed |
| Build | Turn code into something that runs |
| Test | Check it works and broke nothing else |
| Release | Decide it may go to users |
| Deploy | Put it on the servers |
| Operate | Keep it running |
| Monitor | Watch errors, speed and usage. Feeds the next plan |
| Batch size | How many changes travel together. Smaller is safer |
| Value stream map | Working time vs waiting time, stage by stage |
git log --reverse --grep=WORD | When the work on a change started |
Measuring delivery
01 – How software ships
| Metric or command | In one line |
|---|---|
| Deployment frequency | How often you deploy to production. Speed |
| Lead time for changes | Commit to running in production. Speed |
| Change failure rate | Share of deployments that needed a fix. Stability |
| Time to restore | From a failure starting to it being fixed. Stability |
| The rule | Watch all four together. Smaller batches improve all of them |
| Never | Use them to rank individual people |
git rev-list --count --first-parent --since="30 days ago" main | How many changes reached main |
git log --reverse "ID^1..ID^2" | The commits a merge brought in, oldest first |
git log -i --grep="revert" | Changes that undid earlier ones |
Reliability targets
01 – How software ships
| Term or command | In one line |
|---|---|
| SLI | What you measure: good events ÷ all events |
| SLO | The target for the SLI over a window, like 99.9% over 30 days |
| Error budget | 100% minus the SLO. The failure you can afford |
| SLA | A promise to customers with a penalty. Looser than your SLO |
| 99.9% | About 43 minutes of failure a month |
| 99.99% | About 4 minutes a month |
| Good vs bad | 5xx and too slow are bad. 4xx usually aren’t |
| Error budget policy | Agreed in advance: what happens as the budget runs out |
grep -c "TEXT" FILE | Count matching lines |
grep -E "PATTERN" FILE | Match a pattern, like 5[0-9][0-9] |
Ways to release
01 – How software ships
| Strategy or idea | In one line |
|---|---|
| Recreate | Stop everything, start the new version. Downtime built in |
| Rolling | Replace servers a few at a time. No extra cost; bad versions spread until noticed |
| Blue/green | Full second copy, then switch all at once. Instant undo; everyone hit if bad |
| Canary | Small share first, grow while watching. Fewest customers hit |
| Abort criteria | The rule for stopping a canary, written before you start |
| Expand, then contract | Database changes that work with old and new versions at once |
| Rolling back | Undoes code, not what the bad version already did |
| Lambda alias | A name pointing at one version, or two with weights |
aws lambda invoke --function-name NAME:ALIAS out.json | Call through an alias; ExecutedVersion says who answered |
Feature flags
01 – How software ships
| Idea | In one line |
|---|---|
| Feature flag | An if whose answer comes from a setting, not the code |
| Deploy vs release | Deploy puts code on servers; the flag decides who gets the feature |
| Percentage rollout | A canary done with a flag. Buckets keep customers in as it grows |
| Bucket | A hash of the customer’s ID, from 0 to 99. Same customer, same bucket |
| Kill switch | Off for everyone, on the next request, with no deploy |
| Safe default | Can’t read the settings? Everything is off |
| Flag debt | Old flags left in the code. Remove them soon after full rollout |
| Never | Reuse an old flag’s name |
| Real flag services | Such as AWS AppConfig: change a flag with a click, every server follows |
What the cloud is
02 – The cloud, simply
| Term | In one line |
|---|---|
| The cloud | Renting computers by the second instead of buying them |
| Pay for what exists | A forgotten resource keeps costing money until you delete it |
| Region | A separate group of data centres, like us-east-1. Most things live in one |
| Global service | Not tied to a region. IAM is the main one |
| Region selector | Top right of the console. Empty list? Check it first |
| Account ID | 12 digits, in the account menu. Shown in this course as 111122223333 |
| Shared responsibility | AWS secures the cloud; you secure what you put in it |
| Budget | Emails you when spending crosses a limit. Never stops anything |
| Every lab | Ends with a clean-up step. Always do it |
Who can do what: IAM
02 – The cloud, simply
| Term | In one line |
|---|---|
| Root user | The account’s first login. No IAM policy can limit it. MFA on, no access keys, almost never used |
| IAM user | A named identity with long-lived credentials |
| IAM group | Users that share the same policies |
| IAM role | No password, no keys. Assumed, and hands out temporary credentials |
| Policy | JSON: Effect + Action + Resource in each statement |
Version | Always 2012-10-17 |
| Implicit deny | Nothing allowed it, so it’s denied. The default |
| Explicit deny | A matching Deny. Beats every Allow |
| Least privilege | Exactly the actions and resources needed, nothing more |
| Trust policy | Who may assume a role |
| Permissions policy | What the role can do |
| S3 ARNs | Bucket: arn:aws:s3:::name. Files: arn:aws:s3:::name/* |
| AccessDenied? | Look for the missing Allow, or a Deny, in the policies. Never switch to root |
Regions and zones
02 – The cloud, simply
| Term | In one line |
|---|---|
| Region | A separate part of the world with its own copy of AWS, such as us-east-1 |
| Availability zone | One or more buildings in a region, with their own power and network |
| Choosing a region | Distance to customers, the law, price, and which services it has |
| Global | IAM, Route 53: no region |
| Regional | VPC, S3 bucket, load balancer: spread across zones for you |
| Zonal | Subnet, EC2 server, EBS disk, NAT gateway: you spread them yourself |
| Two zones | Protects against one building failing. Each zone must carry the full load alone |
| Two regions | Protects against a whole region failing. Much more work |
| Zone name vs ID | us-east-1a can differ between accounts; use1-az2 never does |
| Resources vanished? | Check the region selector first |
What is a VPC?
03 – Networking
| Term | In one line |
|---|---|
| VPC | Your private network on AWS. Closed by default |
| Subnet | A slice of the VPC, in one availability zone |
| Route table | The rules for where a subnet’s traffic goes |
| Public subnet | Route table sends 0.0.0.0/0 to the internet gateway |
| Private subnet | Route table sends 0.0.0.0/0 to a NAT gateway — out only |
| Isolated subnet | No 0.0.0.0/0 route at all |
local route | Always there. Everything in the VPC can reach everything else |
/16 and /24 | 65,536 addresses and 256 addresses |
| Can’t reach something? | Check the route table first |
Subnets and IP ranges
03 – Networking
| Term | In one line |
|---|---|
| IPv4 address | 32 bits, written as four numbers from 0 to 255 |
/n | The first n bits are fixed. 32 − n bits are free |
| Addresses in a range | 2 to the power of the free bits |
/16 · /20 · /24 · /28 | 65,536 · 4,096 · 256 · 16 |
| Boundary rule | A block starts on a multiple of its own size |
| Reserved in every subnet | First four and the last: 5 addresses. A /24 has 251 usable |
| Subnet size limits | From /16 to /28 |
| Private ranges | 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 |
| Growth | /16 per VPC, leave most of it unused. You can’t resize a subnet |
| Several VPCs | Never overlap: 10.0.0.0/16, 10.1.0.0/16, 10.2.0.0/16 |
Routes, gateways and NAT
03 – Networking
| Term | In one line |
|---|---|
| Route table | Destination → target rules for a subnet. One table per subnet |
local | The VPC’s own range. Always there, can’t be removed |
| Most specific wins | Of all matching routes, the one with the biggest number after the slash |
| Main route table | Used by any subnet not associated with another. Keep it isolated |
| Public subnet | 0.0.0.0/0 → igw. Servers also need a public IP |
| Private subnet | 0.0.0.0/0 → nat. Out only |
| Isolated subnet | No 0.0.0.0/0 route |
| Internet gateway | One per VPC, two-way, free |
| NAT gateway | In a public subnet, one zone, needs an Elastic IP, bills hourly and per GB |
| High availability | One NAT gateway per zone, each zone routing to its own |
| Gateway endpoint | S3 and DynamoDB. A route, free. Cuts NAT charges |
| Interface endpoint | Other services. A private address in your subnet, bills hourly |
| Clean-up order | NAT gateway → wait for Deleted → release Elastic IP → VPC |
Security groups
03 – Networking
| Term | In one line |
|---|---|
| Security group | A firewall on one resource. Allow rules only |
| Stateful | Replies to an allowed connection are allowed automatically |
| New group | No inbound rules; outbound allows all traffic |
| Source = another group | Allows whatever carries that group, at any address |
| The chain | ALB from 0.0.0.0/0 on 443 → app from ALB group on 8080 → database from app group on 5432 |
| Times out | Dropped by the network: security group, route table or network ACL |
| Refused, fast | Reached the server; nothing listening on that port |
| HTTP 500, fast | Reached the app; the code failed. Read its logs |
| 504 from the load balancer | It couldn’t reach a server within 10 seconds. Check the app’s security group |
| Can’t log in to a broken server? | Actions → Monitor and troubleshoot → Get system log |
| Network ACL | Subnet firewall. Stateless, numbered, allow and deny. Usually left at the default |
Domain names and HTTPS
03 – Networking
| Term | In one line |
|---|---|
| DNS | Turns names into IP addresses, by a chain of servers |
| Resolver | Asks on your behalf, and caches answers |
| TTL | How many seconds an answer may be cached |
| Registrar | Where you bought the domain. Sets its name servers |
| Hosted zone | Where a domain’s records live in Route 53. $0.50 a month |
| NS record | The four name servers in charge. Must match at the registrar |
A / AAAA | Name → IPv4 / IPv6 address |
CNAME | Name → another name. Never at the apex |
| Alias | Route 53 only. Name → AWS resource, allowed at the apex |
| ACM | Free trusted certificates for load balancers. DNS validation renews itself |
| TLS termination | The load balancer decrypts; servers don’t handle certificates |
| Port 80 listener | 301 redirect to HTTPS |
| Self-signed | Encrypted, but untrusted. For learning only |
| DNS not working? | dig +short NS example.com — does it match the hosted zone? |
Servers on EC2
04 – Running code
| Term | In one line |
|---|---|
| Instance | A computer rented by the second |
| AMI | The starting disk every instance is copied from |
t3.micro | Family t, generation 3, size micro |
| User data | A script run once, as root, on first boot. Not secret |
/var/log/cloud-init-output.log | What your user data printed. Look here first |
| Reboot | Same ID, same disk, same IP |
| Stop, then start | Same ID, same disk, new public IP |
| Terminate | Gone, disk and all |
| Session Manager | A shell with no open port and no key. Needs the agent, a role, and a way out on 443 |
| Instance profile | How a role is attached to an instance |
AmazonSSMManagedInstanceCore | The policy Session Manager needs |
| IMDSv2 | Metadata needs a token first. Set V2 only |
| Golden rule | Never fix a server by hand. Change the AMI or user data |
Load balancers
04 – Running code
| Term | In one line |
|---|---|
| Load balancer | One fixed address in front of many servers |
| ALB | For HTTP: can route by path and hostname, and do HTTPS |
| Listener | A port and protocol, and what to do with requests on it |
| Target group | The servers, plus the health check that picks who gets traffic |
| Health check | A small request every few seconds. Fail enough in a row: no traffic |
| Time to take a broken server out | Interval × unhealthy threshold. 10 s × 2 = about 20 s |
| Shallow check | “Is this server broken?” — use this |
| Deep check | “Is everything behind it working?” — one shared failure takes out every server |
| Deregistration delay | Lets in-progress requests finish. Default 300 s; QuickCart uses 30 s |
| 502 / 503 / 504 | Broken answer / no target / too slow |
| Port 80 listener | Redirect to HTTPS, 301 |
| Servers’ security group | Port 80 from the load balancer’s security group only |
| Timed out? | Check the security groups first |
Auto scaling
04 – Running code
| Term | In one line |
|---|---|
| Auto Scaling group | Keeps the number of running servers equal to desired |
| Min / desired / max | Never fewer / right now / never more. Scaling only changes desired |
| Launch template | The recipe for each server. Versioned; no subnet |
| Zones | Give the group subnets in two or more; it balances between them |
| EC2 health check | Is the machine running? Always on |
| ELB health check | Does the app answer? Turn on when behind a load balancer |
| Grace period | Time before health checks count. Longer than real start-up time |
| Target tracking | Keep a metric near a target, like average CPU 50%. Out fast, in slowly |
| Terminate gracefully | Through the group, so it drains first |
| Instance refresh | Replace every server in batches, for a new template version |
| Clean up | Delete the group first. Deleting servers just makes it launch more |
Managed databases: RDS
05 – Data
| Term | In one line |
|---|---|
| RDS | AWS runs the database server; you still own the tables and the SQL |
| Endpoint | The DNS name you connect to. Never use an IP address |
| DB subnet group | The private subnets RDS may use. At least two zones |
| Public access | Always No |
| Security group | Port 3306 only from the app servers’ group |
| Multi-AZ | Synchronous standby in another zone. Failover in about 60–120 s. You can’t query it |
| Read replica | Asynchronous copy you can query. Its own endpoint. Can lag |
| Automated backups | Daily snapshot plus change logs. Restore to any second, 1–35 days back |
| Manual snapshot | Kept until you delete it. Take one before anything risky |
| Restore | Always creates a new database with a new endpoint |
| Parameter group | Engine settings. The default can’t be edited; make your own |
| Encryption | Choose at creation. Can’t be switched on later in place |
| Clean up | Delete the database, then its snapshots and subnet group |
Running SQL safely
05 – Data
| Step or statement | What it’s for |
|---|---|
CREATE TABLE x_before_fix AS SELECT id, col FROM t WHERE … | Save the rows you’ll change. Before the transaction |
| Undo script | Written before the change: UPDATE t JOIN x_before_fix ON … SET … |
SELECT … WHERE (same WHERE) | Count the rows first |
START TRANSACTION or BEGIN | Nothing after this is permanent until you commit |
Rows matched: N | Must equal your count. If not, ROLLBACK |
COMMIT | Makes it permanent and visible to everyone |
ROLLBACK | Throws the whole transaction away |
| Autocommit | On by default. A statement outside a transaction is permanent at once |
DDL: CREATE, DROP, TRUNCATE, ALTER | Commits at once, and commits your earlier changes. Can’t be rolled back |
ERROR 1205 … Lock wait timeout | Someone left a transaction open. Keep yours short |
| Private database | aws ssm start-session with AWS-StartPortForwardingSessionToRemoteHost, then mysql -h 127.0.0.1 |
Files on S3
05 – Data
| Term or setting | In one line |
|---|---|
| Bucket | A container for objects, in one region. Its name is unique worldwide |
| Object and key | A file, and its full name. The slashes in a key aren’t real folders |
| Block Public Access | On by default. Leave it on |
| Access | IAM roles for servers, bucket policies for rules, presigned URLs for one file at a time |
| Presigned URL | A link to one object that expires. Keep it short: minutes, not days |
| Versioning | Turn it on when you create the bucket. It only protects later changes |
| Delete marker | What a delete adds when versioning is on. Delete the marker to undo |
| SSE-S3 | Default encryption on every new object. No setup, no charge |
| HTTPS only | Bucket policy: Deny when aws:SecureTransport is false |
| Storage classes | Standard for often, Standard-IA for rarely, Glacier for archives |
| Lifecycle rule | Move to cheaper classes and delete old versions on a schedule |
| Delete a bucket | Empty first, then Delete |
Secrets
05 – Data
| Rule or command | What it means |
|---|---|
| One secret, one place | Code and settings hold the secret’s name, never its value |
| Secrets Manager | Passwords and anything that should rotate. About $0.40 a secret a month |
| Parameter Store SecureString | Settings and rarely changing secrets. Standard parameters cost nothing to store |
| Policy resource | arn:aws:secretsmanager:REGION:ACCOUNT:secret:NAME-* — the -* is required |
aws secretsmanager get-secret-value --secret-id NAME | Read a secret. Add --version-stage AWSPREVIOUS for the one before |
aws ssm get-parameter --name NAME --with-decryption | Read a SecureString parameter |
AmazonSSMManagedInstanceCore | Lets Session Manager in, and can read every parameter in the account |
| Rotation | Changes the secret on a schedule. The app must re-fetch when the database rejects it |
| A hostname | Not a secret. Keep it in plain config |
| Leaked? | Change it first. Clean up the code and history after |
Logs and metrics
06 – Watching it run
| Term or query | In one line |
|---|---|
| Log group | A set of log streams for one app, where retention is set |
| Log stream | The lines from one source, such as one server |
| Retention | Defaults to Never expire. Always set it |
| CloudWatch agent | Ships log files and memory and disk metrics from a server |
| Structured log | One JSON object per line, so every detail is a field |
| Metric | Namespace, name, dimensions; read with a statistic over a period |
Sum / Average / p95 | Counts / levels like CPU / response times |
| Metric filter | { $.level = "ERROR" } → a metric. Only counts lines after it exists |
Default value 0 | Lets “no errors” look different from “no data” |
filter level = "ERROR" | Only error lines |
stats count() by error_type | Count per type |
stats count() by bin(1m) | Count per minute: when did it start and stop? |
@timestamp, @message | CloudWatch’s own fields: the time, and the whole raw line |
Alarms that actually fire
06 – Watching it run
| Term or setting | In one line |
|---|---|
OK / ALARM / INSUFFICIENT_DATA | Fine / crossed the threshold long enough / can’t tell |
| Threshold | Where “fine” ends. Write down why you chose it |
| M out of N | M bad datapoints in the last N periods fires it |
| Flapping | Switching between OK and ALARM. Needs more datapoints or a better threshold |
| Missing data: good | Error counts. No data means no errors |
| Missing data: bad | Heartbeats. Silence means something died |
| SNS topic | Where alarms send notifications. Subscriptions deliver them |
| Pending confirmation | Nothing is delivered until the email link is clicked |
set-alarm-state --state-value ALARM | Tests the notification path. CLI only |
| Real trigger | The only proof it fires in an incident |
| Composite alarm | Several alarms, one rule, one notification |
| Alarm fatigue | Every alarm must mean “do something now” |
Who changed what
06 – Watching it run
| Term or action | In one line |
|---|---|
| CloudTrail | A record of every AWS API call: who, what, when, from where |
| Management event | A change to, or a look at, a resource. In event history |
| Data event | Reading or writing inside a resource, such as an S3 file. Only in a trail, at extra cost |
| Event history | Free, always on, 90 days, one region at a time |
| Read only = false | Only changes. The first filter in any incident |
userIdentity.type | IAMUser, AssumedRole (session name at the end of the ARN), Root, AWSService |
errorCode: AccessDenied | A refused call — how to debug permissions |
| Trail | Copies events to S3, all regions, kept as long as you like |
| Log file validation | Proves the log files weren’t edited |
aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=RevokeSecurityGroupIngress | The same lookup from the CLI |
Images and containers
07 – Containers
| Command or idea | What it means |
|---|---|
| Image | The package: code, libraries, system files. Read-only |
| Container | A running copy of an image. Its written files go when it’s removed |
| Layer | One set of file changes in an image. Shared between images |
docker pull nginx:1.27 | Download an image |
docker run -d -p 8080:80 --name web nginx:1.27 | Start a container in the background, with a published port |
docker ps / docker ps -a | Running containers / all containers |
docker logs web | What a container has printed |
docker exec web COMMAND | Run a command inside a running container |
docker stop web then docker rm web | Stop it, then delete it |
Tag (:1.27) | A movable label. Never release with latest |
Digest (@sha256:) | The exact content. Never changes |
exec format error | Built for the wrong processor. Use --platform linux/amd64 |
Writing a Dockerfile
07 – Containers
| Line or command | What it does |
|---|---|
FROM python:3.12-slim | Start from a pinned, small base image |
COPY requirements.txt . then RUN pip install then COPY app.py . | Libraries first, code last: fast rebuilds |
RUN | Runs once, at build time |
CMD ["a", "b"] | Runs at container start. Brackets, so the app hears stop signals |
ENTRYPOINT | Fixed start command; docker run words are added to it |
USER 1001 | Don’t run as root. Use the number |
.dockerignore | Keep .git, .env and keys out of the build |
COPY --from=build | Multi-stage: ship the result, not the build tools |
docker build -t name:tag . | Build from the Dockerfile here |
docker build -f File -t name:tag . | Build from another Dockerfile |
CACHED in the output | That step was reused |
Secret copied in, then rm | Still in the image. Never copy it in |
Data and networking
07 – Containers
| Command or setting | What it does |
|---|---|
| Writable layer | The container’s own files. Deleted by docker rm |
docker volume create NAME | Make a named volume |
-v NAME:/path | Mount a named volume inside the container |
--mount type=bind,source=FULL_PATH,target=/path | Show a folder from your computer inside the container |
-p 8081:8080 | Port 8081 on your computer leads to 8080 in the container |
-p 127.0.0.1:8080:8080 | Only reachable from your own computer |
docker network create NAME | A network where containers find each other by name |
localhost in a container | That container itself. Use the other container’s name |
docker compose up -d --wait | Start everything in compose.yaml and wait until healthy |
docker compose exec SERVICE COMMAND | Run a command inside a service |
docker compose down | Remove containers and network. Keeps volumes |
docker compose down -v | Also deletes the volumes and their data |
Storing images: ECR
07 – Containers
| Command or setting | What it does |
|---|---|
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin REGISTRY | Log Docker in to ECR for 12 hours |
| Registry address | ACCOUNT_ID.dkr.ecr.REGION.amazonaws.com |
docker build --platform linux/amd64 --provenance=false -t name:v1 . | Build one plain image for AWS’s processors |
docker tag name:v1 REGISTRY/repo:v1 | Give the image its ECR name |
docker push REGISTRY/repo:v1 | Upload it |
repo@sha256:… | Pull one exact image by its digest |
| Immutable tags | A tag can never be moved to another image |
| Scan on push | Check every image for known vulnerabilities when it arrives |
| Lifecycle policy | Expire old or untagged images automatically |
no basic auth credentials | Not logged in. Run the login again |
name unknown | The repository doesn’t exist, or the name is misspelled |
ECS on Fargate
08 – Running containers
| Term or setting | In one line |
|---|---|
| Cluster | A named group for services. Holds no servers on Fargate |
| Task definition | The recipe: image, CPU, memory, port, logs, roles. Every change is a new revision |
| Task | One running copy of a task definition |
| Service | Keeps the desired count of tasks running and registered with the load balancer |
| Fargate | AWS runs the servers. You pay per task, for what it asks for |
awsvpc | Each task gets its own network card, private IP and security group |
| Target type | IP addresses for Fargate |
cpu 256 / memory 512 | A quarter of a vCPU and 512 MB |
| Execution role | ECS’s role: pull the image, send logs, before the app starts |
| Task role | Your code’s role, while it runs |
| Task stops with no logs | Read the stopped reason |
| Targets say “Request timed out” | Security groups or port, not code |
exec format error | Image built for the wrong processor. Build with --platform linux/amd64 |
Releases without downtime
08 – Running containers
| Setting or idea | What to remember |
|---|---|
| Rolling deployment | Replace copies a few at a time while the service keeps running |
| Min 100% / Max 200% | Start new tasks before removing old ones. Capacity never drops |
| Health check | Must only pass when the app can really serve a request |
| Health check grace period | Ignore failed checks while a task starts. Longer than your real start-up time |
| Deregistration delay | Let in-progress requests finish. Longer than your slowest request; QuickCart uses 30 seconds, the default is 300 |
| SIGTERM, then SIGKILL | “Please stop”, then after the stop timeout “stop now” |
stopTimeout | 30 seconds by default on Fargate, 120 at most. Longer than the app’s own graceful timeout |
Exec form CMD ["gunicorn", …] | The app itself receives SIGTERM |
| Circuit breaker + rollback | A release that keeps failing goes back to the last good one by itself |
| Stopped reason | The first place to look when a task won’t start |
| Database changes | Add, then use, then remove: three releases, never one |
| Proof | Count failed requests during a release. Target: 0 |
Kubernetes and EKS
08 – Running containers
| Command or idea | What it does |
|---|---|
| Desired state | You write what you want; controllers keep making it true |
| Reconcile loop | Observe, compare, act. Over and over |
| Pod / Deployment / ReplicaSet | One copy / “run N copies” / one version’s set of copies |
| Service / Ingress | A stable name inside the cluster / a load balancer from outside |
| Readiness vs liveness probe | Take out of traffic / restart the container |
kubectl apply -f FILE | Make the cluster match a file |
kubectl get pods | List pods. describe pod NAME explains problems in its Events |
kubectl delete pod NAME | Delete one pod. The Deployment replaces it |
kubectl set image deployment/NAME app=IMAGE | Release a new image |
kubectl rollout status / undo | Watch a release / go back one version |
| EKS control plane | About $0.10 an hour on standard support. Upgrade before extended support |
| t3.small | About 11 pods, however much CPU is free |
| Zero downtime on EKS | Readiness gate label, preStop sleep, terminationGracePeriodSeconds |
| Clean up | kubectl delete -f first, then eksctl delete cluster |
ECS or EKS?
08 – Running containers
| Question or fact | What to remember |
|---|---|
| Same job? | Yes. Both run the same image, behind the same load balancers, in the same VPC |
| ECS control plane cost | None. You pay for tasks |
| EKS control plane cost | About $0.10 an hour on standard support, about six times more on extended support. Prices change |
| EKS upkeep | Regular version upgrades for the cluster, nodes and add-ons |
| Broken release | ECS with the circuit breaker rolls back itself. Kubernetes waits for kubectl rollout undo |
| Biggest factor | Who will run the platform |
| Portability | Worth something only if leaving AWS is really planned |
| “We need Kubernetes for X” | Name X. If you can’t, you don’t |
| Already on one platform? | A second one doubles how you release, debug and secure |
| A good memo | Answer first, evidence, the strongest counter-argument, and what would change your mind |
Why Terraform
09 – Infrastructure as code
| Idea or command | What it means |
|---|---|
| ClickOps | Building by clicking in the console. Fine for learning; unrepeatable for real environments |
| Infrastructure as code | Infrastructure described in text files, kept in git, built by a tool |
| Declarative | Describe the end result; the tool works out the steps |
| Idempotent | Running it again with no changes does nothing |
terraform init | Set up a folder and download plugins. Once per folder |
terraform plan | Show what would change. Changes nothing |
terraform apply | Show the plan, ask for yes, make the changes |
terraform destroy | Remove everything this code built |
+ ~ - | Create, change in place, destroy |
| The rule | Every fix goes in the code. A console-only fix gets undone |
Your first resource
09 – Infrastructure as code
| Command or symbol | What it means |
|---|---|
terraform init | Download providers, create .terraform.lock.hcl. Again after adding a provider or module |
terraform fmt | Tidy the formatting |
terraform validate | Check the code without contacting AWS |
terraform plan | Show what would change. Changes nothing |
terraform apply | Show the plan, ask for yes, make the changes |
terraform destroy | Delete everything this folder manages |
+ / ~ / - | Create / change in place / destroy |
-/+ and # forces replacement | Destroy then create. Stop and read |
(known after apply) | AWS decides the value when it creates the resource |
version = "~> 6.0" | Any 6.x provider, never 7.0 |
| Commit | .tf files and .terraform.lock.hcl |
| Never commit | .terraform/ and *.tfstate |
Variables, locals and outputs
09 – Infrastructure as code
| Thing | What it does |
|---|---|
variable "x" { type, default, description } | Declare an input. No default means required |
validation { condition, error_message } | Reject bad values before anything is planned |
sensitive = true | Hide the value in output. It’s still in state |
ephemeral = true | Keep a variable out of state and plan files (1.10+) |
locals { name_prefix = "${var.project}-${var.environment}" } | Work a value out once; read it as local.name_prefix |
default_tags { tags = local.common_tags } | Tag every resource the provider creates |
output "x" { value, description } | Print a value after apply; how modules hand values back |
-var-file=dev.tfvars | Use one environment’s values |
-var name=value | Set one value on the command line |
TF_VAR_name | Set a value from the environment — weaker than any tfvars file |
| Precedence, weakest first | default → TF_VAR_ → terraform.tfvars → *.auto.tfvars → -var / -var-file (later wins) |
terraform output -raw x | Print one output, bare |
Loops and lifecycle
09 – Infrastructure as code
| Thing | What it does |
|---|---|
count = length(var.list) | Copies by position: x[0], x[1]. Removing a middle item shifts the rest |
count = var.enabled ? 1 : 0 | On/off switch. Refer to it as x[0] |
for_each = var.map or toset(var.list) | Copies by key: x["orders"]. Removing one touches nothing else |
each.key, each.value | The current key and value inside a for_each |
dynamic "ingress" { for_each, content } | Repeat a block inside one resource; ingress.value is the item |
create_before_destroy = true | Replace by creating first. Pair with name_prefix. Plan shows +/- |
prevent_destroy = true | Refuse any plan that destroys or replaces it. Literal true only |
ignore_changes = [field] | Stop managing a field something else owns. You go blind to it |
replace_triggered_by = [resource] | Replace when another resource changes. Rare |
-/+ in a plan | Destroy, then create. Stop and ask why |
| “because index [2] is out of range for count” | The count trap. Switch to for_each |
Modules
09 – Infrastructure as code
| Thing | What to remember |
|---|---|
| Root module | The folder you run terraform in. Has the provider and the state |
| Child module | A folder called with module "name" { source = … }. No provider, no state of its own |
variables.tf / main.tf / outputs.tf | Inputs / resources / outputs. A habit, not a rule |
| Inputs with no default | Must be set by every caller. Use for things that must be decided, like environment |
module.NAME.OUTPUT | The only way to read a value from a module |
source = "./modules/x" | A local folder: versioned with the repository |
source = "org/name/aws" + version = "~> 6.0" | The public registry, pinned to a major version |
source = "git::https://…//x?ref=v1.4.0" | Git, pinned to a tag. Never a branch |
terraform init | Again after adding a module or changing its source or version |
terraform init -upgrade | Take newer versions allowed by your constraints |
| Compose, don’t bloat | Small modules wired together in the root beat one module with 40 inputs |
Terraform state
09 – Infrastructure as code
| Command or setting | What it does |
|---|---|
terraform state list | List everything Terraform is tracking |
terraform state show aws_ecr_repository.orders | Every detail of one resource |
terraform init -migrate-state | Move state into a new backend |
terraform state pull | Print the current state, to save a copy |
terraform state push FILE | Replace the state. Refused if the file is older, unless you add -force |
terraform force-unlock LOCK_ID | Clear a stuck lock, after checking nobody holds it |
key | One per environment: dev/…, stg/… |
use_lockfile = true | One command at a time. Terraform 1.10 or later |
| Bucket versioning | Turn it on before you need it |
.gitignore | *.tfstate and *.tfstate.* — never commit state |
Environments and regions
09 – Infrastructure as code
| Thing | What to remember |
|---|---|
| One folder per environment | envs/dev, envs/stg: you can see where you are |
main.tf, variables.tf | Identical in every folder. diff should print nothing |
backend.tf → key | Different per environment. Never copy it unchanged |
backend.tf → region | Where the state bucket is, not where you build |
terraform.tfvars | Read automatically. Holds everything that differs |
| No defaults on per-environment variables | So a forgotten value is an error, not dev’s value in prod |
terraform init -reconfigure | Set a folder up again after changing its backend, without copying state |
terraform workspace show | Which workspace you’re in. Fine for short-lived copies, risky for real environments |
provider "aws" { alias = "virginia" … } | A second configuration, for a second region |
configuration_aliases = [aws.us_east_1] | In a module: “I need a second AWS provider” |
providers = { aws.us_east_1 = aws.virginia } | In the module block: which one to use |
data "aws_availability_zones", data "aws_ami" | Never type in zone names or image IDs |
Operating Terraform
09 – Infrastructure as code
| Command or block | What it does |
|---|---|
terraform plan | Always shows drift: any ~ you didn’t write is a question |
terraform plan -refresh-only | Show only what changed outside Terraform. Undo nothing |
terraform plan -detailed-exitcode | Exit 0 = no changes, 1 = error, 2 = changes. For nightly drift checks |
import { to = ADDRESS, id = "ID" } | Adopt something made by hand, through a reviewed plan |
terraform plan -generate-config-out=generated.tf | Draft resource blocks for your import blocks |
moved { from = OLD, to = NEW } | Rename or move a resource without destroying it |
removed { from = ADDRESS } with destroy = false | Stop managing a whole resource, and leave it in AWS |
terraform state rm 'ADDRESS' | Forget one instance. Changes state at once, so add -dry-run first |
terraform state pull > state-backup.json | Save a copy before any repair |
-target=ADDRESS | Emergencies only. Routine use means the state is too big |
terraform init -upgrade | Newest provider within your range. Plan, then commit .terraform.lock.hcl |
Jenkins from zero
10 – Automation
| Thing | Remember |
|---|---|
| Controller | Schedules, stores, serves the UI. 0 executors |
| Agent | Runs builds. Disposable. Picked by label |
| Agents on demand | Amazon EC2 plugin or Kubernetes plugin |
| Reaching the controller | Session Manager: aws ssm start-session --target INSTANCE_ID --document-name AWS-StartPortForwardingSession --parameters portNumber=8080,localPortNumber=8080 |
| AWS permissions | Instance role, or withAWS(role: …). Never stored keys |
| Secrets | Credentials store, used with withCredentials, in single quotes |
| Credential scope | Global for pipelines, System for Jenkins itself, folders to fence them off |
JENKINS_HOME | Everything. secrets/ decrypts credentials.xml |
| Backups | Nightly EBS snapshots with AWS Backup; ThinBackup for settings; restore one every quarter |
| Setup as code | jenkins.yaml with the Configuration as Code plugin; plugins.txt in your own image |
| Security | Not on the internet, no anonymous read, update monthly, short admin list |
The Jenkinsfile
10 – Automation
| Directive | Remember |
|---|---|
pipeline { } | Declarative. Checked before it runs |
agent | any, none, { label 'x' }, { docker { image 'x' } }. Per stage too |
stages / stage / steps | Top to bottom. The first failure skips the rest |
environment | Variables for every step. Read them in sh with single quotes |
parameters | choice, booleanParam, string. Read as params.NAME. Active after one run |
options | timeout, timestamps(), disableConcurrentBuilds(), buildDiscarder(logRotator(numToKeepStr: '20')) |
when | branch, changeRequest(), tag, expression { }. beforeAgent, beforeInput |
input | message, ok, submitter. Put a timeout on the stage. Use agent none at the top |
parallel | Side by side. failFast true |
post | always, success, failure, unstable, aborted, changed, fixed, then cleanup last |
| Quotes | '…' the shell fills in $VAR. "…" Groovy fills in ${…}. Secrets: single quotes only |
| Replay | Try an edited pipeline once, without saving it |
Continuous integration
10 – Automation
| Thing | Remember |
|---|---|
| CI | Every change checked the same way, on a clean agent, before it joins main |
| Order | Cheapest first: lint, tests, build, scan, plan |
| Fail fast | First failure stops the rest. disableConcurrentBuilds(abortPrevious: true) |
| Multibranch | A job per branch and pull request. PR builds test the merge with main |
| Webhook | https://jenkins.example.com/github-webhook/, with a secret. Polling is the slow fallback |
| Required check | GitHub ruleset on main: continuous-integration/jenkins/pr-merge must pass |
| Lint | hadolint Dockerfile, terraform fmt -check -recursive |
| Tests | Write JUnit XML; junit in post → always |
| Scan | trivy image --exit-code 1 --severity CRITICAL --ignore-unfixed |
| Plan on PR | Read-only role, shared state, gh pr comment "$CHANGE_ID" --body-file plan.txt |
| Cache | Downloads (layers, packages, providers). Never results |
| Shared library | vars/name.groovy → step name(). Load with @Library('lib@v1') _ |
| Flaky test | Quarantine with an owner. Never retry a test |
Continuous delivery
10 – Automation
| Idea or code | In one line |
|---|---|
| Build once | One image per change. Every environment gets that same image |
| Deploy by digest | repo@sha256:… never changes. A tag can |
| Promotion | dev → smoke test → stg → smoke test → approval → prod → smoke test |
input message: '…', ok: '…' | Pause for a person. Add submitter to limit who can approve |
timeout around input | No answer means aborted, never approved |
| Smoke test | A few quick checks that the release really runs where it was deployed |
| Rollback | Deploy the previous digest. No rebuild |
retry(3) | Only around steps that are safe to repeat |
| Idempotent | “Set to X” can run twice safely. “Add one more” can’t |
| AWS credentials | An instance profile, or withAWS(role: …) per environment. Never stored keys |
Canary from a pipeline
10 – Automation
| Idea or code | In one line |
|---|---|
| Canary | A small share of real traffic tries the new version first |
| Abort criteria | Error rate, latency, minimum sample and bake time, written down before the release |
| Too little data | Not a pass. Wait for more, or fail the step |
| Judge per version | ALB: one target group per version. Lambda: the ExecutedVersion dimension |
| ALB weighted forward | Two target groups with weights. Roll back by setting the canary’s weight to 0 |
| CodeDeploy | CodeDeployDefault.ECSCanary10Percent5Minutes, with alarms for automatic rollback |
| Lambda alias | --routing-config '{"AdditionalVersionWeights":{"2":0.1}}' |
post { failure { … } } | The rollback runs whatever made the build fail |
| Promote | 100% to the new version. Keep the old one briefly |
Where to look first
11 – When things break
| Symptom or place | What it means, or where it is |
|---|---|
| Timed out while connecting | The network dropped it: security groups, routes, network ACLs |
| Connection refused, instantly | Reached the machine; nothing listening on that port |
| Connected, then timed out with 0 bytes | The app, or something it waits on, is stuck |
| 502 / 503 / 504 from the load balancer | Bad answer / no healthy targets / target too slow |
| 500 | The app failed. Read its logs |
| 4xx | The request was wrong. Don’t restart anything |
Target.Timeout | Health check never answered. Check the app’s security group |
| What changed? | Deploys, and CloudTrail with Read only = false |
| Won’t boot? | Actions → Monitor and troubleshoot → Get system log |
127.0.0.1 vs 0.0.0.0 | This machine only vs every address |
curl -v | Shows each step of a connection, and why it failed |
Runbooks
11 – When things break
| Part or practice | In one line |
|---|---|
| Runbook | Exact steps for one situation, followable at 3am by anyone |
| Trigger | The alarm that sends you here |
| Impact | What customers see |
| Steps | Where, what, and what to do with the answer |
| Verification | A measurable “fixed”, with a duration |
| Rollback | How to undo each step that changes something |
| Escalation | Who to call, and after how long |
| Owner | Who keeps it up to date |
| Last tested | The date and time of the last dry run. Trust it accordingly |
| Dry run | Follow it with a stopwatch; fix every gap |
| Link from the alarm | Put the runbook link in the alarm description |
| Automate the fixed parts | A rollback is a job, not five commands to type |
Real incidents
11 – When things break
| Pattern or practice | In one line |
|---|---|
| Most outages | Small settings — a rule, a port, a route, a typo — not code |
| No outbound rule | Replies are allowed; connections the server starts are not |
| Same message, different causes | Log error_type and latency_ms as fields |
| Shallow health check | Keeps servers in service when a dependency fails |
| Two stacked causes | After each fix, test again |
| Numbers look wrong | Check the test before the system |
| Postmortem | Summary, impact, timeline, detection, causes, went well, went badly, lucky, actions |
| Blameless | What made the mistake possible — not who made it |
| Five whys | Keep asking why until you reach something you can change |
| Action item | Specific, changes the system, has an owner and a date |