Skip to content

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: revert is safe โ€” it adds a new commit that undoes the change; always use it on anything already pushed. reset moves the pointer and rewrites history โ€” local branches only. --soft keeps staged, --mixed unstages, --hard throws 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

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, and prod slowly diverge, so promotion merges get painful and "works in QA, breaks in prod" creeps in. The modern fix: one main branch + 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 reflog shows 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.

alias k=kubectl   # add to ~/.bashrc or ~/.zshrc โ€” saves thousands of keystrokes

โ†’ 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

kubectl get ns
kubectl create ns staging
kubectl get all -n staging

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?" โ†’ apply is idempotent: create if missing, update if existing; create fails if the resource already exists. Always use apply in 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 command safe hai โ€” shell features nahi, pipes nahi, zyada predictable. -m shell powerful hai โ€” pipes kab chahiye tab use karo, warna command hi 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)

# inventory-local.ini
localhost ansible_connection=local
# 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. Never apply without reading plan.

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 .tfstate or .tfvars to 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 apply simultaneously and corrupting the state file. - "count vs for_each?" โ†’ count is index-based โ€” remove item [1] from the middle and all higher indexes shift, causing unnecessary destroys. for_each uses 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:

# Root Application
spec:
  source:
    path: apps/       # directory contains one Application CRD per service

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, if selfHeal is on, reverts it automatically โ€” the cluster always converges back to what Git says. - "how do you roll back with ArgoCD?" โ†’ git revert the 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 via withCredentials at 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.