<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Arunai&apos;s Blog</title><description>Articles on software engineering, DevOps, and creative coding.</description><link>https://ardepn.vercel.app/</link><language>en-us</language><item><title>kubectl Cheatsheet: A Top-Down Flow Map for DevOps &amp; SREs</title><link>https://ardepn.vercel.app/blog/kubectl-cheatsheet/</link><guid isPermaLink="true">https://ardepn.vercel.app/blog/kubectl-cheatsheet/</guid><description>The kubectl commands and flags I actually use, organised as Mermaid flow diagrams. Renders on the web, on GitHub, and in any markdown viewer.</description><pubDate>Fri, 08 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I keep forgetting the same flags. So here is the one place I will look from now on - kubectl at the top, everything I reach for flowing down. The diagrams are written in Mermaid, which means GitHub renders them natively, my Astro blog renders them client-side, and any decent markdown viewer can do the same.&lt;/p&gt;
&lt;p&gt;Heavy bias towards the day-to-day: &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;describe&lt;/code&gt;, &lt;code&gt;logs&lt;/code&gt;, &lt;code&gt;exec&lt;/code&gt;, &lt;code&gt;rollout&lt;/code&gt;, &lt;code&gt;drain&lt;/code&gt;. The exotic stuff (&lt;code&gt;alpha&lt;/code&gt;, &lt;code&gt;kustomize edit&lt;/code&gt;, &lt;code&gt;convert&lt;/code&gt;) is here too but lower density.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The whole thing in one picture&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    K([&quot;&amp;lt;b&amp;gt;kubectl&amp;lt;/b&amp;gt;&quot;])

    K --&amp;gt; C1[&quot;Cluster &amp;amp;amp; Config&quot;]
    K --&amp;gt; C2[&quot;View / Inspect&quot;]
    K --&amp;gt; C3[&quot;Create / Apply&quot;]
    K --&amp;gt; C4[&quot;Modify&quot;]
    K --&amp;gt; C5[&quot;Delete&quot;]
    K --&amp;gt; C6[&quot;Debug&quot;]
    K --&amp;gt; C7[&quot;Rollout&quot;]
    K --&amp;gt; C8[&quot;Scale&quot;]
    K --&amp;gt; C9[&quot;Nodes&quot;]
    K --&amp;gt; C10[&quot;Auth / RBAC&quot;]
    K --&amp;gt; C11[&quot;Networking&quot;]
    K --&amp;gt; C12[&quot;Output / Flags&quot;]

    C1 --&amp;gt; C1a[&quot;cluster-info&amp;lt;br/&amp;gt;version&amp;lt;br/&amp;gt;api-resources&amp;lt;br/&amp;gt;config *&quot;]
    C2 --&amp;gt; C2a[&quot;get&amp;lt;br/&amp;gt;describe&amp;lt;br/&amp;gt;top&amp;lt;br/&amp;gt;explain&amp;lt;br/&amp;gt;events&quot;]
    C3 --&amp;gt; C3a[&quot;apply -f / -k&amp;lt;br/&amp;gt;create&amp;lt;br/&amp;gt;run&amp;lt;br/&amp;gt;expose&quot;]
    C4 --&amp;gt; C4a[&quot;edit&amp;lt;br/&amp;gt;set&amp;lt;br/&amp;gt;patch&amp;lt;br/&amp;gt;replace&amp;lt;br/&amp;gt;label / annotate&quot;]
    C5 --&amp;gt; C5a[&quot;delete -f&amp;lt;br/&amp;gt;delete RES NAME&amp;lt;br/&amp;gt;delete -l&amp;lt;br/&amp;gt;--force --grace-period=0&quot;]
    C6 --&amp;gt; C6a[&quot;logs&amp;lt;br/&amp;gt;exec&amp;lt;br/&amp;gt;port-forward&amp;lt;br/&amp;gt;cp&amp;lt;br/&amp;gt;debug&amp;lt;br/&amp;gt;attach&quot;]
    C7 --&amp;gt; C7a[&quot;rollout status&amp;lt;br/&amp;gt;rollout history&amp;lt;br/&amp;gt;rollout undo&amp;lt;br/&amp;gt;rollout restart&amp;lt;br/&amp;gt;rollout pause/resume&quot;]
    C8 --&amp;gt; C8a[&quot;scale&amp;lt;br/&amp;gt;autoscale&quot;]
    C9 --&amp;gt; C9a[&quot;cordon / uncordon&amp;lt;br/&amp;gt;drain&amp;lt;br/&amp;gt;taint&amp;lt;br/&amp;gt;top node&quot;]
    C10 --&amp;gt; C10a[&quot;auth can-i&amp;lt;br/&amp;gt;auth whoami&amp;lt;br/&amp;gt;certificate&quot;]
    C11 --&amp;gt; C11a[&quot;port-forward&amp;lt;br/&amp;gt;proxy&quot;]
    C12 --&amp;gt; C12a[&quot;-o yaml/json/wide&amp;lt;br/&amp;gt;-o jsonpath&amp;lt;br/&amp;gt;-o custom-columns&amp;lt;br/&amp;gt;-l selector&amp;lt;br/&amp;gt;--field-selector&amp;lt;br/&amp;gt;-A / -n&amp;lt;br/&amp;gt;-w / --watch&quot;]

    classDef root fill:#ffffff,stroke:#5b8def,color:#0b1220,stroke-width:3px,font-weight:bold;
    classDef cat fill:#1f2a44,stroke:#5b8def,color:#fff;
    class K root;
    class C1,C2,C3,C4,C5,C6,C7,C8,C9,C10,C11,C12 cat;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each section below zooms into one of those branches.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. Cluster &amp;amp; config&lt;/h2&gt;
