22 โ Hands-On Command Cheat-Sheets & Labs¶
What this is: The terminal-open muscle-memory companion to the teaching modules. Every command here is runnable โ not pseudocode. Open a terminal alongside this page and type every block; observe the output. The "say this" boxes are interview gold: read them aloud until the answer comes back without looking.
How to use: Work through one tool section per study session. Mark commands you can type cold; revisit anything that required a peek.
Pairs with: Interview Bank ยท Reference Appendix
โญ Golden rule: Reading โ knowing โ run every command yourself.
Contents¶
| # | Tool | Key topics |
|---|---|---|
| 1 | ๐ฟ Git | 3-area model ยท everyday ยท branching ยท undo ยท rebase ยท reflog |
| 2 | ๐ณ Docker | images ยท containers ยท Dockerfile ยท Compose ยท debug flow |
| 3 | โธ๏ธ kubectl | pods ยท deployments ยท services ยท CrashLoopBackOff debug |
| 4 | โ๏ธ Ansible | inventory ยท playbooks ยท idempotency ยท roles ยท vault |
| 5 | ๐๏ธ Terraform | HCL ยท plan/apply ยท state ยท modules ยท environments |
| 6 | ๐ GitOps / ArgoCD | Application CRD ยท sync ยท drift ยท App-of-Apps |
| 7 | ๐ง Jenkins | Jenkinsfile anatomy ยท credentials ยท parallel ยท shared libs |
๐ฟ Git¶
Mental model: Working Directory โโgit addโโโถ Staging Area (Index) โโgit commitโโโถ Local Repo (.git) โโgit pushโโโถ Remote. You always know exactly where a change lives.
โ Deep concepts: 00a โ Preflight ยท 20 โ Confusions & Trade-offs
Everyday commands¶
| Goal | Command | Why |
|---|---|---|
| See current state | git status |
First thing, every time โ what's staged, what's not |
| Stage one file | git add file.py |
Precise โ stage only what belongs in this commit |
| Stage everything | git add . |
Quick; watch for unintended files |
| Commit | git commit -m "feat: add login" |
Conventional commit type prefix keeps history readable |
| Stage + commit (tracked files only) | git commit -am "fix: typo" |
Skips add for already-tracked files |
| Compact log | git log --oneline --graph --all |
See branch topology at a glance |
| Unstaged diff | git diff |
What changed but is NOT staged |
| Staged diff | git diff --staged |
What WILL go into the next commit |
| Inspect one commit | git show <hash> |
Full diff + metadata for any commit |
Branching, merging, and rebasing¶
# Create and switch to a new branch
git checkout -b feature/login # classic (still works everywhere)
git switch -c feature/login # modern equivalent (preferred)
# Merge โ preserves full history, adds a merge commit
git checkout main
git merge feature/login
git branch -d feature/login # delete the merged branch
# Rebase โ replays commits on top of latest main โ linear history
git checkout feature/login
git rebase main # feature now sits on tip of main
# Rule: rebase LOCAL branches only. Never rebase a branch others have pulled.
# Interactive rebase โ squash, edit, or reorder commits before merging
git rebase -i HEAD~3
LAB A โ basic branch & merge
git init lab-git && cd lab-git
git commit --allow-empty -m "init"
git switch -c feature/readme
echo "# Demo" > README.md
git add . && git commit -m "docs: add readme"
git switch main
git merge feature/readme
git branch -d feature/readme
git log --oneline --graph
LAB B โ conflict resolution (the #1 interview skill)
# Two branches edit the SAME line in the SAME file
git switch -c branchA
echo "hello from A" > greet.txt && git add . && git commit -m "A"
git switch main
echo "hello from main" > greet.txt && git add . && git commit -m "main"
git merge branchA # CONFLICT โ open greet.txt
# Remove the <<<<<<< / ======= / >>>>>>> markers; keep what you want
git add greet.txt
git commit # completes the merge
# If it goes wrong: git merge --abort
LAB C โ rebase for linear history
git switch -c feature/api
git commit --allow-empty -m "feat: api v1"
git switch main
git commit --allow-empty -m "chore: unrelated"
git switch feature/api
git rebase main # feature/api now replays on top of main
git log --oneline --graph # clean linear line, no merge bubble
Undo toolbox¶
| Situation | Command | Safe on pushed commits? |
|---|---|---|
| Drop an unstaged edit | git restore file |
n/a |
| Un-stage a file | git restore --staged file |
n/a |
| Fix the last commit message or add a missed file | git commit --amend |
No โ rewrites history |
| Undo a commit safely | git revert <hash> |
Yes โ adds a new undo commit |
| Move HEAD back, keep changes staged | git reset --soft HEAD~1 |
No โ local only |
| Move HEAD back, unstage changes | git reset --mixed HEAD~1 |
No โ local only |
| Move HEAD back, delete all changes | git reset --hard HEAD~1 |
No โ destructive |
๐ฏ Money one-liner:
revertis safe โ it adds a new commit that undoes the change; always use it on anything already pushed.resetmoves the pointer and rewrites history โ local branches only.--softkeeps staged,--mixedunstages,--hardthrows everything away.
Stash, remote, and reflog¶
# Stash โ park work-in-progress without making a commit
git stash # stash tracked changes
git stash -u # include untracked files too
git stash pop # restore and drop the stash entry
git stash apply # restore but keep the entry (safe)
git stash list
# Cherry-pick โ grab a single commit from another branch
git cherry-pick <hash>
# Remote
git clone <url>
git remote -v # show configured remotes
git fetch origin # download changes โ does NOT merge
git pull # fetch + merge (or rebase if configured)
git push
git push -u origin feature/x # first push of a new branch (sets upstream)
git push --force-with-lease # safe force โ checks no one else pushed first
# NEVER plain --force on shared branches
# Reflog โ every HEAD movement is recorded; your time machine
git reflog # find "lost" commits after a bad reset
git reset --hard <hash> # jump back to that point
Inspect & search¶
git log --author="alice"
git blame src/app.py # who wrote each line and when
git diff main..feature # all changes between two branches
git bisect start
git bisect bad # current commit is broken
git bisect good v1.0.0 # this commit was fine โ git binary-searches
Branching strategies¶
| Strategy | Shape | Best for | Downside |
|---|---|---|---|
| Environment branches | branch = environment (test / qa / prod) | Simple pipelines | Config drift between branches |
| Git Flow | main + develop + feature / release / hotfix | Scheduled, versioned releases | Heavy; slows teams down |
| GitHub Flow | main + short feature branches โ PR โ deploy | Continuous delivery (modern default) | Requires solid CI gate |
| Trunk-based | One trunk + feature flags | Fastest CI, smallest batches | Demands feature-flag discipline |
Environment branches โ how promotion flows (the model in row 1):
flowchart TD
Dev(["๐ฉโ๐ป Developer"]):::dev
subgraph T["๐งช TEST โ fast-moving, messy"]
Tb["test branch"]:::test
Ts[["Test server<br/>v1 ยท v2 ยท v3"]]:::test
end
subgraph Q["๐ QA โ testers verify"]
Qb["qa branch"]:::qa
Qs[["QA server<br/>v1 ยท v2"]]:::qa
end
subgraph P["๐ PROD โ real customers"]
Pb["prod branch"]:::prod
Ps[["Prod server<br/>R1"]]:::prod
end
Dev -->|"every commit"| Tb
Tb --> Ts
Tb ==>|"promote stable ยท merge"| Qb
Qb --> Qs
Qb ==>|"promote release-ready ยท merge"| Pb
Pb --> Ps
classDef dev fill:#e8eaf6,stroke:#3f51b5,color:#1a237e;
classDef test fill:#e3f2fd,stroke:#1976d2,color:#0d47a1;
classDef qa fill:#fff3e0,stroke:#ef6c00,color:#e65100;
classDef prod fill:#e8f5e9,stroke:#43a047,color:#1b5e20;
Code is promoted upward: every commit lands on test (auto-deploys to the Test server); once stable, merge โ qa; once release-ready, merge โ prod (R1, R2โฆ = releases). Each branch auto-deploys to its own environment.
โ ๏ธ The catch (why modern teams moved on): long-lived branches drift apart โ
test,qa, andprodslowly diverge, so promotion merges get painful and "works in QA, breaks in prod" creeps in. The modern fix: onemainbranch + per-environment config/overlays (dev/staging/prod) delivered by GitOps โ the code is identical everywhere; only the config differs. See M7 GitOps.๐ฎ๐ณ Hinglish intuition: Modern teams mein long-lived branches se bachte hain โ chhoti, jaldi-merge branches + GitOps per-env overlays. Har environment ke liye alag branch = drift ka recipe.
20-second cheat-sheet¶
add โ staged | commit โ local repo | push โ remote
merge = preserves history + merge commit (safe on shared)
rebase = linear, replay on tip (local only โ never rebase shared)
revert = safe undo (adds commit) | reset = pointer move (local only)
reflog = time machine for "lost" commits
stash = temp shelf | cherry-pick = single commit grab
Golden rules: Never reset --hard on pushed commits. Never plain --force on shared branches. Conventional commits (feat:/fix:/docs:) keep log history readable.
๐ค Interview one-liners: - "merge vs rebase?" โ merge preserves history and adds a merge commit โ safe on shared branches; rebase replays commits for a linear history โ only on your own local branch, never after pushing. - "how do you resolve a conflict?" โ identify the conflicted file, open it, remove the
<<<</====/>>>>markers, keep the correct code,git add,git commit. - "how do you recover a lost commit?" โgit reflogshows every HEAD movement; copy the hash;git reset --hard <hash>jumps back to it.
๐ณ Docker¶
Mental model: Dockerfile โโbuildโโโถ Image (read-only blueprint) โโrunโโโถ Container (running instance with a thin writable layer on top). push / pull โ Registry. Kubernetes uses containerd on nodes โ Docker is for building images, not running them in production.
โ Deep concepts: M3 โ Docker
Working with images¶
docker pull nginx:1.27
docker images
docker build -t myapp:1.0 .
docker tag myapp:1.0 registry.io/acme/myapp:1.0
docker history myapp:1.0 # layer sizes โ find the bloat
docker rmi myapp:1.0
docker image prune -a # remove all unused images (reclaim disk)
Working with containers¶
docker run nginx # foreground, stops on Ctrl-C
docker run -d --name web nginx # detached, named
docker ps # running containers
docker ps -a # all, including stopped
docker stop web && docker start web
docker rm web # -f to force-remove a running container
docker logs -f web # follow output
docker exec -it web bash # shell into a running container (debug)
docker inspect web # IP, mounts, env vars, restart count
docker stats # live CPU + memory usage
docker run flags reference¶
| Flag | Meaning |
|---|---|
-d |
Detached (background) |
-p 8080:80 |
Publish port โ host:container (left = outside world) |
-e LOG_LEVEL=info |
Set an environment variable |
-v mydata:/data |
Named volume โ data survives container removal |
-v $(pwd):/app |
Bind mount โ host directory inside the container (dev only) |
--restart unless-stopped |
Auto-restart on crash or reboot |
--network mynet |
Attach to a named bridge network |
--rm |
Remove container automatically on exit |
--name web |
Give a human-readable name |
Dockerfile best practices¶
# Multi-stage: build stage compiles; final stage ships only the artifact
FROM golang:1.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download # deps layer first โ cache hit on code changes
COPY . .
RUN CGO_ENABLED=0 go build -o server ./cmd/server
FROM gcr.io/distroless/static:nonroot # tiny, no shell, runs as non-root
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]
Key instruction rules:
| Instruction | Rule |
|---|---|
FROM |
Pin a version: nginx:1.27, not nginx:latest in production |
COPY vs ADD |
Prefer COPY; ADD auto-extracts tarballs (surprising side effect) |
CMD |
Default arguments โ easily overridden at docker run |
ENTRYPOINT |
Fixed executable; CMD becomes its arguments |
ARG |
Build-time only (gone after build โ safe for build tokens) |
ENV |
Runtime (persists in the running container) |
EXPOSE |
Documentation only โ does NOT publish ports (still need -p) |
Volumes and networking¶
# Volumes โ data that survives container removal
docker volume create mydata
docker volume ls
docker volume prune
# Networks โ containers on the same network reach each other BY NAME (built-in DNS)
docker network create mynet
docker run -d --name db --network mynet postgres:16
docker run -d --name api --network mynet myapp:1.0 # api can reach db:5432 by name
# Default driver: bridge. Others: host (no isolation), none (no network)
Docker Compose¶
docker compose up -d
docker compose ps
docker compose logs -f web
docker compose down # stop and remove containers
docker compose down -v # also remove named volumes
Compose YAML skeleton:
services:
web:
build: .
ports: ["8080:80"]
environment: [LOG_LEVEL=info]
depends_on: [db]
volumes: [./static:/app/static]
db:
image: postgres:16
volumes: [pgdata:/var/lib/postgresql/data]
volumes:
pgdata:
Registry and cleanup¶
docker login registry.io
docker push registry.io/acme/myapp:1.0
docker system df # disk usage by images/containers/volumes
docker container prune # remove all stopped containers
docker image prune -a # remove all unused images
docker system prune -a # full cleanup โ be careful in dev
Debug flow¶
docker ps -a โ what exit code? (non-zero = crash)
docker logs web โ what was the last output before it stopped?
docker inspect web โ restart count? OOMKilled flag?
docker stats โ CPU/memory spike at crash time?
"Container keeps restarting" is the Docker equivalent of Kubernetes CrashLoopBackOff โ same root causes: bad entrypoint command, missing environment variable, OOMKilled.
Mini-lab¶
mkdir lab-docker && cd lab-docker
cat > index.html <<'EOF'
<h1>Hello from Docker</h1>
EOF
cat > Dockerfile <<'EOF'
FROM nginx:1.27-alpine
COPY index.html /usr/share/nginx/html/
EOF
docker build -t lab-nginx:1.0 .
docker run -d -p 8888:80 --name lab-web lab-nginx:1.0
curl http://localhost:8888 # Hello from Docker
docker logs lab-web
docker exec -it lab-web sh # explore the running container
docker stop lab-web && docker rm lab-web
docker rmi lab-nginx:1.0
20-second cheat-sheet¶
build โ image | run โ container | push/pull โ registry
-d detached | -p host:container | -v volume | -e env var
exec -it โ shell in | logs -f โ follow output | inspect โ metadata
multi-stage โ small image | distroless โ minimal attack surface
Golden rules: Multi-stage builds. Non-root user. .dockerignore (keep node_modules, .git, *.env out). Pin versions in production. Never bake secrets into image layers. Scan with Trivy before pushing.
๐ค Interview one-liners: - "image vs container?" โ image is the frozen read-only blueprint; container is a running instance โ one image can spawn dozens of containers, each with their own writable layer. - "CMD vs ENTRYPOINT?" โ ENTRYPOINT is the fixed executable; CMD is its default arguments (overridable at runtime). Used together:
ENTRYPOINT ["python"]+CMD ["app.py"]. - "Docker on Kubernetes nodes?" โ No since K8s 1.24 โ dockershim removed; nodes use containerd directly. Docker is used to build images in CI pipelines, not to run workloads on nodes.
โธ๏ธ kubectl (Kubernetes)¶
Mental model: Control plane (API server, etcd, scheduler, controller-manager) + Worker nodes (kubelet, kube-proxy, containerd). You declare desired state in YAML; controllers continuously reconcile actual state toward it. kubectl is your API client.
โ Deep concepts: M4 โ Kubernetes Core ยท M9 โ Advanced K8s Internals
Cluster and context¶
kubectl cluster-info
kubectl get nodes -o wide
kubectl config get-contexts
kubectl config use-context staging-cluster
kubectl config set-context --current --namespace=default
Pods¶
kubectl get pods # current namespace
kubectl get pods -A # all namespaces
kubectl get pods -o wide # include node name + pod IP
kubectl get pods -w # watch โ live updates
kubectl describe pod <name> # EVENTS section at bottom = the real clue
kubectl logs <name> # stdout/stderr
kubectl logs <name> -f # follow (tail -f equivalent)
kubectl logs <name> --previous # logs from the LAST crashed container
kubectl logs <name> -c sidecar # specific container in a multi-container pod
kubectl exec -it <name> -- sh # shell into a running pod
kubectl delete pod <name> # deleted; recreated automatically if managed
Deployments¶
kubectl create deployment web --image=nginx:1.27 --replicas=3
kubectl get deploy,rs,pods
kubectl scale deploy web --replicas=5
kubectl set image deploy/web nginx=nginx:1.28 # triggers rolling update
kubectl rollout status deploy/web # watch the rollout progress
kubectl rollout history deploy/web # audit trail of past rollouts
kubectl rollout undo deploy/web # ROLLBACK to previous revision
kubectl rollout restart deploy/web # reload ConfigMap change (no image change)
kubectl edit deploy web # live YAML edit in your $EDITOR
Services and networking¶
kubectl expose deploy web --port=80 --type=ClusterIP
kubectl get svc
kubectl get endpoints web # which pod IPs back this service
kubectl port-forward svc/web 8080:80 # reach a service locally (debug only)
| Service type | Use case |
|---|---|
ClusterIP |
Internal cluster traffic only (default) |
NodePort |
Dev / testing โ opens a port on every node |
LoadBalancer |
Production on cloud โ provisions a cloud load balancer |
Ingress |
HTTP/HTTPS routing by path or hostname โ requires an Ingress controller |
ConfigMap and Secret¶
kubectl create configmap app-cfg --from-literal=LOG_LEVEL=info
kubectl create secret generic db-pass --from-literal=password=s3cret
# Decode a secret value (base64-encoded at rest)
kubectl get secret db-pass -o jsonpath='{.data.password}' | base64 -d
Namespaces¶
Resources, probes, and autoscaling¶
| Concept | Meaning |
|---|---|
requests |
Guaranteed resource; scheduler uses this for pod placement |
limits |
Hard ceiling; exceeding memory limit โ OOMKilled |
readinessProbe |
Traffic gate โ pod is removed from the Service until it passes |
livenessProbe |
Restart gate โ pod is restarted if it fails |
# Horizontal Pod Autoscaler โ scale based on CPU
kubectl autoscale deploy web --min=2 --max=10 --cpu-percent=70
kubectl get hpa
Declarative workflow¶
kubectl apply -f deployment.yaml # create OR update โ idempotent
kubectl apply -k overlays/dev # kustomize overlay
kubectl diff -f deployment.yaml # preview changes before applying
kubectl get deploy web -o yaml # export live manifest as YAML
kubectl explain deploy.spec.replicas # built-in docs for any field
kubectl create deploy web --image=nginx --dry-run=client -o yaml # scaffold YAML
Labels and selectors¶
kubectl get pods --show-labels
kubectl get pods -l app=web,env=prod
kubectl label pod web-abc tier=frontend
Services find their pods exclusively via label selectors โ the selector in the Service spec must match pod labels exactly.
Useful flags and shortcuts¶
# Short resource names: po svc deploy ns cm no rs ep
kubectl get po,svc,deploy -A
# Output formats
kubectl get pod web-abc -o wide
kubectl get pod web-abc -o yaml
kubectl get pods -o jsonpath='{.items[*].metadata.name}'
# Other essentials
kubectl api-resources # all resource types + short names
kubectl top pod # CPU/mem (needs metrics-server)
kubectl get events --sort-by=.lastTimestamp # cluster-wide event log, newest last
kubectl cp <pod>:/var/log/app.log ./app.log # copy a file out of a pod
Node maintenance¶
kubectl cordon node-1 # mark unschedulable โ no new pods land here
kubectl drain node-1 \
--ignore-daemonsets \
--delete-emptydir-data # evict all pods safely before maintenance
kubectl uncordon node-1 # restore scheduling after maintenance
CrashLoopBackOff debug flow (the canonical interview scenario)¶
Step 1: kubectl get pods โ see restart count climbing
Step 2: kubectl describe pod <name> โ read EVENTS (image pull fail? probe fail? OOM?)
Step 3: kubectl logs <name> --previous โ crash output from the last run
Step 4: kubectl get events --sort-by=.lastTimestamp โ cluster-level clues
Step 5: kubectl top pod <name> โ OOMKilled? (memory limit too low)
Common root causes: bad image tag (ImagePullBackOff), missing Secret / ConfigMap (env var absent at start), failing liveness probe, OOMKilled.
Mini-lab¶
kubectl create deployment demo --image=nginx:1.27 --replicas=2
kubectl expose deployment demo --port=80
kubectl rollout status deployment/demo
# Introduce a bad image to trigger ImagePullBackOff
kubectl set image deployment/demo nginx=nginx:does-not-exist
kubectl get pods # ImagePullBackOff or ErrImagePull
kubectl describe pod <failing-pod-name> # see the pull error in Events
# Recover with a rollback
kubectl rollout undo deployment/demo
kubectl rollout status deployment/demo
# Reach the service locally
kubectl port-forward svc/demo 9090:80 &
curl http://localhost:9090 # nginx welcome page
kill %1 # stop port-forward
kubectl delete deployment demo
kubectl delete svc demo
20-second cheat-sheet¶
apply -f = declarative (idempotent) | describe = events | logs --previous = crash output
set image = rolling update | rollout undo = rollback | rollout restart = reload config
ClusterIP = internal | LoadBalancer = cloud LB | Ingress = HTTP front door
requests = scheduling guarantee | limits = ceiling (OOMKill if exceeded)
readiness = traffic gate | liveness = restart gate
Golden rules: Declarative YAML + GitOps beats imperative kubectl. Never treat a pod as a pet โ it's ephemeral. Always check describe and logs --previous before escalating a CrashLoop.
๐ค Interview one-liners: - "how does a request reach a pod?" โ DNS resolves the Service name โ Service selector finds matching pod IPs (Endpoints) โ kube-proxy routes to a pod; for external traffic: cloud LB โ NodePort โ kube-proxy โ pod. - "liveness vs readiness?" โ liveness restarts the container when it fails (the app is broken); readiness removes the pod from the Service until it passes (the app is starting or temporarily busy). Different failure modes โ both needed. - "apply vs create?" โ
applyis idempotent: create if missing, update if existing;createfails if the resource already exists. Always useapplyin GitOps workflows.
โ๏ธ Ansible¶
Mental model: Agentless configuration management โ SSH from a control node to managed hosts. Declare desired state in YAML playbooks; Ansible makes it so, then does nothing if the state is already correct. Run twice โ zero changes (idempotent).
โ Deep concepts: M2 โ Ansible
โ ๏ธ KEY scope clarity โ Ansible is NOT universal. Ansible is used for self-managed servers (on-prem bare-metal, VMs, kubeadm clusters). It is not used on managed Kubernetes services (EKS / AKS / GKE โ the cloud provider manages the nodes). Its job is the node OS layer: install containerd, run kubeadm, tune the kernel, disable swap, set sysctl โ it turns a bare Linux machine into a Kubernetes node. It does not manage running pods; Kubernetes does that.
The stack: Terraform builds the server โ Ansible configures it โ Kubernetes runs the containers.
Inventory¶
# inventory.ini
[web]
web1 ansible_host=10.0.1.11
web2 ansible_host=10.0.1.12
[db]
db1 ansible_host=10.0.1.20
[all:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=~/.ssh/id_rsa
ansible-inventory -i inventory.ini --list # verify how Ansible parsed the inventory
ansible all -i inventory.ini -m ping # connectivity check to every host
Ad-hoc commands¶
# Syntax: ansible <pattern> -m <module> -a "<args>" [-b] [-i inventory]
ansible all -m ping
ansible web -m command -a "uptime"
ansible web -m shell -a "df -h | grep /var" # -m shell allows pipes; -m command does not
ansible web -m apt -a "name=nginx state=present" -b # -b = become (sudo)
ansible web -m service -a "name=nginx state=started enabled=yes" -b
ansible web -m copy -a "src=nginx.conf dest=/etc/nginx/nginx.conf" -b
๐ฎ๐ณ Hinglish intuition:
-m commandsafe hai โ shell features nahi, pipes nahi, zyada predictable.-m shellpowerful hai โ pipes kab chahiye tab use karo, warnacommandhi prefer karo.
Playbook anatomy¶
# site.yml
- hosts: web
become: true # run all tasks as root (sudo)
vars:
nginx_port: 80
tasks:
- name: Install nginx
apt:
name: nginx
state: present
update_cache: yes
- name: Deploy config from template
template:
src: nginx.conf.j2 # Jinja2 template โ variables rendered per host
dest: /etc/nginx/nginx.conf
notify: Restart nginx # trigger handler only if this task changes something
- name: Ensure nginx is running
service:
name: nginx
state: started
enabled: yes
handlers:
- name: Restart nginx # runs ONCE at the end, only if notified
service:
name: nginx
state: restarted
ansible-playbook -i inventory.ini site.yml
ansible-playbook -i inventory.ini site.yml --check # DRY RUN โ shows what would change
ansible-playbook -i inventory.ini site.yml --diff # show exact file content changes
ansible-playbook -i inventory.ini site.yml --limit web1 # run on one host only
ansible-playbook -i inventory.ini site.yml -v # verbose output
ansible-playbook -i inventory.ini site.yml -e "nginx_port=8080" # override a variable
Key modules to know¶
| Module | Purpose |
|---|---|
ping |
Connectivity check (not ICMP โ tests Python + SSH) |
apt / yum / dnf |
Package management per distro |
service / systemd |
Start, stop, enable services |
copy |
Copy a static file to the host |
template |
Render a Jinja2 .j2 template onto the host (per-host variables) |
file |
Create / delete files or directories; set permissions |
lineinfile |
Manage a single line inside a file |
user / group |
User and group management |
git |
Clone or pull a repository |
command |
Run a command โ no pipes, safer |
shell |
Run a shell command โ pipes allowed |
debug |
Print a variable or message during a run |
Variables, facts, and handlers¶
# Dump all auto-gathered facts for a host (OS, CPU, IPs, etc.)
ansible web -m setup
# Variable precedence (lowest โ highest):
# role defaults โ group_vars โ host_vars โ play vars โ -e flag (highest)
Handlers fire once at the end of a play, only if a task reported changed. This is why "restart nginx" only fires when the config file actually changed โ not on every run.
Loops, conditionals, and register¶
- name: Install developer tools
apt:
name: "{{ item }}"
state: present
loop: [git, curl, vim]
- name: Only on Ubuntu systems
apt:
name: htop
when: ansible_distribution == "Ubuntu"
- name: Check disk usage
command: df -h
register: disk_out # capture the output
- debug:
msg: "{{ disk_out.stdout }}"
Roles and Vault¶
# Scaffold a role structure
ansible-galaxy init roles/webserver
# Creates: tasks/ handlers/ templates/ files/ vars/ defaults/ meta/
# Reference in a playbook
# roles: [webserver, monitoring]
# Vault โ encrypt secrets at rest
ansible-vault create group_vars/all/secrets.yml
ansible-vault edit group_vars/all/secrets.yml
ansible-playbook site.yml --ask-vault-pass
Mini-lab (localhost โ no remote host needed)¶
# lab.yml
- hosts: localhost
tasks:
- name: Create a file
file:
path: /tmp/ansible-lab.txt
state: touch
- name: Write content
copy:
content: "Ansible was here\n"
dest: /tmp/ansible-lab.txt
- name: Show the file content
command: cat /tmp/ansible-lab.txt
register: out
- debug:
msg: "{{ out.stdout }}"
ansible-playbook -i inventory-local.ini lab.yml --check # dry run first
ansible-playbook -i inventory-local.ini lab.yml # run it
ansible-playbook -i inventory-local.ini lab.yml # run AGAIN โ 0 changed = idempotent
20-second cheat-sheet¶
inventory = who | playbook = what to do | module = how (apt/service/template)
-b = sudo | --check = dry run | --diff = show changes | -e = override var
handler = runs only on CHANGED (once at end) | facts = auto-gathered host info (ansible_*)
role = reusable task bundle | vault = encrypted secrets at rest
Golden rules: Always --check before a production run. Use template (not copy) for files needing per-host variable substitution. Store secrets in Ansible Vault โ never in plaintext YAML committed to Git.
๐ค Interview one-liners: - "agentless โ how does it work?" โ Ansible SSH-es from the control node; no daemon runs on the managed host โ only Python must be present. The control node pushes modules over SSH, executes them, cleans up. - "idempotent โ why does it matter?" โ modules check current state before acting; running the same playbook twice leaves the system unchanged on the second run โ safe for automation and safe to re-run after failures. - "Ansible vs Terraform?" โ Terraform provisions infrastructure (creates VMs, VPCs, disks); Ansible configures what's on those VMs (installs packages, writes configs, starts services). They complement each other โ Terraform first, Ansible second.
๐๏ธ Terraform¶
Mental model: Declare infrastructure in HCL; Terraform calls the cloud provider's API to make reality match the declaration. A state file tracks what was created. plan shows the diff; apply executes it.
โ Deep concepts: M1 โ Terraform
Core workflow (4 commands)¶
terraform init # download providers, configure the backend โ run once per project
terraform fmt # format all .tf files (run in CI as a lint check)
terraform validate # syntax + type check (no API calls)
terraform plan # PREVIEW โ always run before apply; read the +/~/- diff carefully
terraform apply # execute the plan (prompts for confirmation)
terraform apply -auto-approve # skip prompt โ CI only
terraform show # human-readable current state
terraform output # print declared output values
terraform destroy # tear down all managed resources โ stops the bill
Read the plan symbols:
+= create,~= update in place,-= destroy,-/+= destroy and recreate. Neverapplywithout readingplan.
HCL blocks¶
# Provider configuration
provider "aws" {
region = "us-east-1"
}
# Resource โ the thing to create/manage
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = var.instance_type
tags = {
Name = "web-server"
Environment = var.environment
}
}
# Variable โ parameterise your code
variable "instance_type" {
type = string
default = "t3.micro"
}
# Output โ expose values for humans or other modules
output "web_public_ip" {
value = aws_instance.web.public_ip
}
# Data source โ READ an existing cloud resource (not created by Terraform)
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-*-22.04-amd64-*"]
}
owners = ["099720109477"]
}
Reference syntax: <resource_type>.<name>.<attribute> โ e.g., aws_instance.web.public_ip
State management (top interview topic)¶
terraform state list # list all resources Terraform tracks
terraform state show <address> # detail for one specific resource
terraform state rm <address> # stop tracking (resource survives in cloud)
terraform import <address> <id> # adopt an existing cloud resource into state
Remote backend with locking for teams:
terraform {
backend "s3" {
bucket = "acme-tf-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks" # prevents two concurrent applies
encrypt = true
}
}
State can hold secrets โ never commit
.tfstateor.tfvarsto Git. Add both to.gitignore.
Variables and environments¶
terraform plan -var="instance_type=t3.small"
terraform plan -var-file="prod.tfvars"
TF_VAR_instance_type=t3.small terraform plan # environment variable
# Precedence (lowest โ highest):
# defaults < TF_VAR_ env vars < .tfvars file < -var flag
Modules¶
module "vpc" {
source = "./modules/vpc"
cidr_block = "10.0.0.0/16"
name = "prod-vpc"
}
# Reference module output in another resource
resource "aws_instance" "web" {
subnet_id = module.vpc.public_subnet_id
}
Dev / staging / prod call the same module with different .tfvars files โ one code path, multiple environments.
Meta-arguments¶
# count โ index-based; removing a middle element shifts all indexes
resource "aws_instance" "web" {
count = 3
instance_type = "t3.micro"
}
# for_each โ key-based; removing one key only touches that resource
resource "aws_s3_bucket" "logs" {
for_each = toset(["dev", "staging", "prod"])
bucket = "${each.key}-acme-logs"
}
lifecycle {
create_before_destroy = true # blue/green style replacement
prevent_destroy = true # safety net for production databases
ignore_changes = [tags] # don't revert external tag changes
}
Mini-lab (no cloud account needed)¶
# main.tf โ uses only the local provider, no cloud credentials required
terraform {
required_providers {
local = { source = "hashicorp/local" }
}
}
resource "local_file" "hello" {
content = "Terraform was here\n"
filename = "${path.module}/output.txt"
}
output "file_path" {
value = local_file.hello.filename
}
terraform init
terraform plan
terraform apply -auto-approve
cat output.txt # Terraform was here
terraform state list # local_file.hello
terraform destroy -auto-approve
This covers the full init โ plan โ apply โ state โ destroy lifecycle on your laptop.
20-second cheat-sheet¶
init โ providers | plan โ preview (always first) | apply โ create/change | destroy โ $0
state = map between TF code and real cloud resources โ remote + locked for teams
variable precedence: defaults < TF_VAR_ < .tfvars < -var (highest wins)
module = reusable block | for_each > count (safer on list changes)
never commit: .tfstate / .tfvars / .terraform/
Golden rules: Remote state with DynamoDB lock. Never commit .tfstate, .tfvars, or .terraform/. Pin provider versions (~> 5.0). Run fmt and validate in CI before plan.
๐ค Interview one-liners: - "what is the state file?" โ the source of truth that maps Terraform resource addresses to real cloud resource IDs; without it, Terraform can't know what it created and would try to create everything again. - "how do teams share state safely?" โ remote backend (S3) + state locking (DynamoDB) โ the lock prevents two engineers from running
applysimultaneously and corrupting the state file. - "count vs for_each?" โcountis index-based โ remove item [1] from the middle and all higher indexes shift, causing unnecessary destroys.for_eachuses string keys, so removing one item only touches that exact resource.
๐ GitOps / ArgoCD¶
Mental model: Git is the single source of truth. An in-cluster agent (ArgoCD) continuously pulls desired state from Git and reconciles the cluster toward it. Nobody runs kubectl apply by hand โ every change goes through a Git commit.
โ Deep concepts: M7 โ GitOps ยท ch19 โ Follow One Commit
Push (traditional CI) vs Pull (GitOps)¶
| Push โ traditional | Pull โ GitOps / ArgoCD | |
|---|---|---|
| Who applies to cluster | CI runner runs kubectl apply |
ArgoCD inside the cluster reads Git |
| Cluster credentials | Stored in CI environment (risk: leaked pipeline = cluster access) | Stay in the cluster โ never leave |
| Drift detection | None | Continuous โ auto-reverts on selfHeal |
| Audit trail | CI logs | Git history (immutable, PR-reviewed) |
| Rollback | Re-run an older pipeline | git revert + push, or ArgoCD CLI |
๐ฎ๐ณ Hinglish intuition: Push = CI ko cluster ki chabi de di โ koi bhi CI job cluster touch kar sakta hai. Pull = ArgoCD andar baitha hai, woh Git dekh ke kaam karta hai โ bahar se koi directly cluster nahi chhuta.
Install ArgoCD¶
kubectl create namespace argocd
kubectl apply -n argocd \
-f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Get the initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath='{.data.password}' | base64 -d
# Access the UI locally
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Open https://localhost:8080 โ login with admin / <password above>
Application CRD¶
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/acme/k8s-manifests
targetRevision: main
path: apps/my-app/overlays/staging
destination:
server: https://kubernetes.default.svc # same cluster ArgoCD runs in
namespace: my-app
syncPolicy:
automated: # OMIT this block for MANUAL sync (production approval)
prune: true # delete resources that were removed from Git
selfHeal: true # revert any manual changes (drift correction)
syncOptions:
- CreateNamespace=true
Sync policy per environment:
| Environment | Policy | Why |
|---|---|---|
dev / staging |
automated: {prune: true, selfHeal: true} |
Auto-deploy every push โ fast feedback |
production |
No automated block โ manual sync |
Human approval required before deploying |
ArgoCD CLI¶
argocd login localhost:8080 --insecure
argocd app list
argocd app get my-app
argocd app sync my-app # trigger sync manually (prod approval flow)
argocd app diff my-app # show exact diff between Git and cluster
argocd app history my-app # list past syncs with IDs
argocd app rollback my-app <id> # roll back to a previous sync state
argocd app set my-app --sync-policy automated
argocd app delete my-app
Sync waves, hooks, and App-of-Apps¶
# Sync waves โ lower number is applied first (ensures DB is ready before app starts)
metadata:
annotations:
argocd.argoproj.io/sync-wave: "1" # database manifests
# App manifests use wave "2" โ they wait until all wave-1 resources are Healthy
# Sync hooks โ run a Job at a specific phase
metadata:
annotations:
argocd.argoproj.io/hook: PreSync # options: PreSync / Sync / PostSync
# Use PreSync for database migrations, PostSync for smoke tests
App-of-Apps โ one root Application points to a directory of Application manifests. One PR deploys an entire fleet:
Status terms¶
| Status | Meaning |
|---|---|
Synced |
Cluster matches Git exactly |
OutOfSync |
Cluster differs from Git (drift or pending deploy) |
Healthy |
All resources passing health checks |
Degraded |
At least one resource failing health checks |
Progressing |
Rolling update or sync in progress |
Rollback options¶
# Option 1 โ Git-native (preferred โ Git history stays honest)
git revert <bad-commit-hash>
git push # ArgoCD detects the new commit โ re-syncs automatically
# Option 2 โ ArgoCD CLI
argocd app rollback my-app <history-id>
Debug¶
argocd app get my-app # overall status + sync conditions
argocd app diff my-app # exact diff between Git desired state and cluster
argocd app logs my-app # application container logs via ArgoCD
# Dig into the ArgoCD internals
kubectl -n argocd logs deployment/argocd-application-controller # sync + health logic
kubectl -n argocd logs deployment/argocd-repo-server # manifest render errors
Common issues: OutOfSync stuck โ immutable field changed (need delete + recreate) or a failing sync hook. ComparisonError โ repo-server can't render manifests (Helm/Kustomize error, missing values file).
Mini-lab โ prove drift correction¶
# After ArgoCD is installed and an Application is synced and automated:
kubectl scale deployment my-app --replicas=0 -n my-app # manual change = drift
# Watch: within ~3 minutes ArgoCD detects OutOfSync + selfHeal restores replicas
argocd app get my-app # status returns to Synced / Healthy
20-second cheat-sheet¶
Git = single source of truth | ArgoCD = in-cluster reconciler
sync = make cluster match Git | OutOfSync = drift or pending deploy
selfHeal = auto-revert manual changes | prune = delete what's removed from Git
staging = automated sync | prod = manual sync (approval)
sync waves = ordering (DB before app) | App-of-Apps = fleet at scale
rollback = git revert + push (preferred) or argocd app rollback
Golden rules: CI only commits to Git โ never touches the cluster directly. Production sync is always manual. Drift is a signal, not an incident, when selfHeal is on.
๐ค Interview one-liners: - "push vs pull model?" โ push: CI runs kubectl (cluster credentials live in CI โ one compromised pipeline = full cluster access). Pull: ArgoCD inside the cluster reads Git โ credentials never leave; audit trail is Git history. - "what happens when someone manually scales a deployment?" โ ArgoCD detects the mismatch (
OutOfSync) and, ifselfHealis on, reverts it automatically โ the cluster always converges back to what Git says. - "how do you roll back with ArgoCD?" โgit revertthe bad commit and push; ArgoCD re-syncs automatically. This is cleaner than the CLI rollback because Git history stays honest and the change goes through review.
๐ง Jenkins¶
Mental model: Automation server for CI/CD. The controller orchestrates (schedules builds, stores history, serves the UI); agents execute the actual work. A Jenkinsfile in the repo defines the pipeline โ versioned alongside the code it builds. git push โ webhook โ controller โ assigns an agent โ agent runs the Jenkinsfile stages.
Never run heavy build work directly on the controller โ it's an orchestrator, not a build machine.
โ Deep concepts: M6 โ CI/CD
๐ฎ๐ณ Hinglish intuition: Controller ek manager hai โ kaam batata hai lekin khud nahi karta. Agents actual kaam karte hain (compile, test, build). Controller pe heavy build mat chalaao โ woh sirf schedule aur monitor kare.
Declarative Jenkinsfile anatomy¶
Study every block โ this is the production-grade template:
pipeline {
// Where to run โ any agent, a labeled agent, or a container
agent any
options {
timestamps() // prefix every log line with a timestamp
timeout(time: 30, unit: 'MINUTES') // fail the build if stuck
disableConcurrentBuilds() // prevent parallel runs on the same branch
}
environment {
REGISTRY = 'docker.io/acme'
IMAGE_TAG = "${env.GIT_COMMIT?.take(7)}" // 7-char git SHA as the image tag
}
stages {
stage('Checkout') {
steps { checkout scm } // clone the repo that triggered this build
}
stage('Test') {
// Run tests inside a container โ no runtime needs to be installed on the agent
agent {
docker {
image 'golang:1.22'
reuseNode true
}
}
steps {
sh 'go test -race ./...'
}
}
stage('Build image') {
steps {
sh "docker build -t ${REGISTRY}/app:${IMAGE_TAG} ."
}
}
stage('Security scan') {
// Scan for CVEs BEFORE pushing โ gate on HIGH and CRITICAL
steps {
sh "trivy image --exit-code 1 --severity HIGH,CRITICAL ${REGISTRY}/app:${IMAGE_TAG}"
}
}
stage('Push') {
// Only push on main โ feature branches test but don't publish images
when { branch 'main' }
steps {
withCredentials([usernamePassword(
credentialsId: 'dockerhub-creds',
usernameVariable: 'REG_USER',
passwordVariable: 'REG_PASS'
)]) {
sh 'echo "$REG_PASS" | docker login -u "$REG_USER" --password-stdin'
sh "docker push ${REGISTRY}/app:${IMAGE_TAG}"
}
}
}
stage('Update manifest') {
// GitOps handoff โ bump the image tag in the k8s config repo
// ArgoCD detects the Git change and deploys. Jenkins NEVER runs kubectl.
when { branch 'main' }
steps {
dir('/path/to/k8s-config') {
sh "kustomize edit set image app=${REGISTRY}/app:${IMAGE_TAG}"
sh "git commit -am 'ci: bump app to ${IMAGE_TAG}'"
sh "git push"
}
}
}
}
post {
always { cleanWs() } // clean workspace after every run
success { echo 'Pipeline passed' }
failure { echo 'Pipeline failed โ check the failing stage' }
unstable { echo 'Tests passed but below coverage threshold' }
}
}
Key blocks reference¶
| Block | Purpose |
|---|---|
agent |
Where to run: any, { label 'docker' }, { docker { image '...' } } |
environment |
Inject env vars for all stages; reference with ${VAR} |
options |
Pipeline-level settings: timeout, timestamps, retry count, concurrency |
parameters |
Declare build-time inputs: string, choice, boolean |
when |
Conditional stage execution: branch 'main', expression { ... }, tag 'v*' |
post |
After-run actions: always / success / failure / unstable / aborted |
steps |
Actual commands: sh (Unix), bat (Windows), echo, checkout scm |
Credentials¶
// Username + password (registry, database)
withCredentials([usernamePassword(
credentialsId: 'myservice-creds',
usernameVariable: 'USR',
passwordVariable: 'PWD'
)]) {
sh 'curl -u "$USR:$PWD" https://api.example.com'
}
// Secret text (API token)
withCredentials([string(credentialsId: 'sonar-token', variable: 'SONAR')]) {
sh "sonar-scanner -Dsonar.login=$SONAR"
}
// SSH private key
withCredentials([sshUserPrivateKey(credentialsId: 'deploy-key', keyFileVariable: 'KEY')]) {
sh "ssh -i $KEY deployer@host 'ls /app'"
}
Credentials are stored encrypted in the Jenkins Credential store. They are injected at runtime only and automatically masked in build logs โ never hardcode them or echo them in a step.
Parallel stages¶
stage('Quality gates') {
parallel {
stage('Unit tests') { steps { sh 'make test-unit' } }
stage('Lint') { steps { sh 'make lint' } }
stage('Dependency audit') { steps { sh 'npm audit --audit-level=high' } }
}
}
// All three run simultaneously โ the stage completes when all children finish (or any fails)
Triggers¶
triggers {
// Webhook โ best option; near-instant, no polling overhead
// Configure in your Git host: Settings โ Webhooks โ point to Jenkins URL
githubPush()
// Poll SCM โ fallback when webhooks are not possible
pollSCM('H/5 * * * *') // check every 5 minutes
// Scheduled โ nightly build or periodic release
cron('H 2 * * *')
}
Multibranch Pipeline โ create one Jenkins job that auto-discovers all branches and PRs in a repository. Each branch gets its own build history and Jenkinsfile. PRs trigger build + test before merge is allowed.
Shared libraries¶
Avoid duplicating pipeline logic across repositories. Place common steps in a dedicated shared library repo:
# shared-lib/vars/buildAndPush.groovy
def call(String imageName, String tag) {
sh "docker build -t ${imageName}:${tag} ."
sh "docker push ${imageName}:${tag}"
}
Reference it in any Jenkinsfile:
@Library('shared-lib') _
pipeline {
stages {
stage('Build & Push') {
steps { buildAndPush('acme/app', env.GIT_COMMIT.take(7)) }
}
}
}
Configure the library in Jenkins โ Manage Jenkins โ Configure System โ Global Pipeline Libraries.
Jenkins vs GitHub Actions¶
| Jenkins | GitHub Actions | |
|---|---|---|
| Hosting | Self-hosted (you run controller + agents) | Managed by GitHub |
| Config format | Groovy Jenkinsfile | YAML workflow files |
| Plugin ecosystem | 1,800+ plugins | GitHub Marketplace actions |
| GitHub integration | Via webhook plugin | Native, seamless |
| Secrets storage | Jenkins Credential store | GitHub Secrets |
| Infrastructure cost | Your servers | Included minutes + pay-per-use |
| Best for | On-prem, large orgs, complex multi-stage pipelines | GitHub-native projects, lower ops overhead |
The pipeline logic is the same โ test, build, scan, push, bump tag. Jenkins gives maximum control; Actions gives minimum ops burden. Choose by where your infrastructure lives.
Jenkins + GitOps (the correct CI/CD split)¶
git push
โ Jenkins (webhook)
โ checkout โ test โ build image โ security scan (Trivy)
โ push image to registry
โ bump image tag in k8s-config repo โ git push
โ
ArgoCD detects Git change
โ syncs cluster
โ rolling update
Jenkins handles CI (code โ tested artifact โ image tag committed to Git). ArgoCD handles CD (Git โ cluster). Jenkins never runs kubectl apply. It never holds cluster credentials.
Mini-lab (local Jenkins in Docker)¶
# Start Jenkins locally โ takes ~2 minutes to initialise
docker run -d \
-p 8080:8080 \
-p 50000:50000 \
-v jenkins_home:/var/jenkins_home \
--name jenkins \
jenkins/jenkins:lts
# Retrieve the initial unlock password
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
Open http://localhost:8080, paste the password, install suggested plugins, then create a Pipeline job with this starter Jenkinsfile:
pipeline {
agent any
stages {
stage('Hello') { steps { echo 'Build started' } }
stage('Test') { steps { sh 'echo "all tests pass"' } }
stage('Done') { steps { echo "Build #${env.BUILD_NUMBER} complete" } }
}
post { always { echo 'Workspace clean' } }
}
Next step: Put the Jenkinsfile in a real Git repo, create a Multibranch Pipeline job, and configure the repo URL โ Jenkins auto-discovers branches and runs each one's Jenkinsfile independently.
20-second cheat-sheet¶
controller = orchestrator | agent = executor (run builds here, not controller)
stages = sequential | parallel stages = concurrent
when { branch 'main' } = gate a stage to main only
withCredentials = inject secrets (masked in logs โ never hardcode)
post { always / success / failure } = cleanup and notifications
triggers: githubPush (best) | pollSCM | cron
shared library = reuse pipeline logic across repos
Jenkins = CI only | ArgoCD = CD | Jenkins never kubectl apply
Golden rules: Never build on the controller. Store secrets in the Credential store, never in the Jenkinsfile. Gate push and deploy stages with when { branch 'main' }. Scan before pushing (Trivy). Keep the Jenkinsfile in the repo โ pipeline-as-code means it's reviewed like production code.
๐ค Interview one-liners: - "declarative vs scripted pipeline?" โ declarative has a rigid validated
pipeline {}structure (stages, steps, post) โ 95% of use cases; scripted is raw Groovy (full power, no guardrails โ for edge cases only). - "how do you handle secrets in Jenkins?" โ store in the Credential store (encrypted at rest), inject viawithCredentialsat runtime โ values appear masked in logs; never echo, never hardcode. - "Jenkins vs GitHub Actions?" โ same pipeline concept; Jenkins is self-hosted with 1,800+ plugins (full control, more operational overhead); Actions is GitHub-managed YAML (lower overhead, tight GitHub integration). Choose by where your infra lives.
๐ค Interview rapid-fire (all tools)¶
Answer aloud before checking. Pairs with the Interview Bank.
Git¶
| Question | Answer |
|---|---|
| merge vs rebase? | merge preserves history + adds a merge commit (safe on shared branches); rebase replays commits linearly โ local branches only, never shared |
| revert vs reset? | revert adds a new undo commit โ safe on pushed code; reset moves HEAD and rewrites history โ local only |
| detached HEAD? | HEAD points to a commit hash, not a branch โ commits made here are unreachable after switching unless you create a branch |
| recover a lost commit? | git reflog shows every HEAD move; copy the hash; git reset --hard <hash> |
Docker¶
| Question | Answer |
|---|---|
| image vs container? | image = frozen read-only blueprint; container = running instance with a writable layer โ one image, many containers |
| why multi-stage build? | final image contains only the compiled artifact, not build tools or source โ small, secure, faster to pull |
| Docker on K8s nodes? | No since K8s 1.24 โ dockershim removed; nodes use containerd directly; Docker is for building images in CI |
kubectl¶
| Question | Answer |
|---|---|
| CrashLoopBackOff โ how to debug? | get pods โ describe pod (events) โ logs --previous (crash output) โ get events โ top pod (OOM?) |
| apply vs create? | apply is idempotent (create or update); create fails if the resource already exists โ use apply in GitOps |
| liveness vs readiness? | liveness restarts the container on failure; readiness removes the pod from the Service until it passes โ different failure modes |
Ansible¶
| Question | Answer |
|---|---|
| agentless โ how? | SSH from the control node; no daemon on the managed host โ only Python required |
| idempotent โ what does it mean? | running the same playbook twice leaves the system unchanged and reports zero changes on the second run |
| Ansible vs Terraform? | Terraform provisions infra (VMs, networks, disks); Ansible configures what's on them (packages, services, files) |
Terraform¶
| Question | Answer |
|---|---|
| what is the state file? | maps Terraform resource addresses to real cloud resource IDs โ without it Terraform can't know what it created |
| team state safety? | remote backend (S3) + DynamoDB lock โ prevents two engineers from running apply simultaneously |
| plan vs apply? | plan is a safe read-only preview of changes; apply executes them โ always plan first |
GitOps / ArgoCD¶
| Question | Answer |
|---|---|
| push vs pull model? | push: CI holds cluster creds and runs kubectl (security risk); pull: ArgoCD in-cluster reads Git โ creds never leave the cluster |
| what is self-heal? | ArgoCD detects manual drift (cluster diverges from Git) and automatically reverts the cluster to Git state |
| how to roll back? | git revert the bad commit and push โ ArgoCD re-syncs automatically; Git history stays honest |
Jenkins¶
| Question | Answer |
|---|---|
| controller vs agent? | controller orchestrates (schedules, UI, logs); agent executes (build, test, scan) โ never run heavy builds on the controller |
| how do secrets work? | Credential store (encrypted at rest) + withCredentials at runtime โ values are masked in logs; never echo or hardcode |
| Jenkins + GitOps together? | Jenkins does CI (build/scan/push/bump tag in Git); ArgoCD does CD (reads Git โ deploys to cluster) โ Jenkins never touches the cluster |
Each section links to its teaching module at the top. For the full narrative walkthrough of all tools working together in one pipeline, see ch19 โ Follow One Commit.