<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Frameworks &amp; Tools on Backend Engineering Strategy Tools</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/</link><description>Recent content in Frameworks &amp; Tools on Backend Engineering Strategy Tools</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Mon, 08 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/index.xml" rel="self" type="application/rss+xml"/><item><title>Backstage</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/backstage/</link><pubDate>Mon, 08 Jun 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/backstage/</guid><description>&lt;p&gt;Backstage is an open-source framework for building &lt;strong&gt;Internal Developer Portals (IDPs)&lt;/strong&gt;. Created by Spotify, donated to the CNCF in 2022. The core idea: instead of every team maintaining their own scattered documentation, dashboards, and service scaffolding, you consolidate it into one place that the whole organisation uses.&lt;/p&gt;
&lt;p&gt;You deploy your own instance. Backstage is not a SaaS — it is a framework you run and extend. The investment is real, but so is the payoff once adoption reaches critical mass.&lt;/p&gt;
&lt;h2 id="core-plugins"&gt;Core plugins
&lt;/h2&gt;&lt;p&gt;Backstage ships with a small core and a large plugin ecosystem. The four foundational pieces:&lt;/p&gt;
&lt;h3 id="software-catalog"&gt;Software Catalog
&lt;/h3&gt;&lt;p&gt;The catalog is a registry of everything your organisation owns: services, libraries, websites, pipelines, ML models, databases. Each entity is described by a &lt;code&gt;catalog-info.yaml&lt;/code&gt; file committed alongside the code it describes.&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:#75715e"&gt;# catalog-info.yaml — lives in the repo root&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;backstage.io/v1alpha1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Component&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;payments-service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;description&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Handles payment processing&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;tags&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;java&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;payments&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;annotations&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;github.com/project-slug&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;myorg/payments-service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;backstage.io/techdocs-ref&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;dir:.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;lifecycle&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;production&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;owner&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;payments-team&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;system&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;commerce&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Backstage imports entities from &lt;strong&gt;Locations&lt;/strong&gt; — typically Git repositories. The catalog refreshes on a schedule and stays in sync with the &lt;code&gt;catalog-info.yaml&lt;/code&gt; files across your repos.&lt;/p&gt;
&lt;h3 id="scaffolder-software-templates"&gt;Scaffolder (Software Templates)
&lt;/h3&gt;&lt;p&gt;The Scaffolder lets platform teams publish templates for creating new projects. Developers pick a template, fill in a form, and Backstage creates the repo, applies the template, opens a PR — whatever the template defines.&lt;/p&gt;
&lt;p&gt;Templates are defined in YAML and use &lt;code&gt;${{ parameters.name }}&lt;/code&gt; substitution. Actions are either built-in (&lt;code&gt;publish:github&lt;/code&gt;, &lt;code&gt;fetch:template&lt;/code&gt;, &lt;code&gt;catalog:register&lt;/code&gt;) or custom.&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;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;scaffolder.backstage.io/v1beta3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Template&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;go-service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;title&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Go Microservice&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;owner&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;platform-team&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;parameters&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;title&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Service details&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;properties&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;title&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Service name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;steps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;id&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;fetch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Fetch template&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;action&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;fetch:template&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;input&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;url&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;./skeleton&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;values&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;${{ parameters.name }}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;id&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;publish&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Create repo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;action&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;publish:github&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;input&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;repoUrl&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;github.com?repo=${{ parameters.name }}&amp;amp;owner=myorg&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="techdocs"&gt;TechDocs
&lt;/h3&gt;&lt;p&gt;TechDocs renders Markdown documentation from repos as browsable pages inside Backstage. It uses &lt;strong&gt;MkDocs&lt;/strong&gt; under the hood. Annotate the entity with &lt;code&gt;backstage.io/techdocs-ref: dir:.&lt;/code&gt; and place a &lt;code&gt;mkdocs.yml&lt;/code&gt; in the repo root.&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:#75715e"&gt;# mkdocs.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;site_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Payments Service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;nav&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;Home&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;index.md&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;Architecture&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;architecture.md&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;docs_dir&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;docs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;plugins&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;techdocs-core&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Docs are either built at read-time (dev mode) or pre-built and stored in object storage (S3/GCS) for production. Pre-built is the right default for production — it separates the build from the render and avoids build overhead at page load.&lt;/p&gt;
&lt;h3 id="search"&gt;Search
&lt;/h3&gt;&lt;p&gt;Full-text search across the catalog, TechDocs, and any plugin that contributes a search collator. Powered by Lunr (local) or Elasticsearch for production deployments.&lt;/p&gt;
&lt;h2 id="entity-model"&gt;Entity model
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Kind&lt;/th&gt;
 &lt;th&gt;Represents&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;Component&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;A deployable unit: service, website, library&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;API&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;An interface exposed by a Component&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;Resource&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Infrastructure a Component depends on (DB, queue)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;System&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;A collection of related Components and Resources&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;Domain&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;A business domain grouping Systems&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;Group&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;A team or organisational unit&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;User&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;An individual&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;Template&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;A Scaffolder template&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;Location&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;A pointer to where entities are defined&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The relationships (&lt;code&gt;providesApis&lt;/code&gt;, &lt;code&gt;consumesApis&lt;/code&gt;, &lt;code&gt;dependsOn&lt;/code&gt;, &lt;code&gt;partOf&lt;/code&gt;) are what make the catalog navigable — you can trace dependencies across services and see who owns what.&lt;/p&gt;
&lt;h2 id="architecture"&gt;Architecture
&lt;/h2&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;Browser
 └─ Backstage frontend (React, @backstage/core-*)
 └─ Backstage backend (Node.js, Express)
 ├─ Catalog backend — entity processing pipeline
 ├─ Scaffolder backend — template execution
 ├─ TechDocs backend — doc building/serving
 ├─ Auth backend — OAuth providers
 └─ PostgreSQL — persistent state
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The backend is a Node.js monolith you extend with plugins. Each plugin typically registers API routes and a database schema. The frontend is a React SPA — plugins register pages, sidebar entries, and entity tab content.&lt;/p&gt;
&lt;h2 id="getting-started"&gt;Getting started
&lt;/h2&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;npx @backstage/create-app@latest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd my-backstage-app
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;yarn dev &lt;span style="color:#75715e"&gt;# starts both frontend (:3000) and backend (:7007)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This scaffolds a working app with the catalog, scaffolder, and TechDocs already wired up. SQLite is the default database for local dev; switch to PostgreSQL before running anything that matters.&lt;/p&gt;
&lt;h3 id="helm-chart-production"&gt;Helm chart (production)
&lt;/h3&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;helm repo add backstage https://backstage.github.io/charts
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm install backstage backstage/backstage -f values.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The chart expects an &lt;code&gt;app-config.yaml&lt;/code&gt; mounted as a ConfigMap and PostgreSQL credentials in a Secret.&lt;/p&gt;
&lt;h2 id="app-configyaml"&gt;app-config.yaml
&lt;/h2&gt;&lt;p&gt;The main config file. Controls database, auth providers, integrations, and plugin configuration. Environment-specific overrides go in &lt;code&gt;app-config.production.yaml&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;backend&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;baseUrl&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;https://backstage.example.com&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&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;client&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;pg&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;connection&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;host&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;${POSTGRES_HOST}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;user&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;${POSTGRES_USER}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;password&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;${POSTGRES_PASSWORD}&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;integrations&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;github&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;host&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;github.com&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;token&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;${GITHUB_TOKEN}&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;catalog&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;locations&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;url&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;target&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;https://github.com/myorg/catalog/blob/main/all.yaml&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;auth&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;providers&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;github&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;development&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;clientId&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;${AUTH_GITHUB_CLIENT_ID}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;clientSecret&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;${AUTH_GITHUB_CLIENT_SECRET}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="plugin-ecosystem"&gt;Plugin ecosystem
&lt;/h2&gt;&lt;p&gt;Backstage has 200+ community plugins. Common ones:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Plugin&lt;/th&gt;
 &lt;th&gt;What it adds&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;kubernetes&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Live cluster view scoped to an entity&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;github-actions&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;CI/CD run status on entity pages&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;pagerduty&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;On-call and incident status per service&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;sonarqube&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Code quality metrics per component&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;cost-insights&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Cloud cost breakdown by team&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;lighthouse&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Web performance audits&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;argo-cd&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;ArgoCD sync status on entity pages&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Plugins are npm packages. Install, register in &lt;code&gt;packages/app/src/App.tsx&lt;/code&gt; (frontend) and &lt;code&gt;packages/backend/src/index.ts&lt;/code&gt; (backend).&lt;/p&gt;