&lt;p&gt;Where am I? What am I talking to? What can it do?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    K([&quot;kubectl&quot;]) --&amp;gt; CI[&quot;cluster-info&quot;]
    K --&amp;gt; VR[&quot;version&quot;]
    K --&amp;gt; AR[&quot;api-resources&quot;]
    K --&amp;gt; AV[&quot;api-versions&quot;]
    K --&amp;gt; CFG[&quot;config&quot;]

    CI --&amp;gt; CID[&quot;dump&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;full diagnostic dump&amp;lt;/i&amp;gt;&quot;]
    VR --&amp;gt; VRS[&quot;--short&amp;lt;br/&amp;gt;--client&quot;]

    AR --&amp;gt; ARN[&quot;--namespaced=true|false&quot;]
    AR --&amp;gt; ARV[&quot;--verbs=get,list,watch&quot;]
    AR --&amp;gt; ARO[&quot;-o name&quot;]

    CFG --&amp;gt; CFV[&quot;view&amp;lt;br/&amp;gt;--minify --raw&quot;]
    CFG --&amp;gt; CFC[&quot;current-context&quot;]
    CFG --&amp;gt; CFG1[&quot;get-contexts&quot;]
    CFG --&amp;gt; CFU[&quot;use-context NAME&quot;]
    CFG --&amp;gt; CFS[&quot;set-context --current&amp;lt;br/&amp;gt;--namespace=NS&quot;]
    CFG --&amp;gt; CFR[&quot;rename-context OLD NEW&quot;]
    CFG --&amp;gt; CFD[&quot;delete-context NAME&quot;]

    classDef root fill:#ffffff,stroke:#5b8def,color:#0b1220,stroke-width:3px,font-weight:bold;
    class K root;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;kubectl cluster-info
kubectl version --short
kubectl api-resources --namespaced=true -o name
kubectl config get-contexts
kubectl config use-context prod-eu-west-1
kubectl config set-context --current --namespace=payments
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pin the namespace to the context once and stop typing &lt;code&gt;-n payments&lt;/code&gt; for the rest of the day.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Viewing &amp;amp; inspecting&lt;/h2&gt;
&lt;p&gt;The bread-and-butter loop: &lt;code&gt;get&lt;/code&gt; → &lt;code&gt;describe&lt;/code&gt; → &lt;code&gt;logs&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    K([&quot;kubectl&quot;]) --&amp;gt; GET[&quot;get&quot;]
    K --&amp;gt; DSC[&quot;describe&quot;]
    K --&amp;gt; TOP[&quot;top&quot;]
    K --&amp;gt; EXP[&quot;explain&quot;]
    K --&amp;gt; EVT[&quot;events&quot;]

    GET --&amp;gt; GR[&quot;RESOURCE [NAME]&amp;lt;br/&amp;gt;pods, deploy, svc, ing,&amp;lt;br/&amp;gt;cm, secret, pv, pvc,&amp;lt;br/&amp;gt;node, ns, sa, role, rb,&amp;lt;br/&amp;gt;hpa, pdb, job, cj, ds, sts&quot;]
    GET --&amp;gt; GF[&quot;-o wide&amp;lt;br/&amp;gt;-o yaml / json&amp;lt;br/&amp;gt;-o name&amp;lt;br/&amp;gt;-o jsonpath&amp;lt;br/&amp;gt;-o custom-columns&quot;]
    GET --&amp;gt; GS[&quot;-l app=foo&amp;lt;br/&amp;gt;--field-selector status.phase=Running&amp;lt;br/&amp;gt;--show-labels&amp;lt;br/&amp;gt;--sort-by=.metadata.creationTimestamp&quot;]
    GET --&amp;gt; GW[&quot;-w / --watch&amp;lt;br/&amp;gt;--watch-only&quot;]
    GET --&amp;gt; GA[&quot;-A / --all-namespaces&amp;lt;br/&amp;gt;-n NS&quot;]

    DSC --&amp;gt; DR[&quot;RESOURCE NAME&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;events, conditions, env&amp;lt;/i&amp;gt;&quot;]
    TOP --&amp;gt; TP[&quot;pod [-A] [--containers]&quot;]
    TOP --&amp;gt; TN[&quot;node&quot;]

    EXP --&amp;gt; EX[&quot;RESOURCE.field&amp;lt;br/&amp;gt;--recursive&quot;]

    EVT --&amp;gt; EVS[&quot;--sort-by=.lastTimestamp&amp;lt;br/&amp;gt;--for pod/NAME&amp;lt;br/&amp;gt;--types=Warning&quot;]

    classDef root fill:#ffffff,stroke:#5b8def,color:#0b1220,stroke-width:3px,font-weight:bold;
    class K root;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# Wide pod listing across the cluster, sorted by node
kubectl get pods -A -o wide --sort-by=.spec.nodeName

# Only failing pods
kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded

# Last 50 cluster events, newest first
kubectl get events -A --sort-by=.lastTimestamp | tail -n 50

# What fields does a Deployment have?
kubectl explain deployment.spec.strategy --recursive

# Per-pod CPU/mem
kubectl top pod -A --containers --sort-by=memory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;describe&lt;/code&gt; is where the truth lives - look at the &lt;code&gt;Events:&lt;/code&gt; section before anything else.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Creating &amp;amp; applying&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    K([&quot;kubectl&quot;]) --&amp;gt; APP[&quot;apply&quot;]
    K --&amp;gt; CRT[&quot;create&quot;]
    K --&amp;gt; RUN[&quot;run&quot;]
    K --&amp;gt; EXP[&quot;expose&quot;]
    K --&amp;gt; DIF[&quot;diff&quot;]

    APP --&amp;gt; AF[&quot;-f file.yaml&amp;lt;br/&amp;gt;-f dir/&amp;lt;br/&amp;gt;-R recursive&quot;]
    APP --&amp;gt; AK[&quot;-k overlay/&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;kustomize&amp;lt;/i&amp;gt;&quot;]
    APP --&amp;gt; AS[&quot;--server-side&amp;lt;br/&amp;gt;--force-conflicts&amp;lt;br/&amp;gt;--field-manager=NAME&quot;]
    APP --&amp;gt; APR[&quot;--prune -l app=foo&amp;lt;br/&amp;gt;--dry-run=client|server&quot;]

    CRT --&amp;gt; CD[&quot;deployment NAME --image=IMG&amp;lt;br/&amp;gt;--replicas=N --port=P&quot;]
    CRT --&amp;gt; CS[&quot;secret generic NAME&amp;lt;br/&amp;gt;--from-literal=k=v&amp;lt;br/&amp;gt;--from-file=path&quot;]
    CRT --&amp;gt; CC[&quot;configmap NAME&amp;lt;br/&amp;gt;--from-literal --from-file --from-env-file&quot;]
    CRT --&amp;gt; CSA[&quot;serviceaccount NAME&quot;]
    CRT --&amp;gt; CRB[&quot;rolebinding NAME&amp;lt;br/&amp;gt;--role=R --user=U --serviceaccount=NS:SA&quot;]
    CRT --&amp;gt; CJ[&quot;job NAME --image=IMG -- cmd&quot;]
    CRT --&amp;gt; CCJ[&quot;cronjob NAME --image=IMG --schedule=&apos;*/5 * * * *&apos;&quot;]

    RUN --&amp;gt; RP[&quot;NAME --image=IMG&amp;lt;br/&amp;gt;--restart=Never&amp;lt;br/&amp;gt;--rm -it -- sh&quot;]

    EXP --&amp;gt; EXD[&quot;deployment NAME&amp;lt;br/&amp;gt;--port=80 --target-port=8080&amp;lt;br/&amp;gt;--type=ClusterIP|NodePort|LoadBalancer&quot;]

    DIF --&amp;gt; DF[&quot;-f file.yaml&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;show drift before apply&amp;lt;/i&amp;gt;&quot;]

    classDef root fill:#ffffff,stroke:#5b8def,color:#0b1220,stroke-width:3px,font-weight:bold;
    class K root;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# The two commands that pay rent
