<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Devops on Backend Engineering Strategy Tools</title><link>https://backend-engineering-strategy-tools.github.io/site/tags/devops/</link><description>Recent content in Devops on Backend Engineering Strategy Tools</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Tue, 02 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://backend-engineering-strategy-tools.github.io/site/tags/devops/index.xml" rel="self" type="application/rss+xml"/><item><title>Shared Tooling Images — One Image, Three Contexts</title><link>https://backend-engineering-strategy-tools.github.io/site/thinking/shared-tooling-images/</link><pubDate>Tue, 02 Jun 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/thinking/shared-tooling-images/</guid><description>&lt;p&gt;The problem with per-context tooling: CI uses one version of a linter, a developer&amp;rsquo;s machine has another, a colleague has a third. Someone upgrades locally, the pipeline fails. Someone pins the pipeline, local runs drift. The maintenance surface is the number of places you install tools multiplied by the number of people on the team.&lt;/p&gt;
&lt;p&gt;The fix is one Docker image that travels across all three contexts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;CI/CD&lt;/strong&gt; — the pipeline pulls the image, runs the same tools&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Local development&lt;/strong&gt; — developers run the image instead of installing tools natively&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Colleagues&lt;/strong&gt; — same image, same versions, no setup docs to go stale&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When you need to upgrade a tool, you change one Dockerfile and cut a new tag. Everyone gets it on next pull. No coordination required.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="why-docker"&gt;Why Docker?
&lt;/h2&gt;&lt;p&gt;There are other ways to solve this — dotfiles, Nix, OS package managers, VMs, cloud-init. All valid in different contexts.&lt;/p&gt;
&lt;p&gt;The reason Docker makes sense here is that we are already working in a Kubernetes ecosystem where containers are the starting point. Most CI systems support Docker and OCI-compatible images natively. So the question becomes: is it worth introducing a different tool management approach alongside containers, or is it less costly to stay consistent?&lt;/p&gt;
&lt;p&gt;Personally, I lean toward staying consistent. Your mileage may vary depending on your stack.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="what-goes-in-the-image"&gt;What Goes in the Image
&lt;/h2&gt;&lt;p&gt;The principle: include anything that needs to be consistent across contexts. Linters, formatters, build tools, CLI utilities.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Not the runtime — that is the application&amp;rsquo;s own image.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;What specifically goes in depends on the target platform. A Kubernetes-only image looks different from one that also includes a cloud provider CLI. Adding tools increases the maintenance burden, so keep each image focused. That is also why there are multiple images — see &lt;a class="link" href="#the-repos" &gt;The Repos&lt;/a&gt; below.&lt;/p&gt;
&lt;p&gt;Typical candidates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kubectl&lt;/code&gt;, &lt;code&gt;helm&lt;/code&gt;, &lt;code&gt;kustomize&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Linters and formatters for the languages in use&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jq&lt;/code&gt;, &lt;code&gt;yq&lt;/code&gt;, &lt;code&gt;curl&lt;/code&gt; and similar utilities&lt;/li&gt;
&lt;li&gt;Cloud provider CLI where needed (&lt;code&gt;aws&lt;/code&gt;, &lt;code&gt;openstack&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="versioning"&gt;Versioning
&lt;/h2&gt;&lt;p&gt;Follow SemVer — image tag equals version. Merge to main triggers a release. No need to complicate it beyond trunk-based development.&lt;/p&gt;
&lt;p&gt;Before merging, build and publish an image from the branch so the change can be tested and verified before it lands on mainline. The branch image is short-lived and not promoted to &lt;code&gt;latest&lt;/code&gt; until the merge.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="usage-patterns"&gt;Usage Patterns
&lt;/h2&gt;&lt;p&gt;A shell alias or Makefile target wraps the &lt;code&gt;docker run&lt;/code&gt; so nobody has to remember the full invocation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;alias toolbox&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;docker run -it --rm -v &lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;pwd&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&lt;span style="color:#e6db74"&gt;:/work -w /work image:latest&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then &lt;code&gt;toolbox helm lint .&lt;/code&gt; or &lt;code&gt;toolbox kubectl apply -f .&lt;/code&gt; works the same locally as it does in CI.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Interactive sessions&lt;/strong&gt; are where this pattern pays off beyond CI. Drop into a persistent terminal with the right context already loaded — kubeconfig mounted in, cloud credentials available, working directory set:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -it --rm &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -v &lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;pwd&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;:/work &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -v ~/.kube:/root/.kube:ro &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -w /work &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image:latest bash
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You get a shell that looks the same for everyone on the team, regardless of what is installed on their machine.&lt;/p&gt;
&lt;p&gt;For interactive use it is worth including a help script that runs on entry — either via &lt;code&gt;ENTRYPOINT&lt;/code&gt; or by sourcing it from &lt;code&gt;.bashrc&lt;/code&gt; inside the image. It should print what tools are available, their versions, and any common usage examples. Keeps the image self-documenting: a new colleague drops in and immediately knows what they have to work with without reading a README.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# example /usr/local/bin/toolbox-help&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;=== Toolbox ===&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;kubectl &lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;kubectl version --client -o json | jq -r &lt;span style="color:#e6db74"&gt;&amp;#39;.clientVersion.gitVersion&amp;#39;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;helm &lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;helm version --short&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;aws &lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;aws --version 2&amp;gt;&amp;amp;1&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;Run &amp;#39;toolbox-help&amp;#39; at any time to see this again.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In CI the step just references the image directly — no installation step, no version pinning in the pipeline config beyond the image tag.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-repos"&gt;The Repos
&lt;/h2&gt;&lt;p&gt;Three tooling images, each a superset of the previous, plus two supporting images:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;GitHub repo&lt;/th&gt;
 &lt;th&gt;Docker Hub&lt;/th&gt;
 &lt;th&gt;Contents&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;image-tooling&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;best-tools/tooling-k8s&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;kubectl, helm, kustomize, argocd, k9s, jq, yq&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;image-tooling&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;best-tools/tooling-k8s-aws&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tooling-k8s&lt;/code&gt; + AWS CLI&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;image-tooling&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;best-tools/tooling-k8s-openstack&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tooling-k8s&lt;/code&gt; + OpenStack CLI&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;image-buildx&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;best-tools/buildx&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;CI builder — Docker buildx, AWS CLI, Dagger CLI&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;image-pandoc&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;best-tools/pandoc&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;PDF generation — pandoc + TeX Live&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Repo names use &lt;code&gt;image-&amp;lt;purpose&amp;gt;&lt;/code&gt; in the &lt;a class="link" href="https://github.com/Backend-Engineering-Strategy-Tools" target="_blank" rel="noopener"
 &gt;Backend-Engineering-Strategy-Tools&lt;/a&gt; org. Docker Hub names drop the prefix and just describe the tool. The three tooling flavours share one &lt;code&gt;image-tooling&lt;/code&gt; monorepo so dependency updates and scanning are configured once.&lt;/p&gt;
&lt;p&gt;Pipelines are written in Go using &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/cicd/dagger/" &gt;Dagger&lt;/a&gt; — the same pipeline runs locally and in CI, publishing multi-arch images (amd64 + arm64) triggered by a version tag.&lt;/p&gt;
&lt;hr&gt;</description></item><item><title>Docker &amp; OCI</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/docker/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/docker/</guid><description>&lt;p&gt;Docker packages applications and their dependencies into portable, reproducible units called containers. Unlike virtual machines, containers share the host kernel — they&amp;rsquo;re isolated processes, not emulated hardware. This makes them fast to start, light on resources, and consistent across environments: the same image runs on a developer&amp;rsquo;s laptop, in CI, and in production.&lt;/p&gt;
&lt;p&gt;Docker popularised containers, but the underlying standard is now open. The &lt;strong&gt;OCI (Open Container Initiative)&lt;/strong&gt; defines three specifications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Image spec&lt;/strong&gt; — the format of a container image: layers, config, manifest&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runtime spec&lt;/strong&gt; — how a container is run: namespaces, cgroups, lifecycle&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Distribution spec&lt;/strong&gt; — how images are pushed and pulled from registries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Any tool that produces an OCI image can run on any OCI-compliant runtime. Docker is one implementation. It is still the most natural entry point and the &lt;code&gt;docker&lt;/code&gt; CLI remains the most familiar interface, but it is worth knowing that the ecosystem is broader than Docker Inc.&lt;/p&gt;
&lt;h2 id="oci-images-and-containers"&gt;OCI images and containers
&lt;/h2&gt;&lt;p&gt;An &lt;strong&gt;image&lt;/strong&gt; is a read-only, layered filesystem snapshot built from a Dockerfile — each layer is a diff on top of the previous one. A &lt;strong&gt;container&lt;/strong&gt; is a running instance of an image — an isolated process with its own filesystem, network interface, and process space, sharing the host kernel.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker build -t myapp:1.0 . &lt;span style="color:#75715e"&gt;# build OCI image from Dockerfile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -p 8080:8080 myapp:1.0 &lt;span style="color:#75715e"&gt;# start container&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker ps &lt;span style="color:#75715e"&gt;# list running containers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker exec -it &amp;lt;id&amp;gt; bash &lt;span style="color:#75715e"&gt;# shell into a running container&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Images are stored in registries — Docker Hub, GitHub Container Registry, ECR, Nexus. All speak the OCI distribution spec, so images built with any tool push and pull the same way.&lt;/p&gt;
&lt;h2 id="dockerfile"&gt;Dockerfile
&lt;/h2&gt;&lt;p&gt;The Dockerfile defines how an image is built — each instruction adds a layer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;golang:1.22-alpine&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; &lt;span style="color:#e6db74"&gt;build&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;WORKDIR&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/app&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; go.mod go.sum ./&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; go mod download&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; . .&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; go build -o /app/server .&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;alpine:3.19&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; --from&lt;span style="color:#f92672"&gt;=&lt;/span&gt;build /app/server /server&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;EXPOSE&lt;/span&gt; &lt;span style="color:#e6db74"&gt;8080&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENTRYPOINT&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;/server&amp;#34;&lt;/span&gt;]&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Multi-stage builds&lt;/strong&gt; keep the final image lean: the first stage compiles using the full toolchain, the second copies only the binary. No compiler, no source, no build cache in the image you ship.&lt;/p&gt;
&lt;p&gt;Order matters for layer caching — put things that change rarely (dependency downloads) before things that change often (source code). A cache miss invalidates all subsequent layers.&lt;/p&gt;
&lt;h2 id="volumes-and-bind-mounts"&gt;Volumes and bind mounts
&lt;/h2&gt;&lt;p&gt;Containers have ephemeral filesystems — anything written inside is lost when the container stops. Persist data with volumes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker volume create pgdata
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -v pgdata:/var/lib/postgresql/data postgres:16
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For local development, bind mounts map a host directory into the container:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -v &lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;pwd&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;:/app -w /app node:20 npm test
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="networking"&gt;Networking
&lt;/h2&gt;&lt;p&gt;Containers on the same Docker network can reach each other by name. Docker Compose creates a default network automatically; named networks can be created explicitly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker network create backend
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run --network backend --name db postgres:16
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run --network backend myapp &lt;span style="color:#75715e"&gt;# can reach &amp;#39;db&amp;#39; by hostname&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="podman"&gt;Podman
&lt;/h2&gt;&lt;p&gt;Podman is a drop-in Docker replacement that runs without a daemon and without root. The CLI is intentionally compatible:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;alias docker&lt;span style="color:#f92672"&gt;=&lt;/span&gt;podman &lt;span style="color:#75715e"&gt;# usually just works&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Rootless containers mean a compromised container process cannot escalate to host root. Daemonless means no long-running background service with broad system access. On RHEL and Fedora, Podman is the default. For CI environments and security-conscious setups it is the better choice.&lt;/p&gt;
&lt;p&gt;Podman also supports &lt;strong&gt;pods&lt;/strong&gt; — groups of containers sharing a network namespace, mirroring the Kubernetes pod model. Useful for local development that needs to mirror how things will run in the cluster.&lt;/p&gt;
&lt;h2 id="buildah"&gt;Buildah
&lt;/h2&gt;&lt;p&gt;Buildah builds OCI images without a Docker daemon. It can build from a Dockerfile or construct images programmatically using shell commands — useful in CI pipelines where running a privileged Docker daemon is undesirable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;buildah bud -t myapp:1.0 . &lt;span style="color:#75715e"&gt;# build from Dockerfile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;buildah push myapp:1.0 registry/myapp:1.0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Buildah and Podman share the same underlying storage, so images built with Buildah are immediately available to Podman.&lt;/p&gt;
&lt;h2 id="docker-compose"&gt;Docker Compose
&lt;/h2&gt;&lt;p&gt;Compose manages multi-container applications defined in &lt;code&gt;compose.yml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;app&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;build&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;8080:8080&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;DATABASE_URL&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;postgres://app:secret@db/appdb&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;depends_on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;db&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;db&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;postgres:16&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;pgdata:/var/lib/postgresql/data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;POSTGRES_PASSWORD&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;secret&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;pgdata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker compose up -d &lt;span style="color:#75715e"&gt;# start in background&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker compose logs -f &lt;span style="color:#75715e"&gt;# stream logs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker compose down &lt;span style="color:#75715e"&gt;# stop and remove containers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Compose is useful for local development environments. It is a shame it exists as a separate abstraction — it taught people to think in multi-container terms without teaching them Kubernetes, and then left them with a gap to cross when they needed to go to production. That said, it is practical for what it does and is not going away.&lt;/p&gt;
&lt;p&gt;For production orchestration, see &lt;a class="link" href="../../kubernetes/kubernetes/" &gt;Kubernetes&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="skopeo"&gt;Skopeo
&lt;/h2&gt;&lt;p&gt;Skopeo works with OCI images directly — copy, inspect, and convert — without pulling them to local storage. Useful in pipelines and for auditing registries:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Inspect an image without pulling it&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;skopeo inspect docker://registry.example.com/myapp:1.0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Copy between registries without touching local disk&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;skopeo copy docker://source-registry/myapp:1.0 docker://dest-registry/myapp:1.0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Copy to a local OCI layout&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;skopeo copy docker://myapp:1.0 oci:myapp-local:1.0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;skopeo inspect&lt;/code&gt; is particularly useful for checking image metadata, digest, and labels in CI before deciding whether to promote an image.&lt;/p&gt;
&lt;h2 id="oras"&gt;ORAS
&lt;/h2&gt;&lt;p&gt;ORAS (OCI Registry As Storage) pushes and pulls arbitrary artifacts to OCI registries — not just container images. Helm charts, SBOMs, attestations, Terraform modules, binary releases — anything can be stored in a registry that speaks OCI distribution spec:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Push a file as an OCI artifact&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;oras push registry.example.com/myapp-sbom:1.0 sbom.json:application/spdx+json
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Pull it back&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;oras pull registry.example.com/myapp-sbom:1.0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This matters because it means a single registry can become the distribution mechanism for the entire software supply chain — image, SBOM, signature, attestation — all with the same access controls and audit trail.&lt;/p&gt;
&lt;h2 id="useful-practices"&gt;Useful practices
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Use specific image tags (&lt;code&gt;postgres:16.2&lt;/code&gt;, not &lt;code&gt;postgres:latest&lt;/code&gt;) — &lt;code&gt;latest&lt;/code&gt; changes under you&lt;/li&gt;
&lt;li&gt;Reference images by digest in production (&lt;code&gt;myapp@sha256:abc123&lt;/code&gt;) — tags are mutable, digests are not&lt;/li&gt;
&lt;li&gt;Run as a non-root user: &lt;code&gt;USER appuser&lt;/code&gt; in the Dockerfile&lt;/li&gt;
&lt;li&gt;Add a &lt;code&gt;.dockerignore&lt;/code&gt; to exclude &lt;code&gt;.git&lt;/code&gt;, &lt;code&gt;node_modules&lt;/code&gt;, build artefacts from the build context&lt;/li&gt;
&lt;li&gt;Keep images small — large images are slow to push, pull, and scan&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="resources"&gt;Resources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://opencontainers.org/" target="_blank" rel="noopener"
 &gt;OCI specifications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.docker.com/" target="_blank" rel="noopener"
 &gt;Docker documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://podman.io/docs" target="_blank" rel="noopener"
 &gt;Podman documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://buildah.io/" target="_blank" rel="noopener"
 &gt;Buildah documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/containers/skopeo" target="_blank" rel="noopener"
 &gt;Skopeo GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://oras.land/docs/" target="_blank" rel="noopener"
 &gt;ORAS documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>K9s &amp; Lens</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/k9s/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/k9s/</guid><description>&lt;p&gt;You run everything with &lt;code&gt;kubectl&lt;/code&gt;. Get pods, describe, logs, exec, delete, apply — fifty times a day across five namespaces. It works, but every command is a context switch: type, wait, read, type again. &lt;code&gt;-n namespace&lt;/code&gt; on every single invocation.&lt;/p&gt;
&lt;p&gt;So you use K9s. A terminal UI that shows your entire cluster in one view. Switch namespaces and clusters in a keystroke, tail logs in real time, exec into a pod without constructing the command — everything you reach for in &lt;code&gt;kubectl&lt;/code&gt;, but without the friction.&lt;/p&gt;
&lt;h2 id="k9s"&gt;K9s
&lt;/h2&gt;&lt;p&gt;K9s is a TUI (terminal UI) for Kubernetes. It stays in your terminal, updates live, and is keyboard-driven throughout.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;brew install derailed/k9s/k9s
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;k9s &lt;span style="color:#75715e"&gt;# connect to current context&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;k9s --context prod &lt;span style="color:#75715e"&gt;# specific context&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;k9s -n monitoring &lt;span style="color:#75715e"&gt;# start in a specific namespace&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="navigation"&gt;Navigation
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Key&lt;/th&gt;
 &lt;th&gt;Action&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;:pod&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Jump to pods view&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;:deploy&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Deployments&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;:svc&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Services&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;:ns&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Switch namespace&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Filter/search&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;l&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Logs&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;e&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Edit resource YAML&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;d&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Describe&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;s&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Shell into pod&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;ctrl-d&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Delete&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;?&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Help / full keybinding list&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Most resource types are reachable by typing &lt;code&gt;:&lt;/code&gt; followed by the resource name — &lt;code&gt;:configmap&lt;/code&gt;, &lt;code&gt;:secret&lt;/code&gt;, &lt;code&gt;:ingress&lt;/code&gt;, &lt;code&gt;:pvc&lt;/code&gt;, and so on.&lt;/p&gt;
&lt;h3 id="why-tui-over-gui"&gt;Why TUI over GUI
&lt;/h3&gt;&lt;p&gt;K9s lives in the terminal alongside your other tools. No window switching, works over SSH, starts instantly, and the keyboard-driven workflow is faster once it is in muscle memory. For day-to-day cluster work it is the right default.&lt;/p&gt;
&lt;h2 id="lens"&gt;Lens
&lt;/h2&gt;&lt;p&gt;Lens is a desktop GUI for Kubernetes — a full IDE-style interface with a visual cluster overview, resource browsing, metrics charts, log streaming, and terminal access built in.&lt;/p&gt;
&lt;p&gt;It is the better choice when you need to onboard someone who is not yet comfortable with the terminal, or when you want a visual overview to share with a non-technical stakeholder. For engineers doing operational work all day, K9s is faster.&lt;/p&gt;
&lt;p&gt;Worth noting: Lens has moved toward a commercial model (Lens Desktop Pro). &lt;strong&gt;&lt;a class="link" href="https://github.com/MuhammedKalkan/OpenLens" target="_blank" rel="noopener"
 &gt;OpenLens&lt;/a&gt;&lt;/strong&gt; is the open-source build of the same codebase, without the account requirement.&lt;/p&gt;
&lt;h2 id="kubectx--kubens"&gt;kubectx / kubens
&lt;/h2&gt;&lt;p&gt;If K9s is more than you need and you just want to stop typing &lt;code&gt;--context&lt;/code&gt; and &lt;code&gt;-n&lt;/code&gt; on every command, &lt;code&gt;kubectx&lt;/code&gt; and &lt;code&gt;kubens&lt;/code&gt; solve exactly that:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectx &lt;span style="color:#75715e"&gt;# list contexts&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectx prod &lt;span style="color:#75715e"&gt;# switch to prod context&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectx - &lt;span style="color:#75715e"&gt;# switch back to previous context&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubens &lt;span style="color:#75715e"&gt;# list namespaces&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubens monitoring &lt;span style="color:#75715e"&gt;# switch default namespace&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;No TUI, no GUI — just fast context and namespace switching that persists for the rest of your terminal session. Install alongside K9s; they complement each other.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;brew install kubectx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="resources"&gt;Resources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://k9scli.io/" target="_blank" rel="noopener"
 &gt;K9s documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/derailed/k9s" target="_blank" rel="noopener"
 &gt;K9s GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://k8slens.dev/" target="_blank" rel="noopener"
 &gt;Lens&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/MuhammedKalkan/OpenLens" target="_blank" rel="noopener"
 &gt;OpenLens&lt;/a&gt; — open-source Lens build&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/ahmetb/kubectx" target="_blank" rel="noopener"
 &gt;kubectx/kubens&lt;/a&gt; — fast context and namespace switching&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Kubernetes</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/</guid><description>&lt;p&gt;Kubernetes (K8s) is the de facto standard for container orchestration and the second largest open source project after the Linux kernel. It has well and truly reached the plateau of productivity — the ecosystem is mature and it genuinely delivers.&lt;/p&gt;
&lt;p&gt;That said, the honest take: &lt;strong&gt;K8s is ridiculously hard to deploy and manage&lt;/strong&gt; (day 2 operations especially). Docker Swarm is equally ridiculously easy to get started with. For raw scale, Mesos/DC/OS wins — clusters of 80k+ nodes have been documented in the wild, versus K8s master&amp;rsquo;s practical ceiling of around 5k nodes.&lt;/p&gt;
&lt;p&gt;So the real question is whether the ecosystem justifies the complexity for your situation. For most teams doing cloud-native work, it does.&lt;/p&gt;
&lt;h2 id="core-concepts"&gt;Core concepts
&lt;/h2&gt;&lt;p&gt;The main building blocks:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pods&lt;/strong&gt; — smallest deployable unit, wrapping one or more containers that share network and storage.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Pods" class="gallery-image" data-flex-basis="300px" data-flex-grow="125" height="640" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/pods.png" width="800"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Deployments&lt;/strong&gt; — declare desired state; K8s handles rolling updates and self-healing.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Deployments" class="gallery-image" data-flex-basis="300px" data-flex-grow="125" height="640" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/deployments.png" width="800"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Secrets&lt;/strong&gt; — store sensitive data (passwords, tokens, keys) separately from application config.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Secrets" class="gallery-image" data-flex-basis="300px" data-flex-grow="125" height="640" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/secrets.png" width="800"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DaemonSets&lt;/strong&gt; — run a pod on every node. Typical use: log collectors, monitoring agents.&lt;/p&gt;
&lt;p&gt;&lt;img alt="DaemonSets" class="gallery-image" data-flex-basis="300px" data-flex-grow="125" height="640" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/daemonsets.png" width="800"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ReplicaSets&lt;/strong&gt; — ensure N copies of a pod are running at any given time.&lt;/p&gt;
&lt;p&gt;&lt;img alt="ReplicaSets" class="gallery-image" data-flex-basis="300px" data-flex-grow="125" height="640" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/replicasets.png" width="800"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ingress&lt;/strong&gt; — HTTP/S routing rules at layer 7. Your load balancer config, declarative.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ingress" class="gallery-image" data-flex-basis="300px" data-flex-grow="125" height="1920" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/ingress.png" srcset="https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/ingress_hu_b0bb8a58e86a1fe2.png 800w, https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/ingress_hu_34af71a3280d9a15.png 1600w, https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/ingress.png 2400w" width="2400"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CronJobs&lt;/strong&gt; — scheduled jobs, K8s-native.&lt;/p&gt;
&lt;p&gt;&lt;img alt="CronJobs" class="gallery-image" data-flex-basis="300px" data-flex-grow="125" height="640" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/cronjobs.png" width="800"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Custom Resource Definitions (CRDs)&lt;/strong&gt; — extend the K8s API with your own resource types. The foundation of most K8s operators.&lt;/p&gt;
&lt;p&gt;&lt;img alt="CRDs" class="gallery-image" data-flex-basis="300px" data-flex-grow="125" height="640" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/crd.png" width="800"&gt;&lt;/p&gt;
&lt;h2 id="architecture"&gt;Architecture
&lt;/h2&gt;&lt;p&gt;How the pieces fit together internally:&lt;/p&gt;
&lt;p&gt;&lt;img alt="K8s internal architecture" class="gallery-image" data-flex-basis="300px" data-flex-grow="125" height="1280" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/inner.png" srcset="https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/inner_hu_ca8f0543462c084c.png 800w, https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/inner.png 1600w" width="1600"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="K8s component overview" class="gallery-image" data-flex-basis="300px" data-flex-grow="125" height="640" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/zoo.png" width="800"&gt;&lt;/p&gt;
&lt;h2 id="containers-vs-virtual-machines"&gt;Containers vs virtual machines
&lt;/h2&gt;&lt;p&gt;Not an either/or — they solve different problems and are frequently combined.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Separate: containers alongside VMs" class="gallery-image" data-flex-basis="300px" data-flex-grow="125" height="1280" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/seperate.png" srcset="https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/seperate_hu_557182bcbb415ab4.png 800w, https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/seperate.png 1600w" width="1600"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Combined: containers running on top of VMs" class="gallery-image" data-flex-basis="300px" data-flex-grow="125" height="1280" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/combined.png" srcset="https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/combined_hu_9f383172dd98181c.png 800w, https://backend-engineering-strategy-tools.github.io/site/public-notes/kubernetes/kubernetes/combined.png 1600w" width="1600"&gt;&lt;/p&gt;
&lt;h2 id="local-clusters-for-development"&gt;Local clusters for development
&lt;/h2&gt;&lt;p&gt;When you need K8s without a full cluster:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Tool&lt;/th&gt;
 &lt;th&gt;Best for&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;a class="link" href="https://microk8s.io/" target="_blank" rel="noopener"
 &gt;MicroK8s&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;Ubuntu, snap-based, batteries included&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;a class="link" href="https://minikube.sigs.k8s.io/" target="_blank" rel="noopener"
 &gt;Minikube&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;The classic, broad driver support&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;a class="link" href="https://kind.sigs.k8s.io/" target="_blank" rel="noopener"
 &gt;Kind&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;K8s in Docker, great for CI pipelines&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;a class="link" href="https://k3d.io/" target="_blank" rel="noopener"
 &gt;K3D&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;K3s in Docker, fast startup&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;a class="link" href="https://k3s.io/" target="_blank" rel="noopener"
 &gt;K3S&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;Lightweight K8s, edge and IoT use cases&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="resources"&gt;Resources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/" target="_blank" rel="noopener"
 &gt;kubernetes.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://landscape.cncf.io/" target="_blank" rel="noopener"
 &gt;CNCF Landscape&lt;/a&gt; — map of the cloud-native ecosystem&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.youtube.com/watch?v=PH-2FfFD2PU" target="_blank" rel="noopener"
 &gt;TGI Kubernetes intro (YouTube)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://igy.cx/posts/setup-microk8s-rbac-storage/" target="_blank" rel="noopener"
 &gt;Setting up MicroK8s with RBAC and Storage&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>