&lt;h2 id="adoption-pattern"&gt;Adoption pattern
&lt;/h2&gt;&lt;p&gt;The catalog only becomes useful when it is comprehensive. The typical path:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Import existing services by pointing the catalog at repos that have &lt;code&gt;catalog-info.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add a bulk-importer location (a &lt;code&gt;catalog-info.yaml&lt;/code&gt; that lists other locations) to bootstrap coverage&lt;/li&gt;
&lt;li&gt;Enforce &lt;code&gt;catalog-info.yaml&lt;/code&gt; in new repos via the Scaffolder — templates create it automatically&lt;/li&gt;
&lt;li&gt;Build TechDocs incrementally — services that already have &lt;code&gt;docs/&lt;/code&gt; directories are easy wins&lt;/li&gt;
&lt;li&gt;Add the Kubernetes plugin once catalog ownership is clean — it ties live cluster state to catalog entities&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="resources"&gt;Resources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://backstage.io/" target="_blank" rel="noopener"
 &gt;Backstage.io&lt;/a&gt; — official docs&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/backstage/backstage" target="_blank" rel="noopener"
 &gt;Backstage GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/backstage/charts" target="_blank" rel="noopener"
 &gt;Backstage Helm chart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.cncf.io/projects/backstage/" target="_blank" rel="noopener"
 &gt;CNCF project page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://backstage.io/plugins" target="_blank" rel="noopener"
 &gt;Plugin marketplace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/backstage/backstage/blob/master/docs/architecture-decisions/adr002-default-catalog-file-format.md" target="_blank" rel="noopener"
 &gt;ADR-002: Backstage Software Catalog&lt;/a&gt; — explains the entity model design&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Blender Python — Procedural Mesh for 3D Printing</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/blender-python/</link><pubDate>Tue, 02 Jun 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/blender-python/</guid><description>&lt;p&gt;Write a Python script that builds geometry programmatically using Blender&amp;rsquo;s &lt;code&gt;bpy&lt;/code&gt; API, then export as STL. No manual modelling — the script is the source of truth, re-running it regenerates everything.&lt;/p&gt;
&lt;p&gt;This pattern is useful when you have a family of similar parts (different sizes, repeated structures, parametric variations) or when the geometry is defined by rules rather than artistic decisions.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="when-to-use-this-vs-alternatives"&gt;When to use this vs. alternatives
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;Blender Python&lt;/th&gt;
 &lt;th&gt;OpenSCAD&lt;/th&gt;
 &lt;th&gt;CadQuery&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Booleans, extrude, modifiers&lt;/td&gt;
 &lt;td&gt;✓ built-in&lt;/td&gt;
 &lt;td&gt;✓ CSG only&lt;/td&gt;
 &lt;td&gt;limited&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Sculpt / organic shapes&lt;/td&gt;
 &lt;td&gt;✓&lt;/td&gt;
 &lt;td&gt;✗&lt;/td&gt;
 &lt;td&gt;✗&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Parametric constraints&lt;/td&gt;
 &lt;td&gt;manual&lt;/td&gt;
 &lt;td&gt;manual&lt;/td&gt;
 &lt;td&gt;✓ strong&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Python ecosystem&lt;/td&gt;
 &lt;td&gt;✓ full stdlib&lt;/td&gt;
 &lt;td&gt;✗ own language&lt;/td&gt;
 &lt;td&gt;✓&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Interactive viewport preview&lt;/td&gt;
 &lt;td&gt;✓&lt;/td&gt;
 &lt;td&gt;✗&lt;/td&gt;
 &lt;td&gt;✗&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Export to STL&lt;/td&gt;
 &lt;td&gt;✓ one call&lt;/td&gt;
 &lt;td&gt;✓&lt;/td&gt;
 &lt;td&gt;✓&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For repetitive mechanical geometry with booleans (holes, sockets, cutouts), Blender Python is the fastest path if you already know Python. The interactive viewport lets you catch geometry problems before exporting.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="core-pattern"&gt;Core pattern