kubectl apply -f manifests/
kubectl apply -k overlays/prod

# Server-side apply with explicit field ownership
kubectl apply -f deploy.yaml --server-side --field-manager=ci

# Throwaway debug shell on the cluster network
kubectl run tmp --rm -it --image=nicolaka/netshoot --restart=Never -- bash

# Generate manifests instead of creating - great for committing to git
kubectl create deployment web --image=nginx --replicas=3 --dry-run=client -o yaml &amp;gt; web.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;kubectl diff -f file.yaml&lt;/code&gt; before every &lt;code&gt;apply&lt;/code&gt; against a real cluster. It will save you at least one outage per quarter.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. Modifying in place&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    K([&quot;kubectl&quot;]) --&amp;gt; ED[&quot;edit&quot;]
    K --&amp;gt; SET[&quot;set&quot;]
    K --&amp;gt; PT[&quot;patch&quot;]
    K --&amp;gt; RP[&quot;replace&quot;]
    K --&amp;gt; LB[&quot;label&quot;]
    K --&amp;gt; AN[&quot;annotate&quot;]
    K --&amp;gt; SC[&quot;scale&quot;]

    ED --&amp;gt; ER[&quot;RESOURCE NAME&amp;lt;br/&amp;gt;--output-patch&amp;lt;br/&amp;gt;--save-config&quot;]

    SET --&amp;gt; SI[&quot;image deploy/NAME&amp;lt;br/&amp;gt;container=IMG&quot;]
    SET --&amp;gt; SE[&quot;env deploy/NAME&amp;lt;br/&amp;gt;KEY=VAL --from=cm/NAME --from=secret/NAME&quot;]
    SET --&amp;gt; SR[&quot;resources deploy/NAME&amp;lt;br/&amp;gt;--limits=cpu=1,memory=1Gi&amp;lt;br/&amp;gt;--requests=cpu=100m,memory=128Mi&quot;]
    SET --&amp;gt; SS[&quot;serviceaccount deploy/NAME SA&quot;]
    SET --&amp;gt; SSEL[&quot;selector svc NAME k=v&quot;]

    PT --&amp;gt; PJ[&quot;-p &apos;{...}&apos; --type=strategic|merge|json&quot;]
    RP --&amp;gt; RF[&quot;-f file.yaml&amp;lt;br/&amp;gt;--force &amp;lt;i&amp;gt;delete + recreate&amp;lt;/i&amp;gt;&quot;]

    LB --&amp;gt; LR[&quot;RESOURCE NAME k=v&amp;lt;br/&amp;gt;k- &amp;lt;i&amp;gt;remove&amp;lt;/i&amp;gt;&amp;lt;br/&amp;gt;--overwrite&amp;lt;br/&amp;gt;--all&quot;]
    AN --&amp;gt; AR[&quot;RESOURCE NAME k=v&amp;lt;br/&amp;gt;k- &amp;lt;i&amp;gt;remove&amp;lt;/i&amp;gt;&quot;]

    SC --&amp;gt; SCR[&quot;deploy/NAME --replicas=N&amp;lt;br/&amp;gt;--current-replicas=N &amp;lt;i&amp;gt;guard&amp;lt;/i&amp;gt;&quot;]

    classDef root fill:#ffffff,stroke:#5b8def,color:#0b1220,stroke-width:3px,font-weight:bold;
    class K root;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# Rotate the image without touching git
kubectl set image deploy/web web=ghcr.io/me/web:v1.2.3

# Bump the JVM heap on a running pod&apos;s parent deployment
kubectl set resources deploy/api --requests=memory=512Mi --limits=memory=1Gi

# JSON-patch a single field
kubectl patch deploy/web --type=json \
  -p=&apos;[{&quot;op&quot;:&quot;replace&quot;,&quot;path&quot;:&quot;/spec/replicas&quot;,&quot;value&quot;:5}]&apos;

# Strategic merge patch from a file
kubectl patch deploy/web --patch-file patch.yaml

# Quarantine a pod for forensics - relabel so the Service stops sending traffic
kubectl label pod web-abc123 app- quarantined=true --overwrite
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;label&lt;/code&gt; with the dash suffix (&lt;code&gt;app-&lt;/code&gt;) removes the label. That trick - relabel a misbehaving pod off the Service so it stops taking traffic but stays alive for &lt;code&gt;kubectl exec&lt;/code&gt; - has saved me more than once.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. Deleting&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    K([&quot;kubectl&quot;]) --&amp;gt; DEL[&quot;delete&quot;]
    DEL --&amp;gt; DF[&quot;-f file.yaml&amp;lt;br/&amp;gt;-k overlay/&quot;]
    DEL --&amp;gt; DR[&quot;RESOURCE NAME&amp;lt;br/&amp;gt;RESOURCE/NAME&quot;]
    DEL --&amp;gt; DS[&quot;-l app=foo&amp;lt;br/&amp;gt;--field-selector=...&quot;]
    DEL --&amp;gt; DA[&quot;--all&amp;lt;br/&amp;gt;--all-namespaces&quot;]
    DEL --&amp;gt; DG[&quot;--grace-period=N&amp;lt;br/&amp;gt;--force &amp;lt;i&amp;gt;(0 + force = SIGKILL now)&amp;lt;/i&amp;gt;&quot;]
    DEL --&amp;gt; DC[&quot;--cascade=background|foreground|orphan&quot;]
    DEL --&amp;gt; DW[&quot;--wait=true|false&amp;lt;br/&amp;gt;--timeout=30s&quot;]
    DEL --&amp;gt; DN[&quot;--now &amp;lt;i&amp;gt;same as grace 1s&amp;lt;/i&amp;gt;&quot;]
    DEL --&amp;gt; DI[&quot;--ignore-not-found&quot;]

    classDef root fill:#ffffff,stroke:#5b8def,color:#0b1220,stroke-width:3px,font-weight:bold;
    class K root;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# Force-evict a stuck pod (last resort - the kubelet may not have released resources)
kubectl delete pod web-abc123 --grace-period=0 --force

# Purge everything matching a label, in every namespace
kubectl delete deploy,svc,cm,secret -l owner=ephemeral -A

# Delete by file but ignore &quot;not found&quot; so it is idempotent in CI
kubectl delete -f manifests/ --ignore-not-found=true

# Orphan children instead of cascade-deleting them
kubectl delete deploy web --cascade=orphan
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;--cascade=orphan&lt;/code&gt; is invaluable when migrating ownership - delete the Deployment but keep the ReplicaSet and Pods alive, then re-adopt them.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6. Debugging pods&lt;/h2&gt;
&lt;p&gt;This is the section I open most often.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    K([&quot;kubectl&quot;]) --&amp;gt; LOG[&quot;logs&quot;]
    K --&amp;gt; EXC[&quot;exec&quot;]
    K --&amp;gt; PF[&quot;port-forward&quot;]
    K --&amp;gt; CP[&quot;cp&quot;]
    K --&amp;gt; DBG[&quot;debug&quot;]
    K --&amp;gt; ATC[&quot;attach&quot;]

    LOG --&amp;gt; LP[&quot;POD&amp;lt;br/&amp;gt;deploy/NAME&amp;lt;br/&amp;gt;job/NAME&quot;]
    LOG --&amp;gt; LF[&quot;-f / --follow&quot;]
    LOG --&amp;gt; LC[&quot;-c CONTAINER&amp;lt;br/&amp;gt;--all-containers&amp;lt;br/&amp;gt;--prefix&quot;]
    LOG --&amp;gt; LH[&quot;--previous &amp;lt;i&amp;gt;last crash&amp;lt;/i&amp;gt;&quot;]
    LOG --&amp;gt; LT[&quot;--tail=N&amp;lt;br/&amp;gt;--since=10m&amp;lt;br/&amp;gt;--since-time=RFC3339&amp;lt;br/&amp;gt;--timestamps&quot;]
    LOG --&amp;gt; LSEL[&quot;-l app=foo&amp;lt;br/&amp;gt;--max-log-requests=N&quot;]

    EXC --&amp;gt; EI[&quot;-it POD -- sh&quot;]
    EXC --&amp;gt; EC2[&quot;-c CONTAINER&quot;]
    EXC --&amp;gt; ES[&quot;-- env&amp;lt;br/&amp;gt;-- ps -ef&amp;lt;br/&amp;gt;-- cat /etc/...&amp;lt;br/&amp;gt;-- curl localhost:PORT&quot;]

    PF --&amp;gt; PFP[&quot;POD / svc/NAME / deploy/NAME&quot;]
    PF --&amp;gt; PFM[&quot;LOCAL:REMOTE&amp;lt;br/&amp;gt;:REMOTE &amp;lt;i&amp;gt;random local&amp;lt;/i&amp;gt;&quot;]
    PF --&amp;gt; PFA[&quot;--address 0.0.0.0&quot;]

    CP --&amp;gt; CPS[&quot;NS/POD:/path ./local&quot;]
    CP --&amp;gt; CPD[&quot;./local NS/POD:/path&quot;]
    CP --&amp;gt; CPC[&quot;-c CONTAINER&amp;lt;br/&amp;gt;--retries=N&quot;]

    DBG --&amp;gt; DBP[&quot;POD --image=busybox&amp;lt;br/&amp;gt;--target=CONTAINER &amp;lt;i&amp;gt;shared PID ns&amp;lt;/i&amp;gt;&amp;lt;br/&amp;gt;--share-processes&quot;]
    DBG --&amp;gt; DBN[&quot;node/NODE --image=ubuntu&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;chroot /host&amp;lt;/i&amp;gt;&quot;]
    DBG --&amp;gt; DBC[&quot;--copy-to=NEW --set-image=*=IMG&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;clone &amp;amp;amp; tweak&amp;lt;/i&amp;gt;&quot;]

    ATC --&amp;gt; ATP[&quot;POD -c CONTAINER -i -t&quot;]

    classDef root fill:#ffffff,stroke:#5b8def,color:#0b1220,stroke-width:3px,font-weight:bold;
    class K root;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# Logs from the previous container instance after a crashloop
kubectl logs deploy/api --previous --tail=200

# Live tail across every pod behind a Deployment
kubectl logs -f -l app=api --max-log-requests=20 --prefix

# Drop into a running container
kubectl exec -it deploy/api -c app -- bash

# Forward a Service port locally
kubectl port-forward svc/grafana 3000:80

# Copy a heap dump out of a pod for offline analysis
kubectl cp prod/api-7d-abc:/tmp/heap.hprof ./heap.hprof -c app

# Ephemeral debug container next to a running pod (no rebuild, no restart)
kubectl debug -it api-7d-abc --image=nicolaka/netshoot --target=app

# Debug the node itself
kubectl debug node/ip-10-0-1-23 -it --image=ubuntu
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;kubectl debug&lt;/code&gt; is the single most underused command. Distroless image with no shell? Attach a &lt;code&gt;netshoot&lt;/code&gt; ephemeral container in the same PID namespace and you can &lt;code&gt;strace&lt;/code&gt; the real process.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;7. Rollouts&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    K([&quot;kubectl&quot;]) --&amp;gt; RO[&quot;rollout&quot;]
    RO --&amp;gt; RS[&quot;status deploy/NAME&amp;lt;br/&amp;gt;-w / --watch&amp;lt;br/&amp;gt;--timeout=10m&quot;]
    RO --&amp;gt; RH[&quot;history deploy/NAME&amp;lt;br/&amp;gt;--revision=N &amp;lt;i&amp;gt;show one&amp;lt;/i&amp;gt;&quot;]
    RO --&amp;gt; RU[&quot;undo deploy/NAME&amp;lt;br/&amp;gt;--to-revision=N&quot;]
    RO --&amp;gt; RR[&quot;restart deploy/NAME&amp;lt;br/&amp;gt;ds/NAME  sts/NAME&quot;]
    RO --&amp;gt; RP[&quot;pause deploy/NAME&quot;]
    RO --&amp;gt; RZ[&quot;resume deploy/NAME&quot;]

    classDef root fill:#ffffff,stroke:#5b8def,color:#0b1220,stroke-width:3px,font-weight:bold;
    class K root;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# Wait for a deploy to become healthy (CI uses this)
kubectl rollout status deploy/api --timeout=5m

# What did the last 5 deploys actually change?
kubectl rollout history deploy/api
kubectl rollout history deploy/api --revision=42