&lt;/h2&gt;&lt;p&gt;Every script follows the same structure:&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-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; bpy&lt;span style="color:#f92672"&gt;,&lt;/span&gt; bmesh&lt;span style="color:#f92672"&gt;,&lt;/span&gt; math&lt;span style="color:#f92672"&gt;,&lt;/span&gt; os
&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;# 1. Create a primitive — it becomes bpy.context.object&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ops&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mesh&lt;span style="color:#f92672"&gt;.&lt;/span&gt;primitive_cylinder_add(vertices&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;, radius&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;, depth&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;obj &lt;span style="color:#f92672"&gt;=&lt;/span&gt; bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;context&lt;span style="color:#f92672"&gt;.&lt;/span&gt;object
&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;# 2. Edit vertices directly via bmesh&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ops&lt;span style="color:#f92672"&gt;.&lt;/span&gt;object&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mode_set(mode&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;EDIT&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bm &lt;span style="color:#f92672"&gt;=&lt;/span&gt; bmesh&lt;span style="color:#f92672"&gt;.&lt;/span&gt;from_edit_mesh(obj&lt;span style="color:#f92672"&gt;.&lt;/span&gt;data)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; v &lt;span style="color:#f92672"&gt;in&lt;/span&gt; bm&lt;span style="color:#f92672"&gt;.&lt;/span&gt;verts:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; v&lt;span style="color:#f92672"&gt;.&lt;/span&gt;co&lt;span style="color:#f92672"&gt;.&lt;/span&gt;z &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; v&lt;span style="color:#f92672"&gt;.&lt;/span&gt;co&lt;span style="color:#f92672"&gt;.&lt;/span&gt;x &lt;span style="color:#f92672"&gt;*=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0.9&lt;/span&gt; &lt;span style="color:#75715e"&gt;# taper the top&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bmesh&lt;span style="color:#f92672"&gt;.&lt;/span&gt;update_edit_mesh(obj&lt;span style="color:#f92672"&gt;.&lt;/span&gt;data)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ops&lt;span style="color:#f92672"&gt;.&lt;/span&gt;object&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mode_set(mode&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;OBJECT&amp;#39;&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:#75715e"&gt;# 3. Boolean modifier to cut a hole&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ops&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mesh&lt;span style="color:#f92672"&gt;.&lt;/span&gt;primitive_cylinder_add(radius&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;, depth&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;3.2&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cutter &lt;span style="color:#f92672"&gt;=&lt;/span&gt; bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;context&lt;span style="color:#f92672"&gt;.&lt;/span&gt;object
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mod &lt;span style="color:#f92672"&gt;=&lt;/span&gt; obj&lt;span style="color:#f92672"&gt;.&lt;/span&gt;modifiers&lt;span style="color:#f92672"&gt;.&lt;/span&gt;new(&lt;span style="color:#e6db74"&gt;&amp;#34;Hole&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;BOOLEAN&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mod&lt;span style="color:#f92672"&gt;.&lt;/span&gt;object &lt;span style="color:#f92672"&gt;=&lt;/span&gt; cutter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mod&lt;span style="color:#f92672"&gt;.&lt;/span&gt;operation &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;DIFFERENCE&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;context&lt;span style="color:#f92672"&gt;.&lt;/span&gt;view_layer&lt;span style="color:#f92672"&gt;.&lt;/span&gt;objects&lt;span style="color:#f92672"&gt;.&lt;/span&gt;active &lt;span style="color:#f92672"&gt;=&lt;/span&gt; obj
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ops&lt;span style="color:#f92672"&gt;.&lt;/span&gt;object&lt;span style="color:#f92672"&gt;.&lt;/span&gt;modifier_apply(modifier&lt;span style="color:#f92672"&gt;=&lt;/span&gt;mod&lt;span style="color:#f92672"&gt;.&lt;/span&gt;name)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;data&lt;span style="color:#f92672"&gt;.&lt;/span&gt;objects&lt;span style="color:#f92672"&gt;.&lt;/span&gt;remove(cutter, do_unlink&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&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:#75715e"&gt;# 4. Export&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ops&lt;span style="color:#f92672"&gt;.&lt;/span&gt;object&lt;span style="color:#f92672"&gt;.&lt;/span&gt;select_all(action&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;DESELECT&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;obj&lt;span style="color:#f92672"&gt;.&lt;/span&gt;select_set(&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ops&lt;span style="color:#f92672"&gt;.&lt;/span&gt;wm&lt;span style="color:#f92672"&gt;.&lt;/span&gt;stl_export(filepath&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/tmp/part.stl&amp;#34;&lt;/span&gt;, export_selected_objects&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Build complexity by wrapping each operation in a function, then calling it in a loop over a size or parameter list.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="running-the-script"&gt;Running the script
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;Open Blender → switch to the &lt;strong&gt;Scripting&lt;/strong&gt; workspace&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;New&lt;/strong&gt; or &lt;strong&gt;Open&lt;/strong&gt; to load your &lt;code&gt;.py&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Run Script&lt;/strong&gt; (▶) or press &lt;code&gt;Alt + P&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Output and errors appear in the system console (&lt;code&gt;Window → Toggle System Console&lt;/code&gt; on Windows, or launch Blender from a terminal on macOS/Linux).&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="key-api-surface"&gt;Key API surface
&lt;/h2&gt;&lt;h3 id="primitives"&gt;Primitives
&lt;/h3&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-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ops&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mesh&lt;span style="color:#f92672"&gt;.&lt;/span&gt;primitive_cylinder_add(vertices&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;32&lt;/span&gt;, radius&lt;span style="color:#f92672"&gt;=&lt;/span&gt;r, depth&lt;span style="color:#f92672"&gt;=&lt;/span&gt;h)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ops&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mesh&lt;span style="color:#f92672"&gt;.&lt;/span&gt;primitive_cube_add(size&lt;span style="color:#f92672"&gt;=&lt;/span&gt;s)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ops&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mesh&lt;span style="color:#f92672"&gt;.&lt;/span&gt;primitive_plane_add(size&lt;span style="color:#f92672"&gt;=&lt;/span&gt;s)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;All primitives land at the world origin and become &lt;code&gt;bpy.context.object&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="transforms"&gt;Transforms
&lt;/h3&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-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;obj&lt;span style="color:#f92672"&gt;.&lt;/span&gt;scale &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (sx, sy, sz)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;obj&lt;span style="color:#f92672"&gt;.&lt;/span&gt;location &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (x, y, z)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;obj&lt;span style="color:#f92672"&gt;.&lt;/span&gt;rotation_euler &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (rx, ry, rz) &lt;span style="color:#75715e"&gt;# radians&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ops&lt;span style="color:#f92672"&gt;.&lt;/span&gt;object&lt;span style="color:#f92672"&gt;.&lt;/span&gt;transform_apply(scale&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;, location&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;, rotation&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Apply transforms before any bmesh edits — otherwise vertex coordinates are in local (pre-scale) space, and your edit positions won&amp;rsquo;t match world coordinates.&lt;/p&gt;
&lt;h3 id="bmesh-direct-vertex--edge--face-editing"&gt;bmesh (direct vertex / edge / face editing)
&lt;/h3&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-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ops&lt;span style="color:#f92672"&gt;.&lt;/span&gt;object&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mode_set(mode&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;EDIT&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bm &lt;span style="color:#f92672"&gt;=&lt;/span&gt; bmesh&lt;span style="color:#f92672"&gt;.&lt;/span&gt;from_edit_mesh(obj&lt;span style="color:#f92672"&gt;.&lt;/span&gt;data)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; v &lt;span style="color:#f92672"&gt;in&lt;/span&gt; bm&lt;span style="color:#f92672"&gt;.&lt;/span&gt;verts:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; v&lt;span style="color:#f92672"&gt;.&lt;/span&gt;co&lt;span style="color:#f92672"&gt;.&lt;/span&gt;z &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; v&lt;span style="color:#f92672"&gt;.&lt;/span&gt;co&lt;span style="color:#f92672"&gt;.&lt;/span&gt;x &lt;span style="color:#f92672"&gt;*=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0.9&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bmesh&lt;span style="color:#f92672"&gt;.&lt;/span&gt;update_edit_mesh(obj&lt;span style="color:#f92672"&gt;.&lt;/span&gt;data)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ops&lt;span style="color:#f92672"&gt;.&lt;/span&gt;object&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mode_set(mode&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;OBJECT&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="boolean-modifiers"&gt;Boolean modifiers
&lt;/h3&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-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mod &lt;span style="color:#f92672"&gt;=&lt;/span&gt; target&lt;span style="color:#f92672"&gt;.&lt;/span&gt;modifiers&lt;span style="color:#f92672"&gt;.&lt;/span&gt;new(&lt;span style="color:#e6db74"&gt;&amp;#34;Name&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;BOOLEAN&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mod&lt;span style="color:#f92672"&gt;.&lt;/span&gt;object &lt;span style="color:#f92672"&gt;=&lt;/span&gt; cutter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mod&lt;span style="color:#f92672"&gt;.&lt;/span&gt;operation &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;DIFFERENCE&amp;#39;&lt;/span&gt; &lt;span style="color:#75715e"&gt;# or UNION or INTERSECT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mod&lt;span style="color:#f92672"&gt;.&lt;/span&gt;solver &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;EXACT&amp;#39;&lt;/span&gt; &lt;span style="color:#75715e"&gt;# more reliable than FAST for tight geometry&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;context&lt;span style="color:#f92672"&gt;.&lt;/span&gt;view_layer&lt;span style="color:#f92672"&gt;.&lt;/span&gt;objects&lt;span style="color:#f92672"&gt;.&lt;/span&gt;active &lt;span style="color:#f92672"&gt;=&lt;/span&gt; target
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ops&lt;span style="color:#f92672"&gt;.&lt;/span&gt;object&lt;span style="color:#f92672"&gt;.&lt;/span&gt;modifier_apply(modifier&lt;span style="color:#f92672"&gt;=&lt;/span&gt;mod&lt;span style="color:#f92672"&gt;.&lt;/span&gt;name)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;data&lt;span style="color:#f92672"&gt;.&lt;/span&gt;objects&lt;span style="color:#f92672"&gt;.&lt;/span&gt;remove(cutter, do_unlink&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Apply and remove the cutter immediately — leaving stale cutter objects causes confusion on subsequent runs.&lt;/p&gt;
&lt;h3 id="joining-objects-single-boolean-cut-for-a-grid-of-holes"&gt;Joining objects (single boolean cut for a grid of holes)
&lt;/h3&gt;&lt;p&gt;Rather than applying one boolean per hole, join all cutters first:&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-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; obj &lt;span style="color:#f92672"&gt;in&lt;/span&gt; cyl_objects:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; obj&lt;span style="color:#f92672"&gt;.&lt;/span&gt;select_set(&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;context&lt;span style="color:#f92672"&gt;.&lt;/span&gt;view_layer&lt;span style="color:#f92672"&gt;.&lt;/span&gt;objects&lt;span style="color:#f92672"&gt;.&lt;/span&gt;active &lt;span style="color:#f92672"&gt;=&lt;/span&gt; cyl_objects[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ops&lt;span style="color:#f92672"&gt;.&lt;/span&gt;object&lt;span style="color:#f92672"&gt;.&lt;/span&gt;join()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cutter &lt;span style="color:#f92672"&gt;=&lt;/span&gt; bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;context&lt;span style="color:#f92672"&gt;.&lt;/span&gt;active_object
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# now do one boolean cut on the plate&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;One boolean operation is faster and produces cleaner topology than N serial cuts.&lt;/p&gt;
&lt;h3 id="stl-export-blender-4x--5x"&gt;STL export (Blender 4.x / 5.x)
&lt;/h3&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-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ops&lt;span style="color:#f92672"&gt;.&lt;/span&gt;wm&lt;span style="color:#f92672"&gt;.&lt;/span&gt;stl_export(filepath&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/abs/path/part.stl&amp;#34;&lt;/span&gt;, export_selected_objects&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="collections-organising-multi-part-output"&gt;Collections (organising multi-part output)
&lt;/h3&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-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;col &lt;span style="color:#f92672"&gt;=&lt;/span&gt; bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;data&lt;span style="color:#f92672"&gt;.&lt;/span&gt;collections&lt;span style="color:#f92672"&gt;.&lt;/span&gt;new(&lt;span style="color:#e6db74"&gt;&amp;#34;Round Bases&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;context&lt;span style="color:#f92672"&gt;.&lt;/span&gt;scene&lt;span style="color:#f92672"&gt;.&lt;/span&gt;collection&lt;span style="color:#f92672"&gt;.&lt;/span&gt;children&lt;span style="color:#f92672"&gt;.&lt;/span&gt;link(col)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; c &lt;span style="color:#f92672"&gt;in&lt;/span&gt; obj&lt;span style="color:#f92672"&gt;.&lt;/span&gt;users_collection:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; c&lt;span style="color:#f92672"&gt;.&lt;/span&gt;objects&lt;span style="color:#f92672"&gt;.&lt;/span&gt;unlink(obj)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;col&lt;span style="color:#f92672"&gt;.&lt;/span&gt;objects&lt;span style="color:#f92672"&gt;.&lt;/span&gt;link(obj)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="parametric-families"&gt;Parametric families
&lt;/h2&gt;&lt;p&gt;The main loop pattern — build one function that takes dimensions, call it for each size:&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-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;SIZES &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;15&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;25&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:#66d9ef"&gt;for&lt;/span&gt; w &lt;span style="color:#f92672"&gt;in&lt;/span&gt; SIZES:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; obj &lt;span style="color:#f92672"&gt;=&lt;/span&gt; make_clip(width&lt;span style="color:#f92672"&gt;=&lt;/span&gt;w)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; obj&lt;span style="color:#f92672"&gt;.&lt;/span&gt;name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;clip_&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;w&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;mm&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ops&lt;span style="color:#f92672"&gt;.&lt;/span&gt;object&lt;span style="color:#f92672"&gt;.&lt;/span&gt;select_all(action&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;DESELECT&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; obj&lt;span style="color:#f92672"&gt;.&lt;/span&gt;select_set(&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ops&lt;span style="color:#f92672"&gt;.&lt;/span&gt;wm&lt;span style="color:#f92672"&gt;.&lt;/span&gt;stl_export(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; filepath&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/output/clip_&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;w&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;mm.stl&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; export_selected_objects&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Put all tuneable values at the top of the file in a &lt;code&gt;# CONFIG&lt;/code&gt; block. This makes the script easy to hand to Claude and say &amp;ldquo;change the hole diameter to 7mm and add two more rows.&amp;rdquo;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="clearing-the-scene-before-a-run"&gt;Clearing the scene before a run
&lt;/h2&gt;&lt;p&gt;Add this at the top when iterating interactively — otherwise re-running doubles the objects:&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-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ops&lt;span style="color:#f92672"&gt;.&lt;/span&gt;object&lt;span style="color:#f92672"&gt;.&lt;/span&gt;select_all(action&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;SELECT&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ops&lt;span style="color:#f92672"&gt;.&lt;/span&gt;object&lt;span style="color:#f92672"&gt;.&lt;/span&gt;delete()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; block &lt;span style="color:#f92672"&gt;in&lt;/span&gt; bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;data&lt;span style="color:#f92672"&gt;.&lt;/span&gt;meshes:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bpy&lt;span style="color:#f92672"&gt;.&lt;/span&gt;data&lt;span style="color:#f92672"&gt;.&lt;/span&gt;meshes&lt;span style="color:#f92672"&gt;.&lt;/span&gt;remove(block)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="viewport-layout-before-export"&gt;Viewport layout before export
&lt;/h2&gt;&lt;p&gt;Spread objects out so you can visually review them before committing to an export:&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-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;xoff &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; obj, w &lt;span style="color:#f92672"&gt;in&lt;/span&gt; zip(objects, widths):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; obj&lt;span style="color:#f92672"&gt;.&lt;/span&gt;location&lt;span style="color:#f92672"&gt;.&lt;/span&gt;x &lt;span style="color:#f92672"&gt;=&lt;/span&gt; xoff
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; xoff &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; w &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For print batches, sort largest-first and pack into rows within your bed dimensions (shelf bin-packing against &lt;code&gt;PLATE_W × PLATE_H&lt;/code&gt;).&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="working-with-claude"&gt;Working with Claude
&lt;/h2&gt;&lt;p&gt;The config-block pattern pairs well with LLM iteration. Because all tuneable values sit in one place and each operation is a named function, you can describe changes in plain language:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Change &lt;code&gt;HOLE_DIAM&lt;/code&gt; to 6 and add a third text line below line 2&amp;rdquo;&lt;/em&gt; — Claude edits two constants and adds a &lt;code&gt;make_line()&lt;/code&gt; call.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;The holes in rows 2 and 3 need 3mm more clearance from the edge&amp;rdquo;&lt;/em&gt; — Claude adjusts &lt;code&gt;x_origin&lt;/code&gt; offset computation.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Add a chamfer around the perimeter of the top face&amp;rdquo;&lt;/em&gt; — Claude adds a bmesh loop and inset operation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The workflow is: run → look at viewport → describe the problem → apply updated script → repeat. Iteration is fast because the viewport gives immediate visual feedback and the script regenerates from scratch each run.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="limitations"&gt;Limitations
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Booleans are fragile on non-manifold geometry.&lt;/strong&gt; If a cutter face is coplanar with the target, or vertices are nearly coincident, Blender&amp;rsquo;s solver can produce garbage. Add a small bleed (0.5–1 mm) so cutters fully penetrate surfaces.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No parametric constraints.&lt;/strong&gt; Unlike CadQuery, there is no &amp;ldquo;keep this face parallel to that face&amp;rdquo; system. Dimension changes cascade manually. This is manageable when the config block is the only place numbers live.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Script state accumulates.&lt;/strong&gt; Re-running in an existing scene doubles the objects. Clear the scene first (see above) or check for existing objects by name before creating.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="related"&gt;Related
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/garage/" &gt;Garage — Scripted Parts&lt;/a&gt; — hole box and other physical builds using this approach&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/homelab/rack-support-brace/" &gt;Rack Support Brace&lt;/a&gt; — step-by-step: script → renders → headless → CI/CD&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Code Quality &amp; Architecture Analysis</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/code-quality/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/code-quality/</guid><description>&lt;p&gt;Static analysis catches bugs and code smells before they reach production. Architecture analysis catches structural decay before the codebase becomes unmaintainable. SonarQube does the former; Structure101 does the latter.&lt;/p&gt;
&lt;h2 id="sonarqube"&gt;SonarQube
&lt;/h2&gt;&lt;p&gt;A code quality and security platform. SonarQube runs static analysis on your codebase and reports bugs, code smells, security hotspots, and test coverage in a web dashboard. It supports 30+ languages; the Java and Kotlin analysis is particularly deep. Integrates into CI pipelines via the SonarScanner — a quality gate can fail the build if new code introduces issues above a configured threshold.&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;# Run analysis (from project root, after configuring sonar-project.properties)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sonar-scanner &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -Dsonar.projectKey&lt;span style="color:#f92672"&gt;=&lt;/span&gt;my-project &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -Dsonar.sources&lt;span style="color:#f92672"&gt;=&lt;/span&gt;src/main &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -Dsonar.tests&lt;span style="color:#f92672"&gt;=&lt;/span&gt;src/test &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -Dsonar.host.url&lt;span style="color:#f92672"&gt;=&lt;/span&gt;https://sonarqube.example.com &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -Dsonar.token&lt;span style="color:#f92672"&gt;=&lt;/span&gt;$SONAR_TOKEN
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Quality gates&lt;/strong&gt; define the conditions a project must meet — for example: no new critical bugs, test coverage on new code ≥ 80%, no new security hotspots unreviewed. A gate failure blocks the PR merge or CI pipeline. The built-in &amp;ldquo;Sonar Way&amp;rdquo; gate is a sensible default; most teams customise it over time.&lt;/p&gt;
&lt;p&gt;SonarQube Community Edition is free and covers the core analysis features. Enterprise Edition adds branch analysis, portfolio views, and security reports.&lt;/p&gt;
&lt;h2 id="structure101"&gt;Structure101
&lt;/h2&gt;&lt;p&gt;An architecture analysis tool for Java projects. Structure101 visualises the dependency structure of a codebase as a layered graph and identifies violations — circular dependencies between packages, classes depending on layers they shouldn&amp;rsquo;t reach, packages with too many inbound dependencies. It is particularly useful for large, long-lived Java codebases where architectural boundaries have eroded over time.&lt;/p&gt;
&lt;p&gt;The key report is the &lt;strong&gt;tangle&lt;/strong&gt; metric: a tangle is a group of packages with circular dependencies, and the tangle percentage measures how much of the codebase is affected. A clean architecture has zero tangles. Structure101 shows exactly which dependencies create each tangle, making it possible to systematically break them.&lt;/p&gt;
&lt;p&gt;It integrates with build systems (Maven, Gradle) to enforce architecture rules in CI — build fails if the tangle percentage exceeds a threshold, or if a dependency violates a defined layering rule.&lt;/p&gt;
&lt;h2 id="resources"&gt;Resources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.sonarsource.com/sonarqube/" target="_blank" rel="noopener"
 &gt;SonarQube documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.sonarsource.com/open-source-editions/sonarqube-community-edition/" target="_blank" rel="noopener"
 &gt;SonarQube Community Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://structure101.com/documents/" target="_blank" rel="noopener"
 &gt;Structure101 documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&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>Elasticsearch &amp; Kibana</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/elk/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/elk/</guid><description>&lt;p&gt;Elasticsearch is a distributed search and analytics engine built on Apache Lucene. Kibana is its web UI for querying, visualising, and exploring the data stored in Elasticsearch. Together they form the search and analysis layer of the ELK stack — typically with Logstash or Beats collecting and shipping data into Elasticsearch, and Kibana on top for humans to interact with it.&lt;/p&gt;
&lt;h2 id="elasticsearch"&gt;Elasticsearch
&lt;/h2&gt;&lt;p&gt;A document store where every document is JSON and every field is indexed by default. Queries are also JSON, using a rich query DSL that supports full-text search, structured filters, aggregations, and geospatial queries. Elasticsearch is horizontally scalable — an index is split into shards, shards are distributed across nodes, and replicas provide redundancy. Adding nodes increases both capacity and query throughput.&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-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;Index&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;a&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;document&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;POST&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;/logs/_doc&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;&amp;#34;timestamp&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;2026-06-04T12:00:00Z&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;&amp;#34;level&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;error&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;&amp;#34;service&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;api&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;&amp;#34;message&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;connection refused&amp;#34;&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&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 style="color:#960050;background-color:#1e0010"&gt;Search&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;with&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;filter&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;GET&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;/logs/_search&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;&amp;#34;query&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;&amp;#34;bool&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;&amp;#34;filter&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;&amp;#34;term&amp;#34;&lt;/span&gt;: { &lt;span style="color:#f92672"&gt;&amp;#34;level&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;error&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;&amp;#34;term&amp;#34;&lt;/span&gt;: { &lt;span style="color:#f92672"&gt;&amp;#34;service&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;api&amp;#34;&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&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At scale, index lifecycle management (ILM) policies handle the hot-warm-cold tiering automatically — recent indices stay on fast nodes, older indices roll to cheaper storage, and expired indices are deleted.&lt;/p&gt;
&lt;h2 id="kibana"&gt;Kibana
&lt;/h2&gt;&lt;p&gt;The interface to Elasticsearch. Kibana&amp;rsquo;s core is &lt;strong&gt;Discover&lt;/strong&gt; — a time-series log explorer with free-text search and field filtering — and &lt;strong&gt;Dashboards&lt;/strong&gt; — composable visualisations (time series, bar charts, pie charts, data tables, maps) that query Elasticsearch directly. For log aggregation and observability use cases, a typical workflow is: ship logs into Elasticsearch via Filebeat or Logstash, explore them in Discover, build dashboards for the signals that matter, set up alerting rules on those patterns.&lt;/p&gt;
&lt;p&gt;Kibana also hosts the Elastic APM UI (application performance monitoring), the SIEM app (security event correlation), and the Lens visual editor for building dashboards without writing aggregation queries by hand.&lt;/p&gt;
&lt;h2 id="elk-vs-the-grafana-stack"&gt;ELK vs the Grafana stack
&lt;/h2&gt;&lt;p&gt;The Grafana stack (&lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/observability/loki/" &gt;Loki&lt;/a&gt; + &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/observability/prometheus/" &gt;Prometheus&lt;/a&gt; + &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/observability/grafana/" &gt;Grafana&lt;/a&gt;) has become the common alternative for cloud-native environments. The key difference: Loki indexes only log metadata (labels), not the full log content — it is cheaper to run and query at scale, but full-text search across log bodies is slower. Elasticsearch indexes everything and full-text search is fast, but the storage and memory cost is significantly higher. For log volumes in the hundreds of GB/day and above, the operational cost of Elasticsearch becomes the dominant factor. For environments that need fast full-text search across structured and unstructured data — logs, documents, events — Elasticsearch earns its cost.&lt;/p&gt;
&lt;h2 id="resources"&gt;Resources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.elastic.co/docs/solutions/search" target="_blank" rel="noopener"
 &gt;Elasticsearch documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.elastic.co/docs/solutions/observability" target="_blank" rel="noopener"
 &gt;Kibana documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.elastic.co/guide/en/ecs/current/" target="_blank" rel="noopener"
 &gt;Elastic common schema (ECS)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>IntelliJ IDEA</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/idea/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/idea/</guid><description>&lt;p&gt;IntelliJ IDEA is JetBrains&amp;rsquo; Java and Kotlin IDE. It has the deepest language understanding of any Java IDE — code completion that reasons about types across the entire project, refactoring that updates all call sites, a debugger with expression evaluation, and a profiler integrated into the same window. The free Community Edition covers Java, Kotlin, Groovy, and Scala. The paid Ultimate Edition adds frameworks (Spring, Quarkus, Micronaut), database tools, HTTP client, and built-in support for web technologies.&lt;/p&gt;
&lt;h2 id="key-capabilities"&gt;Key capabilities
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Refactoring&lt;/strong&gt; — rename a class and every reference in every file updates. Extract method, inline variable, move class to a different package, change method signature — all with full project-wide impact analysis before execution.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Navigation&lt;/strong&gt; — &lt;code&gt;Cmd+Click&lt;/code&gt; goes to declaration, &lt;code&gt;Cmd+B&lt;/code&gt; shows all usages, &lt;code&gt;Cmd+E&lt;/code&gt; opens recent files, &lt;code&gt;Cmd+Shift+F&lt;/code&gt; searches across the entire project. In a large codebase, knowing where things are called from matters more than reading the code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inspections&lt;/strong&gt; — real-time static analysis flags potential bugs, code style violations, and anti-patterns as you type, not at build time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Debugger&lt;/strong&gt; — set breakpoints, step through code, evaluate arbitrary expressions in the current scope, watch variables, set conditional breakpoints. Remote debugging attaches to a running JVM with a single click.&lt;/p&gt;
&lt;h2 id="plugins"&gt;Plugins
&lt;/h2&gt;&lt;p&gt;The plugin ecosystem extends IDEA significantly. Notable ones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;IdeaVim&lt;/strong&gt; — vim key bindings throughout the editor&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes&lt;/strong&gt; — YAML editing with schema validation and cluster connectivity&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Database tools&lt;/strong&gt; (Ultimate) — query databases from within the IDE&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="remote-development"&gt;Remote development
&lt;/h2&gt;&lt;p&gt;IDEA supports JetBrains Gateway for remote development — the IDE UI runs locally but the indexing and execution happen on a remote machine or container. Useful for developing against large codebases on powerful remote hardware.&lt;/p&gt;
&lt;h2 id="resources"&gt;Resources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.jetbrains.com/help/idea/" target="_blank" rel="noopener"
 &gt;IntelliJ IDEA documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.jetbrains.com/help/idea/keyboard-shortcuts-and-mouse-reference.html" target="_blank" rel="noopener"
 &gt;JetBrains IDE key bindings&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>Nexus Repository</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/nexus/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/nexus/</guid><description>&lt;p&gt;Sonatype Nexus Repository Manager is a universal artifact repository. It stores and serves build artifacts — Maven JARs, npm packages, Docker images, Helm charts, PyPI packages, raw binaries — from a single platform. It operates in three modes: hosted (your own artifacts), proxy (a local mirror of an upstream registry), and group (a virtual repository that aggregates hosted and proxy repos behind one URL).&lt;/p&gt;
&lt;h2 id="why-it-exists"&gt;Why it exists
&lt;/h2&gt;&lt;p&gt;Two problems: you want a private registry for internal artifacts (container images, internal libraries), and you want to cache upstream registries to reduce build times, avoid rate limits, and insulate builds from upstream outages. Nexus solves both. Point your build tools at Nexus; Nexus fetches from upstream on first request and caches, or serves from your hosted repos.&lt;/p&gt;
&lt;h2 id="repository-types"&gt;Repository types
&lt;/h2&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;# Maven proxy — caches Maven Central
https://nexus.example.com/repository/maven-central/

# Maven hosted — your internal JARs
https://nexus.example.com/repository/maven-releases/
https://nexus.example.com/repository/maven-snapshots/

# Docker hosted — your container images
https://nexus.example.com/repository/docker-hosted/

# npm proxy — caches registry.npmjs.org
https://nexus.example.com/repository/npm-proxy/
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="docker-integration"&gt;Docker integration
&lt;/h2&gt;&lt;p&gt;Nexus can expose a hosted Docker registry on a dedicated port. Configure the Docker daemon to trust the registry, then push and pull normally:&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 login nexus.example.com:8082
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker tag myimage:latest nexus.example.com:8082/myimage:latest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker push nexus.example.com:8082/myimage:latest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="maven-integration"&gt;Maven integration
&lt;/h2&gt;&lt;p&gt;Point Maven at Nexus by configuring &lt;code&gt;settings.xml&lt;/code&gt; to use the Nexus group repository as a mirror:&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-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;mirrors&amp;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;&amp;lt;mirror&amp;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;&amp;lt;id&amp;gt;&lt;/span&gt;nexus&lt;span style="color:#f92672"&gt;&amp;lt;/id&amp;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;&amp;lt;mirrorOf&amp;gt;&lt;/span&gt;*&lt;span style="color:#f92672"&gt;&amp;lt;/mirrorOf&amp;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;&amp;lt;url&amp;gt;&lt;/span&gt;https://nexus.example.com/repository/maven-public/&lt;span style="color:#f92672"&gt;&amp;lt;/url&amp;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;&amp;lt;/mirror&amp;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;&amp;lt;/mirrors&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;All dependency resolution goes through Nexus. Cached. Air-gapped builds are possible by pre-populating the cache.&lt;/p&gt;
&lt;h2 id="resources"&gt;Resources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://help.sonatype.com/repomanager3" target="_blank" rel="noopener"
 &gt;Nexus Repository documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.sonatype.com/products/sonatype-nexus-oss" target="_blank" rel="noopener"
 &gt;Sonatype Nexus OSS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>NGINX</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/nginx/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/nginx/</guid><description>&lt;p&gt;&lt;img alt="NGINX" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px"&gt;&lt;/p&gt;
&lt;p&gt;NGINX is a high-performance web server and reverse proxy built around an event-driven, non-blocking architecture. Where Apache spawns a thread per connection, NGINX handles thousands of concurrent connections from a small, fixed number of worker processes — making it well-suited to acting as the entry point for modern distributed systems.&lt;/p&gt;
&lt;h2 id="server-blocks"&gt;Server blocks
&lt;/h2&gt;&lt;p&gt;NGINX configuration is structured around &lt;code&gt;server&lt;/code&gt; blocks (analogous to Apache&amp;rsquo;s VirtualHost). Each block defines how to handle requests for a given hostname or port:&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-nginx" data-lang="nginx"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;server&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;listen&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server_name&lt;/span&gt; &lt;span style="color:#e6db74"&gt;example.com&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;location&lt;/span&gt; &lt;span style="color:#e6db74"&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;root&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/var/www/html&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;index&lt;/span&gt; &lt;span style="color:#e6db74"&gt;index.html&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Multiple server blocks in a single NGINX instance handle traffic for different domains — NGINX selects the block by matching the &lt;code&gt;Host&lt;/code&gt; header against &lt;code&gt;server_name&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="reverse-proxy"&gt;Reverse proxy
&lt;/h2&gt;&lt;p&gt;The primary use case in infrastructure work: NGINX sits in front of application servers and forwards requests to them. The application never needs to be exposed directly.&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-nginx" data-lang="nginx"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;server&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;listen&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server_name&lt;/span&gt; &lt;span style="color:#e6db74"&gt;api.example.com&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;location&lt;/span&gt; &lt;span style="color:#e6db74"&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;proxy_pass&lt;/span&gt; &lt;span style="color:#e6db74"&gt;http://127.0.0.1:8080&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;Host&lt;/span&gt; $host;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;X-Real-IP&lt;/span&gt; $remote_addr;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;X-Forwarded-For&lt;/span&gt; $proxy_add_x_forwarded_for;
&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;X-Forwarded-For&lt;/code&gt; and &lt;code&gt;X-Real-IP&lt;/code&gt; headers pass the original client IP to the upstream — without these the application sees only the proxy address.&lt;/p&gt;
&lt;h2 id="ssl-termination"&gt;SSL termination
&lt;/h2&gt;&lt;p&gt;NGINX handles TLS and speaks plain HTTP to backends. This centralises certificate management and offloads crypto from application processes:&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-nginx" data-lang="nginx"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;server&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;listen&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;443&lt;/span&gt; &lt;span style="color:#e6db74"&gt;ssl&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server_name&lt;/span&gt; &lt;span style="color:#e6db74"&gt;example.com&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;ssl_certificate&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/etc/letsencrypt/live/example.com/fullchain.pem&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ssl_certificate_key&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/etc/letsencrypt/live/example.com/privkey.pem&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ssl_protocols&lt;/span&gt; &lt;span style="color:#e6db74"&gt;TLSv1.2&lt;/span&gt; &lt;span style="color:#e6db74"&gt;TLSv1.3&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;location&lt;/span&gt; &lt;span style="color:#e6db74"&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;proxy_pass&lt;/span&gt; &lt;span style="color:#e6db74"&gt;http://127.0.0.1:8080&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&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;# Redirect HTTP → HTTPS
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;server&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;listen&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server_name&lt;/span&gt; &lt;span style="color:#e6db74"&gt;example.com&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;301&lt;/span&gt; &lt;span style="color:#e6db74"&gt;https://&lt;/span&gt;$host$request_uri;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pair with &lt;a class="link" href="../letsencrypt/" &gt;Let&amp;rsquo;s Encrypt&lt;/a&gt; and Certbot for automated certificate issuance and renewal.&lt;/p&gt;
&lt;h2 id="load-balancing"&gt;Load balancing
&lt;/h2&gt;&lt;p&gt;NGINX distributes traffic across multiple upstream instances using an &lt;code&gt;upstream&lt;/code&gt; block:&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-nginx" data-lang="nginx"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;upstream&lt;/span&gt; &lt;span style="color:#e6db74"&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;server&lt;/span&gt; 10.0.0.1:&lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server&lt;/span&gt; 10.0.0.2:&lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server&lt;/span&gt; 10.0.0.3:&lt;span style="color:#ae81ff"&gt;8080&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;server&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;listen&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;location&lt;/span&gt; &lt;span style="color:#e6db74"&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;proxy_pass&lt;/span&gt; &lt;span style="color:#e6db74"&gt;http://app&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Default is round-robin. Other strategies: &lt;code&gt;least_conn&lt;/code&gt; (fewest active connections), &lt;code&gt;ip_hash&lt;/code&gt; (sticky sessions by client IP), &lt;code&gt;hash&lt;/code&gt; (consistent hashing by arbitrary key).&lt;/p&gt;
&lt;h2 id="static-files"&gt;Static files
&lt;/h2&gt;&lt;p&gt;NGINX serves static assets efficiently — far more so than most application frameworks. A common pattern: serve static files directly from NGINX, proxy only dynamic requests to the application.&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-nginx" data-lang="nginx"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;location&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/static/&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;alias&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/var/www/static/&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;expires&lt;/span&gt; &lt;span style="color:#e6db74"&gt;1y&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;add_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;Cache-Control&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;public,&lt;/span&gt; &lt;span style="color:#e6db74"&gt;immutable&amp;#34;&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;location&lt;/span&gt; &lt;span style="color:#e6db74"&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;proxy_pass&lt;/span&gt; &lt;span style="color:#e6db74"&gt;http://app&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="useful-patterns"&gt;Useful patterns
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Rate limiting&lt;/strong&gt; — protect endpoints from abuse:&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-nginx" data-lang="nginx"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;limit_req_zone&lt;/span&gt; $binary_remote_addr &lt;span style="color:#e6db74"&gt;zone=api:10m&lt;/span&gt; &lt;span style="color:#e6db74"&gt;rate=10r/s&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:#66d9ef"&gt;location&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/api/&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;limit_req&lt;/span&gt; &lt;span style="color:#e6db74"&gt;zone=api&lt;/span&gt; &lt;span style="color:#e6db74"&gt;burst=20&lt;/span&gt; &lt;span style="color:#e6db74"&gt;nodelay&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_pass&lt;/span&gt; &lt;span style="color:#e6db74"&gt;http://app&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Health check path&lt;/strong&gt; — expose a simple status endpoint:&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-nginx" data-lang="nginx"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;location&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/healthz&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;access_log&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;off&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;ok\n&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;add_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;Content-Type&lt;/span&gt; &lt;span style="color:#e6db74"&gt;text/plain&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Test config before reloading: &lt;code&gt;nginx -t&lt;/code&gt;. Reload without dropping connections: &lt;code&gt;nginx -s reload&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="resources"&gt;Resources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://nginx.org/en/docs/" target="_blank" rel="noopener"
 &gt;NGINX documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.digitalocean.com/community/tools/nginx" target="_blank" rel="noopener"
 &gt;NGINX config generator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://ssl-config.mozilla.org/" target="_blank" rel="noopener"
 &gt;Mozilla SSL Configuration Generator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Reloader</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/reloader/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/reloader/</guid><description>&lt;p&gt;Reloader is a Kubernetes controller from Stakater that watches ConfigMaps and Secrets and automatically triggers rolling restarts of Deployments, StatefulSets, and DaemonSets when the watched resources change. Kubernetes does not do this natively — updating a ConfigMap does not restart pods that consume it, so configuration changes don&amp;rsquo;t take effect until the next deploy.&lt;/p&gt;
&lt;h2 id="usage"&gt;Usage
&lt;/h2&gt;&lt;p&gt;Annotate a Deployment to watch a specific ConfigMap or Secret:&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;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;apps/v1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Deployment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-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;annotations&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;reloader.stakater.com/auto&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;true&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;# watch all referenced ConfigMaps/Secrets&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# OR be specific:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;configmap.reloader.stakater.com/reload&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;my-config&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;secret.reloader.stakater.com/reload&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;my-secret&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When the ConfigMap or Secret changes, Reloader detects it and triggers a rolling restart by updating a pod template annotation. The deployment rolls out new pods that pick up the updated configuration.&lt;/p&gt;
&lt;h2 id="installation"&gt;Installation
&lt;/h2&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;helm repo add stakater https://stakater.github.io/stakater-charts
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm install reloader stakater/reloader -n reloader --create-namespace
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="why-not-use-a-hash-annotation-manually"&gt;Why not use a hash annotation manually
&lt;/h2&gt;&lt;p&gt;The common alternative is to inject a hash of the ConfigMap into the pod template annotations via Helm or Kustomize — when the hash changes, Kubernetes rolls the deployment. This works but requires build-time tooling. Reloader handles it at runtime without any changes to the deployment pipeline.&lt;/p&gt;
&lt;h2 id="resources"&gt;Resources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/stakater/Reloader" target="_blank" rel="noopener"
 &gt;Reloader GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/stakater/stakater-charts" target="_blank" rel="noopener"
 &gt;Stakater charts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Virtualization — KVM and KubeVirt</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/virtualization/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/frameworks-tools/virtualization/</guid><description>&lt;p&gt;KVM is the Linux kernel&amp;rsquo;s native hypervisor. KubeVirt extends Kubernetes to run virtual machines using KVM under the hood. They are the same virtualization layer at different levels of abstraction — KVM on bare metal, KubeVirt in a Kubernetes cluster.&lt;/p&gt;
&lt;h2 id="kvm"&gt;KVM
&lt;/h2&gt;&lt;p&gt;Kernel-based Virtual Machine. KVM turns the Linux kernel into a hypervisor using hardware virtualization extensions (Intel VT-x, AMD-V). Virtual machines run as regular Linux processes backed by QEMU for device emulation. Managed via &lt;code&gt;libvirt&lt;/code&gt; and its CLI tools (&lt;code&gt;virsh&lt;/code&gt;, &lt;code&gt;virt-install&lt;/code&gt;) or the &lt;code&gt;virt-manager&lt;/code&gt; GUI.&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;# Create a VM from an ISO&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;virt-install &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --name ubuntu-vm &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --ram &lt;span style="color:#ae81ff"&gt;4096&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; --vcpus &lt;span style="color:#ae81ff"&gt;2&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; --disk path&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/var/lib/libvirt/images/ubuntu.qcow2,size&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;40&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; --cdrom /tmp/ubuntu.iso &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --os-variant ubuntu22.04
&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;# List running VMs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;virsh list
&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;# Start/stop&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;virsh start ubuntu-vm
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;virsh shutdown ubuntu-vm
&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;# Connect to console&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;virsh console ubuntu-vm
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;KVM gives near-native performance for CPU-bound workloads. Network and disk I/O use virtio drivers for efficient paravirtualised I/O. Live migration moves a running VM between hosts without downtime if shared storage is available.&lt;/p&gt;
&lt;h2 id="kubevirt"&gt;KubeVirt
&lt;/h2&gt;&lt;p&gt;KubeVirt adds &lt;code&gt;VirtualMachine&lt;/code&gt; and &lt;code&gt;VirtualMachineInstance&lt;/code&gt; CRDs to Kubernetes. VMs are defined as Kubernetes resources, scheduled by the Kubernetes scheduler, and managed alongside containers. Under the hood, each VM runs as a pod containing a QEMU-KVM process.&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;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;kubevirt.io/v1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;VirtualMachine&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ubuntu-vm&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;running&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;template&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;domain&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;devices&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;disks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;rootdisk&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;disk&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;bus&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;virtio&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;resources&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;requests&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;memory&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;4Gi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;cpu&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;2&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;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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;rootdisk&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;containerDisk&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;kubevirt/fedora-cloud-container-disk-demo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;virtctl&lt;/code&gt; CLI complements &lt;code&gt;kubectl&lt;/code&gt; for VM-specific operations:&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;virtctl start ubuntu-vm
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;virtctl stop ubuntu-vm
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;virtctl console ubuntu-vm &lt;span style="color:#75715e"&gt;# serial console&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;virtctl ssh ubuntu-vm &lt;span style="color:#75715e"&gt;# SSH via the Kubernetes API&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;virtctl migrate ubuntu-vm &lt;span style="color:#75715e"&gt;# live migrate to another node&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="cdi--containerized-data-importer"&gt;CDI — Containerized Data Importer
&lt;/h2&gt;&lt;p&gt;KubeVirt is typically paired with CDI, which imports VM disk images from URLs, container registries, or PVCs into &lt;code&gt;DataVolume&lt;/code&gt; resources that VMs can boot from. CDI handles the data flow; the VM definition just references the DataVolume.&lt;/p&gt;
&lt;h2 id="why-vms-in-kubernetes"&gt;Why VMs in Kubernetes
&lt;/h2&gt;&lt;p&gt;Some workloads can&amp;rsquo;t be containerised — legacy applications expecting a full OS, Windows workloads, software with kernel module requirements. KubeVirt lets those workloads live in the same cluster as containers, managed with the same tooling, subject to the same scheduling and networking policies.&lt;/p&gt;
&lt;h2 id="resources"&gt;Resources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.linux-kvm.org/page/Documents" target="_blank" rel="noopener"
 &gt;KVM documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kubevirt.io/user-guide/" target="_blank" rel="noopener"
 &gt;KubeVirt documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/kubevirt/containerized-data-importer" target="_blank" rel="noopener"
 &gt;CDI documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>