# Roll back one revision
kubectl rollout undo deploy/api

# Roll-restart all pods (reads new ConfigMap/Secret without changing the image)
kubectl rollout restart deploy/api

# Pause mid-rollout, change something, resume - bundles multiple set commands into one rollout
kubectl rollout pause deploy/api
kubectl set image deploy/api app=img:v2
kubectl set env  deploy/api LOG_LEVEL=debug
kubectl rollout resume deploy/api
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pause / change / resume is the cleanest way to apply a multi-field change as a single rollout instead of triggering N successive rollouts.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;8. Scaling&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    K([&quot;kubectl&quot;]) --&amp;gt; SC[&quot;scale&quot;]
    K --&amp;gt; AS[&quot;autoscale&quot;]

    SC --&amp;gt; SCR[&quot;deploy/NAME --replicas=N&amp;lt;br/&amp;gt;rs/NAME  sts/NAME  rc/NAME&quot;]
    SC --&amp;gt; SCG[&quot;--current-replicas=N&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;refuse if mismatch&amp;lt;/i&amp;gt;&quot;]
    SC --&amp;gt; SCT[&quot;--timeout=30s&quot;]

    AS --&amp;gt; ASR[&quot;deploy/NAME&amp;lt;br/&amp;gt;--min=N --max=M&amp;lt;br/&amp;gt;--cpu-percent=70&quot;]

    classDef root fill:#ffffff,stroke:#5b8def,color:#0b1220,stroke-width:3px,font-weight:bold;
    class K root;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;kubectl scale deploy/api --replicas=10
kubectl scale deploy/api --replicas=10 --current-replicas=5   # refuses if drifted
kubectl autoscale deploy/api --min=2 --max=20 --cpu-percent=70
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;--current-replicas&lt;/code&gt; is your concurrency guard. Use it in any script that scales by name without holding a lock.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;9. Node operations&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    K([&quot;kubectl&quot;]) --&amp;gt; CD[&quot;cordon NODE&quot;]
    K --&amp;gt; UC[&quot;uncordon NODE&quot;]
    K --&amp;gt; DR[&quot;drain NODE&quot;]
    K --&amp;gt; TN[&quot;taint nodes NODE&quot;]
    K --&amp;gt; TPN[&quot;top node&quot;]
    K --&amp;gt; GTN[&quot;get nodes&quot;]

    DR --&amp;gt; DR1[&quot;--ignore-daemonsets&quot;]
    DR --&amp;gt; DR2[&quot;--delete-emptydir-data&quot;]
    DR --&amp;gt; DR3[&quot;--force &amp;lt;i&amp;gt;evict unmanaged pods&amp;lt;/i&amp;gt;&quot;]
    DR --&amp;gt; DR4[&quot;--grace-period=N&amp;lt;br/&amp;gt;--timeout=5m&quot;]
    DR --&amp;gt; DR5[&quot;--pod-selector=&apos;app!=critical&apos;&quot;]
    DR --&amp;gt; DR6[&quot;--disable-eviction &amp;lt;i&amp;gt;skip PDB&amp;lt;/i&amp;gt;&quot;]

    TN --&amp;gt; T1[&quot;key=value:NoSchedule&quot;]
    TN --&amp;gt; T2[&quot;key=value:PreferNoSchedule&quot;]
    TN --&amp;gt; T3[&quot;key=value:NoExecute&quot;]
    TN --&amp;gt; T4[&quot;key:effect- &amp;lt;i&amp;gt;remove taint&amp;lt;/i&amp;gt;&quot;]

    GTN --&amp;gt; GTN1[&quot;-o wide&amp;lt;br/&amp;gt;-l role=worker&amp;lt;br/&amp;gt;--show-labels&quot;]

    classDef root fill:#ffffff,stroke:#5b8def,color:#0b1220,stroke-width:3px,font-weight:bold;
    class K root;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# Standard node drain - what you do before patching the host
kubectl cordon ip-10-0-1-23
kubectl drain ip-10-0-1-23 --ignore-daemonsets --delete-emptydir-data --timeout=10m
# ... do work ...
kubectl uncordon ip-10-0-1-23

# Add a taint so only tolerating workloads land here
kubectl taint nodes gpu-0 dedicated=ml:NoSchedule

# Remove that taint
kubectl taint nodes gpu-0 dedicated:NoSchedule-

# Pressure check
kubectl top nodes
kubectl get nodes -o wide --sort-by=.status.capacity.memory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Always test &lt;code&gt;drain&lt;/code&gt; with &lt;code&gt;--dry-run=server&lt;/code&gt; first if you have PDBs - it tells you which pods would block the drain before you commit.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;10. Auth &amp;amp; RBAC&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    K([&quot;kubectl&quot;]) --&amp;gt; AU[&quot;auth&quot;]
    K --&amp;gt; CRT[&quot;certificate&quot;]

    AU --&amp;gt; AC[&quot;can-i VERB RESOURCE&amp;lt;br/&amp;gt;--namespace=NS&amp;lt;br/&amp;gt;--as=USER&amp;lt;br/&amp;gt;--as-group=GRP&amp;lt;br/&amp;gt;--all-namespaces&amp;lt;br/&amp;gt;--list&quot;]
    AU --&amp;gt; AW[&quot;whoami&quot;]
    AU --&amp;gt; AR[&quot;reconcile -f rbac.yaml&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;create/update RBAC, prune extras&amp;lt;/i&amp;gt;&quot;]

    CRT --&amp;gt; CA[&quot;approve CSR-NAME&quot;]
    CRT --&amp;gt; CD[&quot;deny CSR-NAME&quot;]

    classDef root fill:#ffffff,stroke:#5b8def,color:#0b1220,stroke-width:3px,font-weight:bold;
    class K root;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# Can I do the thing?
kubectl auth can-i delete pods -n prod
kubectl auth can-i &apos;*&apos; &apos;*&apos; --all-namespaces

# Can the CI service account do the thing?
kubectl auth can-i create deployments -n prod \
  --as=system:serviceaccount:ci:deployer

# Who am I right now?
kubectl auth whoami

# List every permission I have in this namespace
kubectl auth can-i --list -n payments
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;--as&lt;/code&gt; impersonation against &lt;code&gt;auth can-i&lt;/code&gt; is the safest way to debug RBAC without giving anyone production credentials.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;11. Networking access&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    K([&quot;kubectl&quot;]) --&amp;gt; PF[&quot;port-forward&quot;]
    K --&amp;gt; PRX[&quot;proxy&quot;]

    PF --&amp;gt; PF1[&quot;pod/NAME&amp;lt;br/&amp;gt;svc/NAME&amp;lt;br/&amp;gt;deploy/NAME&quot;]
    PF --&amp;gt; PF2[&quot;LOCAL:REMOTE&amp;lt;br/&amp;gt;:REMOTE &amp;lt;i&amp;gt;random local&amp;lt;/i&amp;gt;&amp;lt;br/&amp;gt;multiple pairs ok&quot;]
    PF --&amp;gt; PF3[&quot;--address 0.0.0.0&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;expose beyond localhost&amp;lt;/i&amp;gt;&quot;]

    PRX --&amp;gt; PR1[&quot;--port=8001&amp;lt;br/&amp;gt;--address=127.0.0.1&quot;]
    PRX --&amp;gt; PR2[&quot;--accept-paths&amp;lt;br/&amp;gt;--reject-paths&amp;lt;br/&amp;gt;--api-prefix=/k8s&quot;]

    classDef root fill:#ffffff,stroke:#5b8def,color:#0b1220,stroke-width:3px,font-weight:bold;
    class K root;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# Forward a Service port - works through the API server, no LB needed
kubectl port-forward svc/postgres 5432:5432

# Hit the API directly - everything kubectl does is just HTTP
kubectl proxy --port=8001 &amp;amp;
curl http://localhost:8001/api/v1/namespaces/default/pods
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;kubectl proxy&lt;/code&gt; plus &lt;code&gt;curl&lt;/code&gt; is the cleanest way to learn the API. Watch the request kubectl actually makes with &lt;code&gt;-v=8&lt;/code&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;12. Output, selectors, and the flags you forget&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    K([&quot;kubectl&quot;]) --&amp;gt; OUT[&quot;-o / --output&quot;]
    K --&amp;gt; SEL[&quot;selectors&quot;]
    K --&amp;gt; NS[&quot;namespace&quot;]
    K --&amp;gt; WAT[&quot;watch&quot;]
    K --&amp;gt; DRY[&quot;dry-run&quot;]
    K --&amp;gt; VRB[&quot;verbosity&quot;]

    OUT --&amp;gt; O1[&quot;yaml | json&amp;lt;br/&amp;gt;name | wide&quot;]
    OUT --&amp;gt; O2[&quot;jsonpath=&apos;{.items[*].metadata.name}&apos;&quot;]
    OUT --&amp;gt; O3[&quot;jsonpath-as-json&quot;]
    OUT --&amp;gt; O4[&quot;go-template / go-template-file&quot;]
    OUT --&amp;gt; O5[&quot;custom-columns=NAME:.metadata.name,IP:.status.podIP&quot;]
    OUT --&amp;gt; O6[&quot;custom-columns-file=cols.txt&quot;]

    SEL --&amp;gt; S1[&quot;-l key=value&amp;lt;br/&amp;gt;-l &apos;key in (a,b)&apos;&amp;lt;br/&amp;gt;-l &apos;key!=value&apos;&amp;lt;br/&amp;gt;-l &apos;key&apos;  &amp;lt;i&amp;gt;exists&amp;lt;/i&amp;gt;&amp;lt;br/&amp;gt;-l &apos;!key&apos; &amp;lt;i&amp;gt;not exists&amp;lt;/i&amp;gt;&quot;]
    SEL --&amp;gt; S2[&quot;--field-selector status.phase=Running&amp;lt;br/&amp;gt;--field-selector spec.nodeName=NODE&amp;lt;br/&amp;gt;--field-selector metadata.namespace!=kube-system&quot;]

    NS --&amp;gt; N1[&quot;-n NS&amp;lt;br/&amp;gt;-A / --all-namespaces&quot;]

    WAT --&amp;gt; W1[&quot;-w / --watch&amp;lt;br/&amp;gt;--watch-only&quot;]
    WAT --&amp;gt; W2[&quot;wait --for=condition=Ready pod/NAME&amp;lt;br/&amp;gt;--for=delete&amp;lt;br/&amp;gt;--timeout=5m&quot;]

    DRY --&amp;gt; D1[&quot;--dry-run=client&amp;lt;br/&amp;gt;--dry-run=server&amp;lt;br/&amp;gt;--validate=strict&quot;]

    VRB --&amp;gt; V1[&quot;-v=6 &amp;lt;i&amp;gt;requests&amp;lt;/i&amp;gt;&amp;lt;br/&amp;gt;-v=8 &amp;lt;i&amp;gt;request bodies&amp;lt;/i&amp;gt;&amp;lt;br/&amp;gt;-v=9 &amp;lt;i&amp;gt;also responses&amp;lt;/i&amp;gt;&quot;]

    classDef root fill:#ffffff,stroke:#5b8def,color:#0b1220,stroke-width:3px,font-weight:bold;
    class K root;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# Just the pod names
kubectl get pods -o jsonpath=&apos;{.items[*].metadata.name}&apos;

# Pods + node assignment as a table
kubectl get pods -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName,IP:.status.podIP

# Wait until ready
kubectl wait --for=condition=Available deploy/api --timeout=5m
kubectl wait --for=delete pod/web-abc123 --timeout=2m

# Render a manifest server-side without persisting it - schema validation included
kubectl apply -f deploy.yaml --dry-run=server -o yaml

# What is kubectl actually sending? (debug RBAC, networking, webhooks)
kubectl get pods -v=8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;kubectl wait&lt;/code&gt; is the right answer in scripts. Polling &lt;code&gt;get&lt;/code&gt; in a loop is what people write before they discover it.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;A few more that did not fit cleanly&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# Render kustomize without applying
kubectl kustomize overlays/prod

# What changed since last apply on the server?
kubectl diff -f deploy.yaml

# Convert v1beta1 manifests to current API version
kubectl convert -f legacy.yaml --output-version apps/v1

# Plugin manager (krew) - for kubectl-tree, kubectl-neat, kubectl-stern, etc
kubectl krew install tree
kubectl tree deploy api

# Generate a bash completion
source &amp;lt;(kubectl completion bash)

# Alias I cannot live without
alias k=kubectl
complete -o default -F __start_kubectl k
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;How this page renders&lt;/h2&gt;
&lt;p&gt;The diagrams are written as standard &lt;code&gt;```mermaid&lt;/code&gt; fences in the markdown source. GitHub renders Mermaid natively, so &lt;a href=&quot;https://github.com/arunaiDeepan&quot;&gt;the file in the repo&lt;/a&gt; renders identically there. On this site, a tiny script imports &lt;code&gt;mermaid&lt;/code&gt; from a CDN, walks every &lt;code&gt;pre.mermaid&lt;/code&gt; block, and swaps in an SVG. The same source, three viewers, no images committed to git.&lt;/p&gt;
&lt;p&gt;If you spot a command I missed - especially a flag - send it my way and I will add it to the diagram.&lt;/p&gt;
</content:encoded></item><item><title>What I Learned About Image Caching From a YouTube Rabbit Hole: OpenShift vs Spegel</title><link>https://ardepn.vercel.app/blog/blog-2/</link><guid isPermaLink="true">https://ardepn.vercel.app/blog/blog-2/</guid><description>Why your redeployed image isn&apos;t updating on OpenShift, how image pull policies and CRI-O caching work, and why Spegel&apos;s P2P image sharing is cool but won&apos;t work with CRI-O.</description><pubDate>Thu, 17 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Ever pushed a new Docker image with the same tag, deployed it, and… nothing changed? Yep. Been there. Here&apos;s what I found out - and a really cool tool I wish worked on OpenShift.&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;So... Why Isn’t My New Code Showing Up?&lt;/h2&gt;
&lt;p&gt;Let’s set the scene. You’re working on OpenShift, you build a new image with the same tag (&lt;code&gt;v1.0.0&lt;/code&gt;, &lt;code&gt;latest&lt;/code&gt;, whatever), push it, redeploy, and your app is still doing the old stuff.&lt;/p&gt;
&lt;p&gt;It’s one of those classic “what the hell is going on?” moments.&lt;/p&gt;
&lt;p&gt;Turns out - it’s all about &lt;strong&gt;image caching&lt;/strong&gt;, and OpenShift is playing by its own rules (for good reasons).&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The Real Reason: OpenShift Caches Images (and It’s Not a Bug)&lt;/h2&gt;
&lt;p&gt;Here’s the deal: OpenShift (like Kubernetes in general) doesn&apos;t automatically pull updated images with the same tag unless you tell it to.&lt;/p&gt;
&lt;h3&gt;TL;DR&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;If the image tag is &lt;code&gt;latest&lt;/code&gt;&lt;/strong&gt;: OpenShift sets &lt;code&gt;imagePullPolicy: Always&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Any other tag?&lt;/strong&gt;: It defaults to &lt;code&gt;IfNotPresent&lt;/code&gt;, meaning “don’t pull again if I already have this tag locally.”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So when you push a new image with &lt;code&gt;v1.0.0&lt;/code&gt;, OpenShift is like: “Cool, I already have &lt;code&gt;v1.0.0&lt;/code&gt; cached. I’m good.” And your changes? Nowhere in sight.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.redhat.com/en/documentation/openshift_container_platform/4.8/html/images/managing-images#image-pull-policy&quot;&gt;OpenShift Docs: Managing container images&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Why This Actually Makes Sense (But Still Hurts)&lt;/h2&gt;
&lt;p&gt;This behavior isn’t dumb - it’s deliberate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Faster pod startup&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reduced registry bandwidth&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lower pull costs&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;More reliable startup when the registry is flaky&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s just not what you expect when you&apos;re in the middle of debugging and nothing looks like it&apos;s changing.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Then I Found Spegel… on YouTube&lt;/h2&gt;
&lt;p&gt;So there I was, doing the usual late-night YouTube spiral, when I randomly stumbled across Spegel in this video titled &lt;a href=&quot;https://youtu.be/TsejK1D4y5k?feature=shared&quot;&gt;Microsoft Tried To Steal A Project And Almost Got Away With It...&lt;/a&gt; - and yeah, it got interesting real fast. A cool open-source project github.com/spegel-org/spegel&lt;/p&gt;
&lt;p&gt;It does something clever: &lt;strong&gt;turns your cluster into a peer-to-peer image-sharing network.&lt;/strong&gt; Every node becomes a tiny registry, so when one node has the image, others can grab it from there - &lt;strong&gt;no need to go to an external registry&lt;/strong&gt; every time.&lt;/p&gt;
&lt;p&gt;Here’s what caught my attention:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Super fast deployments&lt;/strong&gt; (especially for larger images)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cluster-wide image caching&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reduces external registry hits&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Works transparently with existing workflows&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Why Spegel Isn&apos;t Available in OpenShift&lt;/h2&gt;
&lt;p&gt;Now here’s the twist.&lt;/p&gt;
&lt;p&gt;I got super excited, then realized... &lt;strong&gt;Spegel doesn&apos;t work on OpenShift&lt;/strong&gt;. And here&apos;s why:&lt;/p&gt;
&lt;h3&gt;It’s all about container runtimes: &lt;code&gt;containerd&lt;/code&gt; vs &lt;code&gt;CRI-O&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Spegel depends on &lt;code&gt;containerd&lt;/code&gt;&lt;/strong&gt; to manage the local image cache and registry mirroring.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenShift uses &lt;code&gt;CRI-O&lt;/code&gt;&lt;/strong&gt; as its default container runtime - chosen for its Kubernetes focus, tight SELinux integration, and security posture.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While &lt;code&gt;containerd&lt;/code&gt; is a CNCF project with rich features for pluggable registries, &lt;code&gt;CRI-O&lt;/code&gt; is &lt;strong&gt;much more minimal&lt;/strong&gt; and doesn&apos;t expose the same image-layer APIs that Spegel hooks into.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.openshift.com/container-platform/latest/architecture/architecture-rhcos.html#rhcos-about-virt-extensions_architecture-rhcos&quot;&gt;Why OpenShift uses CRI-O&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So, unless OpenShift shifts to &lt;code&gt;containerd&lt;/code&gt; (which is unlikely), tools like Spegel just aren&apos;t compatible out-of-the-box.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;OpenShift&apos;s More Traditional Approach&lt;/h2&gt;
&lt;p&gt;OpenShift’s image caching is still pretty robust, just… different. It leans on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Image streams&lt;/strong&gt;: OpenShift’s own abstraction for managing image tags and versions
&lt;a href=&quot;https://docs.redhat.com/en/documentation/openshift_container_platform/4.18/html/images/managing-image-streams&quot;&gt;OpenShift Docs: Image Streams&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Immutable tags&lt;/strong&gt;: You should really stop reusing the same tag for different images 😅&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explicit pull policies&lt;/strong&gt;: You can override the default caching by doing this:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;spec:
  containers:
    - name: myapp
      image: myregistry.com/myapp:v1.0.0
      imagePullPolicy: Always
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or better, just use unique tags for every build:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;podman build -t myapp:${GIT_COMMIT} .
podman push myapp:${GIT_COMMIT}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Spegel’s Peer-to-Peer Vibes&lt;/h2&gt;
&lt;p&gt;If you&apos;re running vanilla Kubernetes or something like K3s with &lt;code&gt;containerd&lt;/code&gt;, Spegel is amazing. It gives you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Instant image access&lt;/strong&gt; across the cluster&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic optimization&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Registry-free local pulls&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zero-cost bandwidth reuse&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No more waiting for a 1GB image to pull for the 20th time from Docker Hub.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/spegel-org/spegel&quot;&gt;Spegel GitHub Project&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Final Thoughts: Two Philosophies, Two Worlds&lt;/h2&gt;
&lt;p&gt;This whole deep dive showed me how OpenShift and Spegel represent two very different takes on the same problem:&lt;/p&gt;
&lt;h3&gt;OpenShift: The “Stable, Predictable” Way&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Relies on tags being &lt;strong&gt;immutable&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Optimized for &lt;strong&gt;enterprise-grade stability&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Makes developers responsible for versioning and caching logic&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Spegel: The “Fast and Distributed” Way&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Cluster becomes its own mini-registry&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Smart and automatic&lt;/strong&gt; caching via P2P&lt;/li&gt;
&lt;li&gt;Geared for &lt;strong&gt;speed, resilience, and cost reduction&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;TL;DR: What You Can Do on OpenShift&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Don’t reuse tags&lt;/strong&gt; - Use something like a Git SHA or timestamp&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set &lt;code&gt;imagePullPolicy: Always&lt;/code&gt;&lt;/strong&gt; when you &lt;em&gt;really&lt;/em&gt; need to force an update&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Leverage ImageStreams&lt;/strong&gt; if you’re doing tag promotion workflows&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Understand your runtime&lt;/strong&gt; - tools like Spegel won’t work with CRI-O&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;Would I Use Spegel?&lt;/h2&gt;
&lt;p&gt;Absolutely, if I were running containerd-based clusters. It’s clever, fast, and fits well in test/staging or self-hosted production.&lt;/p&gt;
&lt;p&gt;But for OpenShift? It’s not the right tool. And that’s fine. OpenShift gives me a more secure, enterprise-grade runtime and tools like ImageStreams that, once you get used to them, work pretty well.&lt;/p&gt;
&lt;p&gt;Sometimes the trade-offs are worth it.&lt;/p&gt;
&lt;hr /&gt;
</content:encoded></item><item><title>Building Animated Sine Waves for My Portfolio</title><link>https://ardepn.vercel.app/blog/fourier-portfolio/</link><guid isPermaLink="true">https://ardepn.vercel.app/blog/fourier-portfolio/</guid><description>How I built a clean, performant sine wave background animation using HTML5 Canvas and Svelte.</description><pubDate>Sat, 15 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;The Idea&lt;/h2&gt;
&lt;p&gt;I wanted a subtle, living background for my portfolio - not distracting, but enough to make it feel dynamic. Smooth sine waves flowing across the screen felt like the right balance between mathematical elegance and visual restraint.&lt;/p&gt;
&lt;h2&gt;How It Works&lt;/h2&gt;
&lt;p&gt;The animation is a Svelte component (&lt;code&gt;FourierCanvas.svelte&lt;/code&gt;) rendered as a client-side island inside an Astro layout. It uses the HTML5 Canvas API to draw 6 horizontal sine waves at different vertical positions, each flowing at its own speed and direction.&lt;/p&gt;
&lt;p&gt;Each wave is defined by a few simple parameters:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const waves = [
  { y: 0.12, amp: 22, freq: 0.0025, speed: 0.35, opacity: 0.045 },
  { y: 0.28, amp: 28, freq: 0.002, speed: -0.25, opacity: 0.04 },
  // ... 4 more waves
];
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;y&lt;/strong&gt; - vertical position as a fraction of viewport height&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;amp&lt;/strong&gt; - wave amplitude in pixels&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;freq&lt;/strong&gt; - spatial frequency (how tight the oscillation is)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;speed&lt;/strong&gt; - how fast the wave flows (negative = leftward)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;opacity&lt;/strong&gt; - transparency of the white stroke&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every frame, each wave gets a &quot;breathing&quot; multiplier - a slow sinusoidal modulation of its amplitude - so the waves gently pulse over time rather than looking static.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const breath = 1 + 0.12 * Math.sin(time * 0.4 + wave.y * 5);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Rendering&lt;/h2&gt;
&lt;p&gt;For each wave, two passes are drawn:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Filled region&lt;/strong&gt; - a very faint white fill from the wave curve down to the bottom of the canvas, creating a layered depth effect&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stroke line&lt;/strong&gt; - a crisp 1px white line tracing the sine curve itself&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The waves are all white (&lt;code&gt;rgba(255, 255, 255, ...)&lt;/code&gt;) at low opacity, which works cleanly on the dark &lt;code&gt;#161717&lt;/code&gt; background and doesn&apos;t fight with the content on top.&lt;/p&gt;
&lt;h2&gt;Performance&lt;/h2&gt;
&lt;p&gt;The animation is intentionally lightweight:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;6 waves only&lt;/strong&gt; - enough for visual interest, not enough to tax the GPU&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;4-6px step size&lt;/strong&gt; - we don&apos;t need to plot every pixel; &lt;code&gt;lineTo&lt;/code&gt; interpolates smoothly&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;requestAnimationFrame&lt;/code&gt;&lt;/strong&gt; - the browser handles 60fps scheduling natively&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Debounced resize&lt;/strong&gt; - window resize recalculates dimensions on a 150ms timeout, not every frame&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DPR-aware&lt;/strong&gt; - canvas resolution scales with &lt;code&gt;devicePixelRatio&lt;/code&gt; (capped at 2x) for crisp rendering on retina displays without wasting GPU on 3x+ screens&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fixed positioning&lt;/strong&gt; - the canvas is &lt;code&gt;position: fixed&lt;/code&gt; at &lt;code&gt;100vw x 100vh&lt;/code&gt;, covering the full viewport with zero layout cost&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Tech Stack&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Astro&lt;/strong&gt; for static site generation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Svelte&lt;/strong&gt; for the interactive canvas component (&lt;code&gt;client:only=&quot;svelte&quot;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vanilla CSS&lt;/strong&gt; with CSS custom properties for theming&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloudflare Pages&lt;/strong&gt; for deployment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The result? A clean, breathing background that adds life to the page without getting in the way.&lt;/p&gt;
</content:encoded></item></channel></rss>