<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Software on dev@iostream.pl</title><link>https://dev.iostream.pl/tags/software/</link><description>Recent content in Software on dev@iostream.pl</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Sat, 25 Jul 2026 00:20:00 +0200</lastBuildDate><atom:link href="https://dev.iostream.pl/tags/software/index.xml" rel="self" type="application/rss+xml"/><item><title>Seek &amp; Destroy</title><link>https://dev.iostream.pl/posts/2026-07-25/</link><pubDate>Sat, 25 Jul 2026 00:20:00 +0200</pubDate><guid>https://dev.iostream.pl/posts/2026-07-25/</guid><description>&lt;p&gt;Every respectable text editor supports searching and replacing text, whether interactively or automatically. Editors from the upper shelves can also work with regular expressions.&lt;/p&gt;
&lt;p&gt;And then there is the editor from the highest shelf — Emacs — which handles all of this brilliantly, in several different ways.&lt;/p&gt;
&lt;h1 id="searching"&gt;Searching
&lt;/h1&gt;&lt;p&gt;In an average text editor, searching for text requires pressing a keyboard shortcut. Alternatively, you can try to remember beneath which pile of debris on your desk the mouse is buried, move it around until the pointer appears, and click whichever button opens the appropriate dialog box.&lt;/p&gt;
&lt;p&gt;Then you type the search term, confirm it, and the text is found. Thank you very much, and we hope to see you again during your next search.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;But not in Emacs.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;By default, the King of Editors provides &lt;strong&gt;incremental search&lt;/strong&gt;. Pressing &lt;code&gt;C-s&lt;/code&gt; — or invoking &lt;code&gt;M-x isearch-forward&lt;/code&gt; — starts the search and waits for input in the minibuffer.&lt;/p&gt;
&lt;p&gt;With every character you type, Emacs attempts to find the entire string entered so far and moves the cursor to the matching location in the text. The current match is highlighted by default. Any other occurrences visible in the buffer are highlighted as well, using a different combination of foreground and background colours.&lt;/p&gt;
&lt;p&gt;The search string is remembered between searches. Pressing &lt;code&gt;C-s&lt;/code&gt; again moves the cursor to the next occurrence of the current search string.&lt;/p&gt;
&lt;p&gt;When Emacs reaches the end of the buffer, it displays an appropriate message in the minibuffer. Pressing &lt;code&gt;C-s&lt;/code&gt; once more wraps the search around and continues from the beginning of the buffer.&lt;/p&gt;
&lt;h2 id="searching-backwards"&gt;Searching backwards
&lt;/h2&gt;&lt;p&gt;Incremental search also works &lt;strong&gt;backwards&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;C-r&lt;/code&gt; shortcut — or &lt;code&gt;M-x isearch-backward&lt;/code&gt; — behaves just like forward incremental search, except that it moves in the opposite direction.&lt;/p&gt;
&lt;p&gt;After reaching the &lt;em&gt;beginning&lt;/em&gt; of the buffer, Emacs displays an appropriate message in the minibuffer. Pressing &lt;code&gt;C-r&lt;/code&gt; again wraps the search around and continues from the &lt;em&gt;end of the buffer&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="search-results"&gt;Search results
&lt;/h2&gt;&lt;p&gt;Pressing &lt;code&gt;M-s o&lt;/code&gt; during an incremental search — which invokes &lt;code&gt;M-x isearch-occur&lt;/code&gt; — opens another &lt;em&gt;window&lt;/em&gt; containing the search results: every line in which the requested phrase occurs.&lt;/p&gt;
&lt;p&gt;The matching phrase is, naturally, highlighted so that it stands out. At the top of the window, Emacs displays a summary showing how many matches were found, on how many lines, and in which buffer.&lt;/p&gt;
&lt;p&gt;More importantly, the results are navigable. Move to any entry and press &lt;code&gt;RET&lt;/code&gt;, and Emacs takes you directly to the corresponding line in the original buffer.&lt;/p&gt;
&lt;h2 id="non-incremental-search"&gt;Non-incremental search
&lt;/h2&gt;&lt;p&gt;What if someone simply wants to enter a complete search string first and then jump to its first occurrence?&lt;/p&gt;
&lt;p&gt;A traditional, non-incremental search can be performed as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;C-s RET &amp;lt;search text&amp;gt; RET&lt;/code&gt; — or &lt;code&gt;M-x search-forward&lt;/code&gt; — searches forwards,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C-r RET &amp;lt;search text&amp;gt; RET&lt;/code&gt; — or &lt;code&gt;M-x search-backward&lt;/code&gt; — searches backwards.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In other words, it works much like incremental search, except that you press &lt;code&gt;RET&lt;/code&gt; before entering the phrase.&lt;/p&gt;
&lt;h2 id="case-sensitivity"&gt;Case sensitivity
&lt;/h2&gt;&lt;p&gt;By default, Emacs uses a convenient mechanism known as &lt;strong&gt;case folding&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;As long as the search string contains only lowercase letters, Emacs ignores case and attempts to match as many occurrences as possible. As soon as you enter an uppercase letter, the search becomes case-sensitive.&lt;/p&gt;
&lt;p&gt;During an incremental search, &lt;code&gt;M-c&lt;/code&gt; toggles case sensitivity explicitly, so there is no need to introduce an uppercase letter merely to change the matching behaviour.&lt;/p&gt;
&lt;h2 id="searching-with-regular-expressions"&gt;Searching with regular expressions
&lt;/h2&gt;&lt;p&gt;Emacs also provides both incremental and non-incremental searches based on regular expressions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;M-x re-search-forward&lt;/code&gt; searches forwards for the next piece of text matching the given regular expression. When a match is found, the cursor is moved to its end. If no match exists, Emacs displays an appropriate message in the minibuffer.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;M-x re-search-backward&lt;/code&gt; performs the same operation in the opposite direction.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C-M-s&lt;/code&gt; — &lt;code&gt;M-x isearch-forward-regexp&lt;/code&gt; — starts an incremental forward search using a regular expression.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C-M-r&lt;/code&gt; — &lt;code&gt;M-x isearch-backward-regexp&lt;/code&gt; — starts an incremental backward search using a regular expression.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="searching-within-a-region"&gt;Searching within a region
&lt;/h2&gt;&lt;p&gt;By default, Emacs searches through the buffer. To limit a search to a particular region, one can use a small but extremely useful trick: &lt;strong&gt;narrowing&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Press &lt;code&gt;C-x n n&lt;/code&gt; — or invoke &lt;code&gt;M-x narrow-to-region&lt;/code&gt; — to narrow the buffer to the currently selected region.&lt;/p&gt;
&lt;p&gt;This is interesting because the rest of the text temporarily disappears. All subsequent operations, including searching, are restricted to the visible part of the buffer.&lt;/p&gt;
&lt;p&gt;Once the work is complete, press &lt;code&gt;C-x n w&lt;/code&gt; — or invoke &lt;code&gt;M-x widen&lt;/code&gt; — to restore the hidden text to its rightful place.&lt;/p&gt;
&lt;h2 id="search-history"&gt;Search history
&lt;/h2&gt;&lt;p&gt;Search history is, of course, preserved.&lt;/p&gt;
&lt;p&gt;While searching, you can use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;M-p&lt;/code&gt; to recall the previous search string,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;M-n&lt;/code&gt; to move to the next search string in the history.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="other-useful-commands"&gt;Other useful commands
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;C-y&lt;/code&gt; — &lt;code&gt;M-x yank&lt;/code&gt; — inserts the most recently killed text into the search string, because why would it not?&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C-w&lt;/code&gt; adds the remainder of the word at point to the search string.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="replacing"&gt;Replacing
&lt;/h1&gt;&lt;p&gt;Once again, the King of Editors refuses to blend in with the crowd and offers something better than the competition.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;M-%&lt;/code&gt; — &lt;code&gt;M-x query-replace&lt;/code&gt; — interactively replaces occurrences of one string with another, starting from the position at which the command was invoked.&lt;/p&gt;
&lt;p&gt;At every match, Emacs asks the user what should happen next:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;y&lt;/code&gt; replaces the current match and moves to the next one,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;n&lt;/code&gt; skips the current match and moves to the next one,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;q&lt;/code&gt; ends the replacement operation,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;!&lt;/code&gt; replaces the current match and every remaining match without asking any more unnecessary questions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other available actions can be found, for example, by invoking:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;M-x describe-function RET query-replace RET
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;C-M-%&lt;/code&gt; — &lt;code&gt;M-x query-replace-regexp&lt;/code&gt; — works in the same way, but uses regular expressions.&lt;/p&gt;
&lt;p&gt;Emacs also tries to preserve the &lt;strong&gt;capitalisation style&lt;/strong&gt; of the replaced text.&lt;/p&gt;
&lt;p&gt;Suppose that every occurrence of &lt;code&gt;windows&lt;/code&gt; must be replaced with &lt;code&gt;linux&lt;/code&gt;. Emacs may perform the following replacements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;windows&lt;/code&gt; becomes &lt;code&gt;linux&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Windows&lt;/code&gt; becomes &lt;code&gt;Linux&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WINDOWS&lt;/code&gt; becomes &lt;code&gt;LINUX&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
 &lt;p&gt;The author believes that Windows should be replaced with Linux not only in this example, but also in the lives of us all.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="non-interactive-replacement"&gt;Non-interactive replacement
&lt;/h2&gt;&lt;p&gt;Emacs also provides commands that replace all matching occurrences without asking for confirmation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;M-x replace-string&lt;/code&gt; replaces every occurrence of one string with another, starting from the position at which the command was invoked.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;M-x replace-regexp&lt;/code&gt; replaces every piece of text matching the given regular expression with the specified replacement string, again starting from the current position.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="replacing-within-a-region"&gt;Replacing within a region
&lt;/h2&gt;&lt;p&gt;If a region is active, Emacs performs replacement commands within that region by default.&lt;/p&gt;
&lt;p&gt;There is no need to narrow or widen the buffer manually — although, naturally, Emacs will not prevent anyone from doing so.&lt;/p&gt;</description></item><item><title>Jenkins, Part Two: Disposable Agents and Builder Images</title><link>https://dev.iostream.pl/posts/2026-07-12/</link><pubDate>Sun, 12 Jul 2026 17:20:00 +0200</pubDate><guid>https://dev.iostream.pl/posts/2026-07-12/</guid><description>&lt;h1 id="once-again-why-all-these-containers"&gt;Once again: why all these containers?
&lt;/h1&gt;&lt;p&gt;The Kubernetes approach — short-lived, disposable things — works beautifully for Jenkins and similar CI/CD systems.&lt;/p&gt;
&lt;p&gt;Here is a list of tasks to execute in a place described by a specification, not by a physical location. Whether that place existed five minutes ago or will disappear five minutes from now does not matter all that much.&lt;/p&gt;
&lt;p&gt;Look at how the approach to running CI/CD jobs has evolved over the last twenty years:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;run the job in a blocking shell script,&lt;/li&gt;
&lt;li&gt;run the job directly on the Jenkins master,&lt;/li&gt;
&lt;li&gt;run the job on a dedicated Jenkins node,&lt;/li&gt;
&lt;li&gt;run the job on one of several dedicated Jenkins nodes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And now?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;run the job inside an ephemeral Pod, created just before the work starts and removed when the work is done.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hard not to admire that, is it?&lt;/p&gt;
&lt;p&gt;And there is more to admire. Each container can provide a different set of tools. Instead of installing everything manually on physical machines, you can point Jenkins at a ready-made image, built by someone else — and if you do not like that one, you can use another.&lt;/p&gt;
&lt;p&gt;All right, that is enough admiration for now.&lt;/p&gt;
&lt;h1 id="what-do-we-already-have"&gt;What do we already have?
&lt;/h1&gt;&lt;p&gt;Quite a lot:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Jenkins&lt;/strong&gt; running under Kubernetes,&lt;/li&gt;
&lt;li&gt;a working &lt;strong&gt;container registry&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;PVC&lt;/strong&gt; for Jenkins data,&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;Kubernetes cloud&lt;/strong&gt; definition in Jenkins,&lt;/li&gt;
&lt;li&gt;two already built &lt;strong&gt;images&lt;/strong&gt; that we are about to use.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="what-else-do-we-need"&gt;What else do we need?
&lt;/h1&gt;&lt;p&gt;Eventually, Jenkins will run jobs such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;building Python applications,&lt;/li&gt;
&lt;li&gt;building Go applications,&lt;/li&gt;
&lt;li&gt;building Hugo sites.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So we need three images.&lt;/p&gt;
&lt;h1 id="python-builder"&gt;Python builder
&lt;/h1&gt;&lt;p&gt;The Python image can be built from the following &lt;code&gt;Dockerfile&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-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;registry.lab.local/base-builder:0.1&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:#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;USER&lt;/span&gt; &lt;span style="color:#e6db74"&gt;root&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:#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; apt-get update &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get install -y --no-install-recommends &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; python3 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; python3-pip &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; python3-venv &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; python3-build &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;&amp;amp;&amp;amp;&lt;/span&gt; rm -rf /var/lib/apt/lists/*&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;RUN&lt;/span&gt; python3 -m pip install --break-system-packages &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; build &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; twine &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wheel &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; setuptools &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pytest &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ruff&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;USER&lt;/span&gt; &lt;span style="color:#e6db74"&gt;builder&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;/workspace&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:#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;CMD&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;sleep&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;infinity&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;Wait a moment. Do not reach for &lt;code&gt;docker build&lt;/code&gt; yet.&lt;/p&gt;
&lt;p&gt;Actually, do not reach for it at all.&lt;/p&gt;
&lt;p&gt;Put this &lt;code&gt;Dockerfile&lt;/code&gt; into a source repository, preferably in a dedicated subdirectory. Later it will be much easier to tell what kind of creatures are being produced from it.&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;We are about to enable a tiny bit of magic. If you are not ready, perhaps take a short walk.&lt;/p&gt;
&lt;p&gt;Jenkins can wait. That is, after all, usually its main job.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;Create a &lt;code&gt;Jenkinsfile&lt;/code&gt; with the following contents:&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-groovy" data-lang="groovy"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pipeline &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; agent &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; kubernetes &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; yaml &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;apiVersion: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;kind: Pod
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; hostAliases:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - ip: &amp;#34;10.10.10.24&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; hostnames:
&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;registry.lab.local&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; containers:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - name: base
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; image: registry.lab.local/base-builder:0.1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; command: [&amp;#34;sleep&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; args: [&amp;#34;infinity&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - name: kaniko
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; image: registry.lab.local/kaniko-builder:latest
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; command: [&amp;#34;sleep&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; args: [&amp;#34;infinity&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; volumeMounts:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - name: docker-config
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; mountPath: /kaniko/.docker
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; volumes:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - name: docker-config
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; configMap:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; name: kaniko-docker-config
&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;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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;}&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; environment &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IMAGE_NAME &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;registry.lab.local/python-builder&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IMAGE_TAG &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;3.11.1&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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; stages &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stage&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;Prepare&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; container&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;base&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; script &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; env&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;GIT_SHA&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; sh&lt;span style="color:#f92672"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; script: &lt;span style="color:#e6db74"&gt;&amp;#39;git rev-parse --short HEAD&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; returnStdout: &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;).&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;trim&lt;/span&gt;&lt;span style="color:#f92672"&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; currentBuild&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;displayName&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;#${env.BUILD_NUMBER} python-3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; currentBuild&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;description&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;python-builder:3, commit ${env.GIT_SHA}&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;}&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; sh &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; echo &amp;#34;IMAGE=${IMAGE_NAME}:${IMAGE_TAG}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; ls -la
&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;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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; stage&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;Build and push image&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; container&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;kaniko&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sh &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; /kaniko/executor \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --context &amp;#34;${WORKSPACE}&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --dockerfile &amp;#34;${WORKSPACE}/python-builder/Dockerfile&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --destination &amp;#34;${IMAGE_NAME}:${IMAGE_TAG}&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --insecure \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --skip-tls-verify \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --insecure-registry registry.lab.local
&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;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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;}&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; post &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; success &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;Built and pushed ${IMAGE_NAME}:${IMAGE_TAG}&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;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Commit it to the same repository, in the same subdirectory as the &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The cluster needs one additional &lt;strong&gt;ConfigMap&lt;/strong&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;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;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;ConfigMap&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;kaniko-docker-config&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;jenkins&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;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;config.json&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:#e6db74"&gt; {
&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;auths&amp;#34;: {}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; }&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;Jenkinsfile&lt;/code&gt; refers to this ConfigMap in the &lt;code&gt;agent&lt;/code&gt; section.&lt;/p&gt;
&lt;p&gt;Now create a new &lt;strong&gt;Multibranch Pipeline&lt;/strong&gt; job. Give it a sensible name, for example &lt;code&gt;python-builder&lt;/code&gt;, and configure it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;enter the repository URL,&lt;/li&gt;
&lt;li&gt;if credentials are required, select them, or add them first,&lt;/li&gt;
&lt;li&gt;under &lt;strong&gt;Build configuration&lt;/strong&gt; → &lt;strong&gt;Mode&lt;/strong&gt;, make sure &lt;strong&gt;by Jenkinsfile&lt;/strong&gt; is selected,&lt;/li&gt;
&lt;li&gt;in &lt;strong&gt;Script Path&lt;/strong&gt;, enter the path to the file, for example &lt;code&gt;python-builder/Jenkinsfile&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Save the job and wait a moment.&lt;/p&gt;
&lt;p&gt;Jenkins will start scanning branches in your repository, looking for the specified &lt;code&gt;Jenkinsfile&lt;/code&gt; in each of them. If it finds one, it will try to run the job for that branch.&lt;/p&gt;
&lt;p&gt;Once the &lt;strong&gt;Stage View&lt;/strong&gt; starts rendering, you can observe what is happening in the &lt;code&gt;jenkins&lt;/code&gt; namespace:&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;kubectl get pods -n jenkins -w
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A new Pod will appear, do its job, and leave, as in the example below:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME READY STATUS RESTARTS AGE
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;jenkins-0 1/1 Running 12 (57m ago) 19d
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python-builder-main-1-30xvv-l1cp7-vcw9m 0/3 Pending 0 0s
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python-builder-main-1-30xvv-l1cp7-vcw9m 0/3 ContainerCreating 0 0s
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python-builder-main-1-30xvv-l1cp7-vcw9m 3/3 Running 0 1s
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python-builder-main-1-30xvv-l1cp7-vcw9m 3/3 Terminating 0 63s
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python-builder-main-1-30xvv-l1cp7-vcw9m 0/3 Error 0 93s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The job completed successfully and the &lt;code&gt;python-builder&lt;/code&gt; image landed in the registry.&lt;/p&gt;
&lt;p&gt;You can verify that either through &lt;code&gt;registry-ui&lt;/code&gt; or 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-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl http://registry.lab.local/v2/python-builder/tags/list
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The response should look like this:&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:#f92672"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;:&lt;span style="color:#e6db74"&gt;&amp;#34;python-builder&amp;#34;&lt;/span&gt;,&lt;span style="color:#f92672"&gt;&amp;#34;tags&amp;#34;&lt;/span&gt;:[&lt;span style="color:#e6db74"&gt;&amp;#34;3.11.1&amp;#34;&lt;/span&gt;]}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The image is now ready to use.&lt;/p&gt;
&lt;h1 id="what-just-happened"&gt;What just happened?
&lt;/h1&gt;&lt;p&gt;The contents of the &lt;code&gt;Jenkinsfile&lt;/code&gt; definitely deserve an explanation, so let us start from the beginning.&lt;/p&gt;
&lt;h2 id="agent"&gt;&lt;code&gt;agent&lt;/code&gt;
&lt;/h2&gt;&lt;p&gt;The &lt;code&gt;agent&lt;/code&gt; section is without a doubt the most interesting part of this file.&lt;/p&gt;
&lt;p&gt;Besides the type name, &lt;code&gt;kubernetes&lt;/code&gt;, it contains a full Kubernetes Pod definition. In the specification we can see that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;hostAliases&lt;/code&gt; are used, because we do not have proper DNS here and still need to smuggle the registry address into the Pod,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;all important containers are listed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;base&lt;/code&gt;, which provides Git and basic shell tools,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kaniko&lt;/code&gt;, which provides&amp;hellip; well, kaniko.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keeping the names consistent is important.&lt;/p&gt;
&lt;p&gt;If you name the containers something like &lt;code&gt;the_one_with_git&lt;/code&gt; and &lt;code&gt;the_one_with_kaniko&lt;/code&gt;, you will have to use exactly those names later in the actual pipeline steps:&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-groovy" data-lang="groovy"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;stages &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stage&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;Prepare&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; container&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;the_one_with_git&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In short: Jenkins creates an agent described in the &lt;code&gt;agent&lt;/code&gt; section, and pipeline steps are executed inside specific containers from that Pod — the ones that provide the required tools.&lt;/p&gt;
&lt;p&gt;This is the important part.&lt;/p&gt;
&lt;p&gt;We are not installing build tools inside the main Jenkins server. The server only orchestrates work. The tools live in disposable containers, created only for the job that needs them.&lt;/p&gt;
&lt;p&gt;That keeps the Jenkins controller cleaner, makes builds easier to reproduce, and avoids the traditional CI server problem where one machine slowly turns into a museum of forgotten compilers, package managers, SDKs, symlinks and emotional damage.&lt;/p&gt;
&lt;h2 id="kaniko"&gt;&lt;code&gt;kaniko&lt;/code&gt;
&lt;/h2&gt;&lt;p&gt;Remember that &lt;code&gt;kaniko&lt;/code&gt; is not Docker.&lt;/p&gt;
&lt;p&gt;Its job is simply to build a container image and push it to a registry.&lt;/p&gt;
&lt;p&gt;The important parameters are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--context&lt;/code&gt; — the working directory, usually the job workspace,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--dockerfile&lt;/code&gt; — the location of the &lt;code&gt;Dockerfile&lt;/code&gt;, in this case inside a subdirectory,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--destination&lt;/code&gt; — the registry location, image name and tag to push after the build.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Simple? Simple.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;But wait!&amp;rdquo; you might shout. &amp;ldquo;What about that mysterious &lt;code&gt;kaniko-docker-config&lt;/code&gt; ConfigMap?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Here it is.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;kaniko&lt;/code&gt; likes to look inside &lt;code&gt;/kaniko/.docker&lt;/code&gt;, mainly for a &lt;code&gt;config.json&lt;/code&gt; file containing credentials, if they are required.&lt;/p&gt;
&lt;p&gt;Our registry does not require authentication, but we still mount a ConfigMap there and provide an empty &lt;code&gt;auths&lt;/code&gt; object:&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;auths&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This allows kaniko to try pushing the image anonymously right away.&lt;/p&gt;
&lt;h1 id="go-builder"&gt;Go builder
&lt;/h1&gt;&lt;p&gt;Not enough examples? Fine. Let us create a &lt;code&gt;go-builder&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Dockerfile&lt;/code&gt; could look like this:&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.26-bookworm&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:#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; apt-get update &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get install -y --no-install-recommends &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; git &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; openssh-client &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ca-certificates &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; curl &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wget &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jq &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; make &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; rsync &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;&amp;amp;&amp;amp;&lt;/span&gt; rm -rf /var/lib/apt/lists/*&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;RUN&lt;/span&gt; update-ca-certificates&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;USER&lt;/span&gt; &lt;span style="color:#e6db74"&gt;root&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:#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;/workspace&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:#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 version&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;CMD&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;sleep&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;infinity&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;And the &lt;code&gt;Jenkinsfile&lt;/code&gt; could look like this:&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-groovy" data-lang="groovy"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pipeline &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; agent &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; kubernetes &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; yaml &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;apiVersion: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;kind: Pod
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; hostAliases:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - ip: &amp;#34;10.10.10.24&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; hostnames:
&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;registry.lab.local&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; containers:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - name: base
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; image: registry.lab.local/base-builder:0.1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; command: [&amp;#34;sleep&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; args: [&amp;#34;infinity&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - name: kaniko
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; image: registry.lab.local/kaniko-builder:latest
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; command: [&amp;#34;sleep&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; args: [&amp;#34;infinity&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; volumeMounts:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - name: docker-config
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; mountPath: /kaniko/.docker
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; volumes:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - name: docker-config
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; configMap:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; name: kaniko-docker-config
&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;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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;}&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; environment &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IMAGE_NAME &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;registry.lab.local/go-builder&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IMAGE_TAG &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;1.26.1&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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; stages &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stage&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;Prepare&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; container&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;base&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; script &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; env&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;GIT_SHA&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; sh&lt;span style="color:#f92672"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; script: &lt;span style="color:#e6db74"&gt;&amp;#39;git rev-parse --short HEAD&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; returnStdout: &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;).&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;trim&lt;/span&gt;&lt;span style="color:#f92672"&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; currentBuild&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;displayName&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;#${env.BUILD_NUMBER} go-${env.IMAGE_TAG}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; currentBuild&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;description&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;go-builder:${env.IMAGE_TAG}, commit ${env.GIT_SHA}&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;}&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; sh &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; echo &amp;#34;GO_VERSION=${IMAGE_TAG}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; echo &amp;#34;IMAGE=${IMAGE_NAME}:${IMAGE_TAG}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; ls -la
&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;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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; stage&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;Build and push image&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; container&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;kaniko&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sh &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; /kaniko/executor \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --context &amp;#34;${WORKSPACE}&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --dockerfile &amp;#34;${WORKSPACE}/go-builder/Dockerfile&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --destination &amp;#34;${IMAGE_NAME}:${IMAGE_TAG}&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --insecure \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --skip-tls-verify \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --insecure-registry registry.lab.local
&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;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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;}&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; post &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; success &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;Built and pushed ${IMAGE_NAME}:${IMAGE_TAG}&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;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Yes, you do not need to rub your eyes in disbelief.&lt;/p&gt;
&lt;p&gt;This &lt;code&gt;Jenkinsfile&lt;/code&gt; looks almost identical to the previous one. The agent definition is the same, the &lt;code&gt;kaniko&lt;/code&gt; invocation is the same, and the base image usage is the same. In practice, only names, version numbers and file paths change.&lt;/p&gt;
&lt;p&gt;Simple? Simple.&lt;/p&gt;
&lt;h1 id="example-use-case"&gt;Example use case
&lt;/h1&gt;&lt;p&gt;If you have a Go application lying around, you can use a &lt;code&gt;Jenkinsfile&lt;/code&gt; like this one.&lt;/p&gt;
&lt;p&gt;In this example, the test application is called &lt;code&gt;rme&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-groovy" data-lang="groovy"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pipeline &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; agent &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; kubernetes &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; yaml &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;apiVersion: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;kind: Pod
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; hostAliases:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - ip: &amp;#34;10.10.10.24&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; hostnames:
&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;registry.lab.local&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; containers:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - name: rme-go-builder
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; image: registry.lab.local/go-builder:1.26.1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; command: [&amp;#34;sleep&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; args: [&amp;#34;infinity&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - name: rme-kaniko
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; image: registry.lab.local/kaniko-builder:latest
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; command: [&amp;#34;sleep&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; args: [&amp;#34;infinity&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; volumeMounts:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - name: docker-config
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; mountPath: /kaniko/.docker
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; volumes:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - name: docker-config
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; configMap:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; name: kaniko-docker-config
&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;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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;}&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; environment &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IMAGE_NAME &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;registry.lab.local/rme&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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; stages &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stage&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;Prepare&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; container&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;rme-go-builder&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; script &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; env&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;VERSION&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; env&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;GIT_COMMIT&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;take&lt;/span&gt;&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;&lt;span style="color:#f92672"&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; currentBuild&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;displayName&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;#${env.BUILD_NUMBER} ${env.VERSION}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; currentBuild&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;description&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;${env.IMAGE_NAME}:${env.VERSION}&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;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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; stage&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;Build binary&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; container&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;rme-go-builder&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sh &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; git config --global --add safe.directory &amp;#34;$WORKSPACE&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; CGO_ENABLED=0 GOOS=linux go build -o rme .
&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;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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; stage&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;Build and push image&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; container&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;rme-kaniko&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sh &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; /kaniko/executor \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --context &amp;#34;${WORKSPACE}&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --dockerfile &amp;#34;${WORKSPACE}/Dockerfile&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --destination &amp;#34;${IMAGE_NAME}:${VERSION}&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --destination &amp;#34;${IMAGE_NAME}:latest&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --insecure \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --skip-tls-verify \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --insecure-registry registry.lab.local
&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;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The application &lt;code&gt;Dockerfile&lt;/code&gt; can be very small:&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;scratch&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:#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; rme /rme&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;EXPOSE&lt;/span&gt; &lt;span style="color:#e6db74"&gt;9101&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:#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;/rme&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;This is a more realistic example.&lt;/p&gt;
&lt;p&gt;It uses a dedicated builder image, &lt;code&gt;go-builder&lt;/code&gt;, to compile the Go application, and &lt;code&gt;kaniko&lt;/code&gt; only for building and pushing the final container image.&lt;/p&gt;
&lt;p&gt;A few details are worth noticing, because the devil usually lives exactly there:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;container names matter, because they are later used in &lt;code&gt;container(...)&lt;/code&gt; blocks,&lt;/li&gt;
&lt;li&gt;the second &lt;code&gt;--destination&lt;/code&gt; in the &lt;code&gt;kaniko&lt;/code&gt; invocation:&lt;/li&gt;
&lt;/ul&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;--destination &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;IMAGE_NAME&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;:latest&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;is intentional.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;latest&lt;/code&gt; tag will not magically appear in our registry by itself. If we want the most recently pushed image to also be available as &lt;code&gt;latest&lt;/code&gt;, we need to tag and push it that way.&lt;/p&gt;
&lt;h1 id="summary-and-notes"&gt;Summary and notes
&lt;/h1&gt;&lt;p&gt;As you can see, there is less and less that we strictly &lt;strong&gt;must&lt;/strong&gt; do inside the cluster, and more and more that we &lt;strong&gt;can&lt;/strong&gt; do in a controlled and repeatable way.&lt;/p&gt;
&lt;p&gt;Versioned configuration, job history, artifact history, example applications, builds running inside containers — quite a lot is happening here.&lt;/p&gt;
&lt;p&gt;Why use a &lt;strong&gt;Multibranch Pipeline&lt;/strong&gt;?&lt;/p&gt;
&lt;p&gt;Good question, but the answers are fairly obvious:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;we do not have to clone the repository manually or hard-code credentials in the &lt;code&gt;Jenkinsfile&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;test branches do not require separate Jenkins jobs,&lt;/li&gt;
&lt;li&gt;Jenkins detects new branches automatically,&lt;/li&gt;
&lt;li&gt;deleted branches can disappear from Jenkins automatically as well,&lt;/li&gt;
&lt;li&gt;each branch can have its own version of the &lt;code&gt;Jenkinsfile&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;pull requests are handled in a much cleaner way.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;And now a suspicious thought appears.&lt;/p&gt;
&lt;p&gt;What if we put all those YAML files under version control and let some clever mechanism take care of them automatically, instead of calling &lt;code&gt;kubectl&lt;/code&gt; by hand every few minutes?&lt;/p&gt;
&lt;p&gt;Could such a thing exist?&lt;/p&gt;
&lt;p&gt;Who knows.&lt;/p&gt;</description></item><item><title>Jenkins in Kubernetes: Bring a Bucket</title><link>https://dev.iostream.pl/posts/2026-07-11/</link><pubDate>Sat, 11 Jul 2026 11:10:00 +0200</pubDate><guid>https://dev.iostream.pl/posts/2026-07-11/</guid><description>
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; working with Jenkins requires a lot of determination. It is also wise to prepare a bucket in advance — for blood, sweat, tears, and all the failed pipelines.&lt;/p&gt;
&lt;p&gt;There. I warned you.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h1 id="why-jenkins"&gt;Why Jenkins?
&lt;/h1&gt;&lt;p&gt;Jenkins has accumulated enough myths and urban legends to deserve several dedicated episodes of a good television show about mysterious phenomena and conspiracy theories.&lt;/p&gt;
&lt;p&gt;For now, let us focus on a few practical facts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Jenkins is a true Swiss Army knife in the CI/CD world.&lt;/li&gt;
&lt;li&gt;Jenkins is worth knowing, because it may be lurking just around the corner in the organization you are about to join.&lt;/li&gt;
&lt;li&gt;Jenkins is easy to run both locally and centrally.&lt;/li&gt;
&lt;li&gt;Thanks to its many plugins, Jenkins can probably be used for almost anything.&lt;/li&gt;
&lt;li&gt;You do not have to spend your life clicking around the UI. Jenkins Pipelines do their job.&lt;/li&gt;
&lt;li&gt;It is worth getting familiar with Jenkins if only to form your own opinion about what you actually need from a CI/CD ecosystem.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="a-pinch-of-history"&gt;A pinch of history
&lt;/h1&gt;&lt;p&gt;Jenkins originally started as a tool for building Java code, wrapped in a convenient web service available to everyone.&lt;/p&gt;
&lt;p&gt;At first it was called &lt;strong&gt;Hudson&lt;/strong&gt;, and perhaps it would still be called that today, but Sun Microsystems — where Hudson was created — was acquired by the large and not particularly cuddly company known for large and not particularly agile databases.&lt;/p&gt;
&lt;p&gt;Then came a dispute over the name and project ownership. A fork appeared, someone proposed a new name, and off it went.&lt;/p&gt;
&lt;p&gt;If this reminds you of MariaDB, congratulations: your pattern recognition module is working correctly.&lt;/p&gt;
&lt;h1 id="jenkins-in-a-kubernetes-cluster"&gt;Jenkins in a Kubernetes cluster
&lt;/h1&gt;&lt;p&gt;We will use Jenkins not only to build containers, but also to build regular applications and run ordinary tasks using purpose-built containers.&lt;/p&gt;
&lt;h2 id="requirements"&gt;Requirements
&lt;/h2&gt;&lt;p&gt;Jenkins stores data on disk, so we will need a PersistentVolumeClaim.&lt;/p&gt;
&lt;p&gt;From the cluster&amp;rsquo;s point of view, the main Jenkins server will run as a StatefulSet. An Ingress controller will provide HTTP(S) access through the address &lt;code&gt;jenkins.lab.local&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As usual in this lab, add the following line to &lt;code&gt;/etc/hosts&lt;/code&gt; on every machine that needs to access Jenkins, including your workstation:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;10.10.10.24 jenkins.lab.local
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-image"&gt;The image
&lt;/h2&gt;&lt;p&gt;You can use an existing Jenkins image, for example the latest LTS image.&lt;/p&gt;
&lt;p&gt;I do not recommend doing that here.&lt;/p&gt;
&lt;p&gt;The bucket for blood, sweat and tears would need to be emptied far too often, with very little benefit. It is better to commit a custom image yourself — or in pairs, because at some point the basement nerds need someone to hold the light bulb.&lt;/p&gt;
&lt;h2 id="dockerfile"&gt;Dockerfile
&lt;/h2&gt;&lt;p&gt;The &lt;code&gt;Dockerfile&lt;/code&gt; looks like this:&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;jenkins/jenkins:lts-jdk21&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; &lt;span style="color:#e6db74"&gt;jenkins-tools&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:#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;eclipse-temurin:21-jre-jammy&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:#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;ARG&lt;/span&gt; JENKINS_VERSION&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;.555.3&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;USER&lt;/span&gt; &lt;span style="color:#e6db74"&gt;root&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:#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; apt-get update &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get install -y --no-install-recommends &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; curl &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; git &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; openssh-client &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ca-certificates &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tini &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; fontconfig &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jq &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; unzip &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; zip &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;&amp;amp;&amp;amp;&lt;/span&gt; rm -rf /var/lib/apt/lists/*&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;RUN&lt;/span&gt; useradd -m -d /var/jenkins_home -u &lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt; jenkins&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;RUN&lt;/span&gt; mkdir -p /usr/share/jenkins /var/jenkins_home /usr/share/jenkins/ref/plugins&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;RUN&lt;/span&gt; curl -fsSL &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -o /usr/share/jenkins/jenkins.war &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:#e6db74"&gt;&amp;#34;https://get.jenkins.io/war-stable/&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;JENKINS_VERSION&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/jenkins.war&amp;#34;&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:#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;jenkins-tools /usr/bin/jenkins-plugin-cli /usr/bin/jenkins-plugin-cli&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;jenkins-tools /opt/jenkins-plugin-manager.jar /opt/jenkins-plugin-manager.jar&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;COPY&lt;/span&gt; plugins.txt /tmp/plugins.txt&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;RUN&lt;/span&gt; JENKINS_WAR&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/usr/share/jenkins/jenkins.war &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; JENKINS_HOME&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/usr/share/jenkins/ref &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jenkins-plugin-cli &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --war /usr/share/jenkins/jenkins.war &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --plugin-file /tmp/plugins.txt&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;RUN&lt;/span&gt; chown -R jenkins:jenkins /usr/share/jenkins /var/jenkins_home&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;USER&lt;/span&gt; &lt;span style="color:#e6db74"&gt;jenkins&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:#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;ENV&lt;/span&gt; JENKINS_HOME&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/var/jenkins_home&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;VOLUME&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/var/jenkins_home&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:#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:#ae81ff"&gt;50000&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:#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;/usr/bin/tini&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;--&amp;#34;&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;CMD&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;java&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;-Djenkins.install.runSetupWizard=false&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;-jar&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;/usr/share/jenkins/jenkins.war&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;--httpPort=8080&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;You can put plugins in &lt;code&gt;plugins.txt&lt;/code&gt; and install them directly into the image.&lt;/p&gt;
&lt;p&gt;You can also install them later, because Jenkins will have persistent storage. Whether this is a convenience or the beginning of a future archaeology project depends on your level of optimism.&lt;/p&gt;
&lt;h1 id="building-the-image"&gt;Building the image
&lt;/h1&gt;&lt;p&gt;The image is built for the &lt;code&gt;arm64&lt;/code&gt; architecture:&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 buildx build &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --platform linux/arm64 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -t registry.lab.local/jenkins-lab:0.0.1 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --push .
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The most likely result at this point is something like this:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; [stage-1 2/11] RUN apt-get update &amp;amp;&amp;amp; apt-get install -y --no-install-recommends curl git openssh-client ca-certificates tini fontconfig jq unzip zip &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;0.126 exec /bin/sh: exec format error
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Which means we need to help Docker understand how to run &lt;code&gt;arm64&lt;/code&gt; binaries:&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 --privileged --rm tonistiigi/binfmt --install arm64
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A successful installation should produce something like this:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Installing: arm64 OK
&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; &amp;#34;supported&amp;#34;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;linux/amd64&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;linux/amd64/v2&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;linux/amd64/v3&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;linux/arm64&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;linux/386&amp;#34;
&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; &amp;#34;emulators&amp;#34;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;python3.14&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;qemu-aarch64&amp;#34;
&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;Now we can build the image again:&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 buildx build &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --platform linux/arm64 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -t registry.lab.local/jenkins-lab:0.0.1 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --push .
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="cluster-objects"&gt;Cluster objects
&lt;/h1&gt;&lt;p&gt;For Jenkins to work properly, we need a few Kubernetes objects.&lt;/p&gt;
&lt;h2 id="namespace"&gt;Namespace
&lt;/h2&gt;&lt;p&gt;The simplest way to create the namespace is:&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;kubectl create namespace jenkins
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you keep your configuration under version control — and you should, because future you has enough problems already — use a manifest instead:&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&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;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;Namespace&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;jenkins&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="persistentvolumeclaim"&gt;PersistentVolumeClaim
&lt;/h2&gt;&lt;p&gt;The PVC definition looks like this:&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;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;PersistentVolumeClaim&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;jenkins-home&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;jenkins&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;accessModes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;ReadWriteOnce&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;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;storage&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;20Gi&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;storageClassName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;local-path&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="serviceaccount-and-role"&gt;ServiceAccount and Role
&lt;/h2&gt;&lt;p&gt;Jenkins needs permission to create and manage agent Pods in its namespace.&lt;/p&gt;
&lt;p&gt;The following definitions are enough for this lab:&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;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;ServiceAccount&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;jenkins&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;jenkins&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;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;rbac.authorization.k8s.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;Role&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;jenkins-agent-manager&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;jenkins&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;rules&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;apiGroups&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;resources&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;pods&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;pods/exec&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;pods/log&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;persistentvolumeclaims&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;verbs&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;get&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;list&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;watch&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;create&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;delete&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;patch&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;update&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;apiGroups&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;resources&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;secrets&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;configmaps&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;verbs&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;get&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;list&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;watch&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;apiGroups&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;resources&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;events&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;verbs&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;get&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;list&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;watch&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 style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;rbac.authorization.k8s.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;RoleBinding&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;jenkins-agent-manager&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;jenkins&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;subjects&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;ServiceAccount&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;jenkins&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;jenkins&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;roleRef&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;Role&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;jenkins-agent-manager&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;apiGroup&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;rbac.authorization.k8s.io&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="service-and-ingress"&gt;Service and Ingress
&lt;/h2&gt;&lt;p&gt;Now we need a Service and an Ingress:&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;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;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;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;jenkins&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;jenkins&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;selector&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 style="color:#ae81ff"&gt;jenkins&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:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;http&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;port&lt;/span&gt;: &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;targetPort&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;http&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;agent&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;port&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;50000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;targetPort&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;agent&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;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;networking.k8s.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;Ingress&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;jenkins&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;jenkins&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;ingressClassName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;nginx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;rules&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;jenkins.lab.local&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;http&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;paths&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;path&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;pathType&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Prefix&lt;/span&gt;
&lt;/span&gt;&lt;/span&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;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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;jenkins&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;port&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;http&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="statefulset"&gt;StatefulSet
&lt;/h2&gt;&lt;p&gt;Finally, the StatefulSet:&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;StatefulSet&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;jenkins&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;jenkins&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;serviceName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;jenkins&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;replicas&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1&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;selector&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;matchLabels&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 style="color:#ae81ff"&gt;jenkins&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;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;labels&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 style="color:#ae81ff"&gt;jenkins&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;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;serviceAccountName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;jenkins&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;nodeSelector&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;przeznaczenie&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ci-cd&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;jenkins-home&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;persistentVolumeClaim&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;claimName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;jenkins-home&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;containers&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;jenkins&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;registry.lab.local/jenkins-lab:0.0.1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;imagePullPolicy&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Always&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;ports&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;http&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;containerPort&lt;/span&gt;: &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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;agent&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;containerPort&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;50000&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;env&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;JENKINS_HOME&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;value&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/var/jenkins_home&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;JAVA_OPTS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;value&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;-Djenkins.install.runSetupWizard=false&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 style="color:#f92672"&gt;volumeMounts&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;jenkins-home&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;mountPath&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/var/jenkins_home&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;startupProbe&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;httpGet&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;path&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/login&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;port&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;http&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;periodSeconds&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;failureThreshold&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;60&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;readinessProbe&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;httpGet&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;path&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/login&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;port&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;http&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;periodSeconds&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;failureThreshold&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;12&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;livenessProbe&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;httpGet&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;path&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/login&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;port&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;http&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;periodSeconds&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;failureThreshold&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;blockquote&gt;
 &lt;p&gt;Save all of the above definitions as YAML files and apply them with &lt;code&gt;kubectl&lt;/code&gt;.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h1 id="first-start"&gt;First start
&lt;/h1&gt;&lt;p&gt;Jenkins should start and greet us by asking for the initial token.&lt;/p&gt;
&lt;p&gt;That token can be found in the depths of its home directory on the PVC. After that, it is the usual Jenkins ritual: administrator account, password, pending plugins, and all the other traditional rites of passage.&lt;/p&gt;
&lt;p&gt;A freshly started Jenkins is a strange creature.&lt;/p&gt;
&lt;p&gt;On one hand, it can be armed to the teeth with plugins. On the other hand, it is as naked as a jaybird. It does not have any build tools installed — but that is fine. We do not really need them.&lt;/p&gt;
&lt;p&gt;The actual work will be done by purpose-built containers, created only when needed.&lt;/p&gt;
&lt;h1 id="configuring-the-kubernetes-cloud"&gt;Configuring the Kubernetes cloud
&lt;/h1&gt;&lt;p&gt;This is where the Jenkins &lt;strong&gt;Cloud&lt;/strong&gt; configuration comes in.&lt;/p&gt;
&lt;p&gt;Configure it like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Manage Jenkins&lt;/strong&gt; → &lt;strong&gt;Clouds&lt;/strong&gt; → &lt;strong&gt;Add a new cloud&lt;/strong&gt; → &lt;strong&gt;Kubernetes&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Name:&lt;/strong&gt; &lt;code&gt;kubernetes&lt;/code&gt;, or any other name you prefer&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes URL:&lt;/strong&gt; &lt;code&gt;https://kubernetes.default.svc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes namespace:&lt;/strong&gt; &lt;code&gt;jenkins&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Enable &lt;strong&gt;WebSocket&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Jenkins URL:&lt;/strong&gt; &lt;code&gt;http://jenkins.jenkins.svc.cluster.local:8080&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Save the configuration.&lt;/p&gt;
&lt;p&gt;Now all that remains is to create a few useful images, wire them into Jenkins pipelines, and then use them, use them, and use them some more.&lt;/p&gt;
&lt;h1 id="base-builder"&gt;Base builder
&lt;/h1&gt;&lt;p&gt;The first image will be&amp;hellip; a base image for building other, better base images.&lt;/p&gt;
&lt;p&gt;What does that mean?&lt;/p&gt;
&lt;p&gt;The plan is for Jenkins to build many different applications, and also run jobs that may not build anything at all, but still need containers with specific tools inside.&lt;/p&gt;
&lt;p&gt;There are two ways to approach this problem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;create one large image with every possible tool inside,&lt;/li&gt;
&lt;li&gt;create many small, specialized images and use each one only when needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The second approach is easier to maintain.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Dockerfile&lt;/code&gt; for the &lt;strong&gt;base builder&lt;/strong&gt; looks roughly like this:&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;debian:bookworm-slim&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:#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; apt-get update &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get install -y --no-install-recommends &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ca-certificates &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; curl &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wget &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; git &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; openssh-client &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bash &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; make &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jq &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; yq &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tar &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; gzip &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; xz-utils &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; unzip &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; zip &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; rsync &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; file &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; procps &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; iputils-ping &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dnsutils &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;&amp;amp;&amp;amp;&lt;/span&gt; rm -rf /var/lib/apt/lists/*&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;RUN&lt;/span&gt; useradd -m -u &lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt; builder&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;USER&lt;/span&gt; &lt;span style="color:#e6db74"&gt;builder&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;/workspace&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:#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;CMD&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;sleep&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;infinity&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;Build it like this:&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 buildx build &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --platform linux/arm64 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -t registry.lab.local/base-builder:0.1 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --push .
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="kaniko-builder"&gt;Kaniko builder
&lt;/h1&gt;&lt;p&gt;The second image will contain &lt;strong&gt;kaniko&lt;/strong&gt;, mainly for building container images.&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; this is a lab setup. Kaniko works well enough for our purposes here, but before using it in production, check the current state of the project and consider alternatives such as Buildah or Podman. The CI/CD ecosystem moves quickly, usually while pretending it is doing so in a controlled manner.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;Yes, kaniko is simpler to use than Docker in this setup. It does not require root privileges and it does not need a Docker daemon.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Dockerfile&lt;/code&gt; can look like this:&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;gcr.io/kaniko-project/executor:latest&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; &lt;span style="color:#e6db74"&gt;kaniko&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:#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.21&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:#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; apk add --no-cache &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ca-certificates &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bash &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; coreutils &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; curl &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; git&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;COPY&lt;/span&gt; --from&lt;span style="color:#f92672"&gt;=&lt;/span&gt;kaniko /kaniko /kaniko&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;ENV&lt;/span&gt; PATH&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/kaniko:&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;PATH&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&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:#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;/workspace&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:#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;CMD&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;sleep&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;infinity&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;Build it like this:&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 buildx build &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --platform linux/arm64 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -t registry.lab.local/kaniko-builder:0.1 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --push .
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These two images need to be built manually.&lt;/p&gt;
&lt;p&gt;All the other ones will be created later by Jenkins itself.&lt;/p&gt;
&lt;p&gt;If you managed to get here in one piece, that is already quite good. In the next episode, we will learn how to make actual use of our newly assembled toys.&lt;/p&gt;</description></item><item><title>2026-06-30</title><link>https://dev.iostream.pl/posts/2026-06-30/</link><pubDate>Tue, 30 Jun 2026 19:10:00 +0200</pubDate><guid>https://dev.iostream.pl/posts/2026-06-30/</guid><description>&lt;h1 id="suit-up-and-march-on"&gt;Suit Up and March On!
&lt;/h1&gt;&lt;h2 id="what-is-helm"&gt;What is Helm?
&lt;/h2&gt;&lt;p&gt;Kubernetes is popular enough that it was only a matter of time before someone came up with a way to install applications using one or two commands instead of manually assembling yet another small mountain of YAML files.&lt;/p&gt;
&lt;p&gt;That tool is called &lt;strong&gt;Helm&lt;/strong&gt;. It helps define, install, upgrade and manage applications running inside a Kubernetes cluster.&lt;/p&gt;
&lt;p&gt;Think of it as the Kubernetes equivalent of &lt;code&gt;apt&lt;/code&gt;, &lt;code&gt;dnf&lt;/code&gt; or &lt;code&gt;brew&lt;/code&gt; — except instead of installing packages on a machine, it installs applications into your cluster.&lt;/p&gt;
&lt;h2 id="how-does-it-work"&gt;How does it work?
&lt;/h2&gt;&lt;p&gt;Helm introduces three basic concepts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Chart&lt;/strong&gt; — a package containing templates for all Kubernetes manifests required by an application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Values&lt;/strong&gt; — parameters used to customize those templates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Release&lt;/strong&gt; — a deployed instance of a chart inside a cluster. The first installation creates the initial revision, and every upgrade creates another one, making rollbacks straightforward.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In practice, you take a chart, adjust its values, install it into the cluster and get a named release that Helm can later upgrade, list, inspect or remove.&lt;/p&gt;
&lt;h2 id="what-do-we-need"&gt;What do we need?
&lt;/h2&gt;&lt;p&gt;Not much:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a Kubernetes cluster,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubectl&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;helm&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our first application will be &lt;strong&gt;Headlamp&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Kubernetes Dashboard still exists, but these days Headlamp is often the more pleasant choice, so that is what we will use here.&lt;/p&gt;
&lt;p&gt;Headlamp is one of those rare applications that you install once and it simply works. Such things deserve appreciation.&lt;/p&gt;
&lt;h2 id="installing-applications-with-helm"&gt;Installing applications with Helm
&lt;/h2&gt;&lt;p&gt;Most applications are distributed through Helm repositories.&lt;/p&gt;
&lt;p&gt;Adding one is simple:&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;helm repo add repository-name https://some.repo.url
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm repo update
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm install my-application repository-name/chart
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;repository-name&lt;/code&gt; is your local alias for the repository.&lt;/p&gt;
&lt;p&gt;Likewise, &lt;code&gt;my-application&lt;/code&gt; is the name of your release. It may look redundant at first, but imagine testing three different MariaDB installations from three different sources. Suddenly meaningful release names start looking less like ceremony and more like self-defense.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;helm repo update&lt;/code&gt; works much like &lt;code&gt;apt update&lt;/code&gt;: it refreshes the local cache with information about available chart versions.&lt;/p&gt;
&lt;p&gt;You can also search through charts available in your configured repositories:&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;helm search repo headlamp
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm search repo bitnami
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="where-do-the-values-come-from"&gt;Where do the values come from?
&lt;/h2&gt;&lt;p&gt;The easiest way is to ask Helm for the default configuration:&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;helm show values repository-name/chart &amp;gt; values.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then edit the generated file and adjust it to your environment.&lt;/p&gt;
&lt;p&gt;For Headlamp, the following &lt;code&gt;values.yaml&lt;/code&gt; is enough for this lab:&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;config&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;inCluster&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:#f92672"&gt;ingress&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;enabled&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;ingressClassName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;nginx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;hosts&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;headlamp.lab.local&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;paths&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;path&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;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Prefix&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;nodeSelector&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;przeznaczenie&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;observability&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We need to define the Ingress class, the hostname under which the application will be available, and optionally a &lt;code&gt;nodeSelector&lt;/code&gt; if we want to control where the Pod runs.&lt;/p&gt;
&lt;p&gt;As usual in this lab setup, we also need to add the hostname to &lt;code&gt;/etc/hosts&lt;/code&gt; on every machine that will access the service, including the workstation:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;10.10.10.24 headlamp.lab.local
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="installing-headlamp"&gt;Installing Headlamp
&lt;/h2&gt;&lt;p&gt;Now comes the easy part:&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;helm repo add headlamp https://kubernetes-sigs.github.io/headlamp/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm repo update
&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;helm install headlamp headlamp/headlamp &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --namespace headlamp &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --create-namespace &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -f values.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After opening the URL in a browser, we get a friendly login screen:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Login screen" class="gallery-image" data-flex-basis="310px" data-flex-grow="129" height="376" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://dev.iostream.pl/posts/2026-06-30/headlamp-1.png" width="487"&gt;&lt;/p&gt;
&lt;p&gt;Headlamp asks for a Kubernetes authentication token.&lt;/p&gt;
&lt;p&gt;Generating one is straightforward:&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;kubectl -n headlamp create token headlamp
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The application itself is clean, tasteful and not overloaded with options. Most things are exactly where you expect them to be.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Headlamp UI" class="gallery-image" data-flex-basis="451px" data-flex-grow="188" height="916" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://dev.iostream.pl/posts/2026-06-30/headlamp-2.png" srcset="https://dev.iostream.pl/posts/2026-06-30/headlamp-2_hu_3c665b46754731f4.png 800w, https://dev.iostream.pl/posts/2026-06-30/headlamp-2_hu_251e384dec7fcfd6.png 1600w, https://dev.iostream.pl/posts/2026-06-30/headlamp-2.png 1725w" width="1725"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Headlamp UI" class="gallery-image" data-flex-basis="451px" data-flex-grow="188" height="916" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://dev.iostream.pl/posts/2026-06-30/headlamp-3.png" srcset="https://dev.iostream.pl/posts/2026-06-30/headlamp-3_hu_2be15f917b29e3f8.png 800w, https://dev.iostream.pl/posts/2026-06-30/headlamp-3_hu_163c51f0590c4ee4.png 1600w, https://dev.iostream.pl/posts/2026-06-30/headlamp-3.png 1725w" width="1725"&gt;&lt;/p&gt;
&lt;p&gt;You can browse resources, inspect logs and click through various settings without leaving the browser.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Headlamp UI" class="gallery-image" data-flex-basis="451px" data-flex-grow="188" height="912" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://dev.iostream.pl/posts/2026-06-30/headlamp-4.png" srcset="https://dev.iostream.pl/posts/2026-06-30/headlamp-4_hu_fac915c1e8f7d707.png 800w, https://dev.iostream.pl/posts/2026-06-30/headlamp-4_hu_f7d985a2f3d157cb.png 1600w, https://dev.iostream.pl/posts/2026-06-30/headlamp-4.png 1716w" width="1716"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Headlamp UI" class="gallery-image" data-flex-basis="451px" data-flex-grow="188" height="915" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://dev.iostream.pl/posts/2026-06-30/headlamp-5.png" srcset="https://dev.iostream.pl/posts/2026-06-30/headlamp-5_hu_c12026e6a6c5acb6.png 800w, https://dev.iostream.pl/posts/2026-06-30/headlamp-5_hu_614b5cdb15bee19d.png 1600w, https://dev.iostream.pl/posts/2026-06-30/headlamp-5.png 1722w" width="1722"&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;Another excellent Kubernetes client is &lt;strong&gt;k9s&lt;/strong&gt;. It runs entirely in the terminal, so you do not get fancy graphical topology views, but everything else is there — and it is fast.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="k9s" class="gallery-image" data-flex-basis="451px" data-flex-grow="188" height="949" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://dev.iostream.pl/posts/2026-06-30/k9s-1.png" srcset="https://dev.iostream.pl/posts/2026-06-30/k9s-1_hu_7f4b4c77f089e7c2.png 800w, https://dev.iostream.pl/posts/2026-06-30/k9s-1_hu_a6d23966b1a797a2.png 1600w, https://dev.iostream.pl/posts/2026-06-30/k9s-1.png 1786w" width="1786"&gt;&lt;/p&gt;
&lt;p&gt;As you can see, basic Helm usage is pleasantly simple.&lt;/p&gt;
&lt;h2 id="installing-databases"&gt;Installing databases
&lt;/h2&gt;&lt;p&gt;There is not much point in overexplaining databases here. We are going to install a few of them from a popular public repository and see how little work is actually required.&lt;/p&gt;
&lt;p&gt;We will use Bitnami&amp;rsquo;s repository:&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;helm repo add bitnami https://charts.bitnami.com/bitnami
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm repo update
&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;kubectl create namespace databases
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The separate namespace is not strictly magical, but it keeps things tidy.&lt;/p&gt;
&lt;h2 id="postgresql"&gt;PostgreSQL
&lt;/h2&gt;&lt;p&gt;Example &lt;code&gt;values.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;global&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;storageClass&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;local-path&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;architecture&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;standalone&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;postgresPassword&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;some-password&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;username&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;marcin&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;password&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;some-other-password&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;database&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;utensils&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 style="color:#f92672"&gt;primary&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;nodeSelector&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;przeznaczenie&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;heavy-worker&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;persistence&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;enabled&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;size&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;20Gi&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;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;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;NodePort&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:#f92672"&gt;postgresql&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5432&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;nodePorts&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;postgresql&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;30432&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The database will be exposed through NodePort &lt;strong&gt;30432&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Since this is not a web application, we need to remember which node actually hosts the Pod. Thanks to the &lt;code&gt;nodeSelector&lt;/code&gt;, that should be &lt;code&gt;10.10.10.25&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Installation:&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;helm install postgresql bitnami/postgresql &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --namespace databases &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -f values.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Afterwards Helm prints a few useful notes, including commands for retrieving generated credentials:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;To get the password for &amp;#34;postgres&amp;#34; run:
&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;export POSTGRES_ADMIN_PASSWORD=$(kubectl get secret --namespace databases postgresql -o jsonpath=&amp;#34;{.data.postgres-password}&amp;#34; | base64 -d)
&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;To get the password for &amp;#34;marcin&amp;#34; run:
&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;export POSTGRES_PASSWORD=$(kubectl get secret --namespace databases postgresql -o jsonpath=&amp;#34;{.data.password}&amp;#34; | base64 -d)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="mariadb"&gt;MariaDB
&lt;/h2&gt;&lt;p&gt;Example configuration:&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;global&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;storageClass&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;local-path&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;architecture&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;standalone&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;rootPassword&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;some-password&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;database&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;utensils&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;username&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;marcin&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;password&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;some-other-password&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 style="color:#f92672"&gt;primary&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;nodeSelector&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;przeznaczenie&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;heavy-worker&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;persistence&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;enabled&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;size&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;20Gi&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;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;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;NodePort&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:#f92672"&gt;mysql&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;3306&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;nodePorts&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;mysql&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;30306&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This database will listen on NodePort &lt;strong&gt;30306&lt;/strong&gt;, on the same node as PostgreSQL.&lt;/p&gt;
&lt;p&gt;Installation:&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;helm install mariadb bitnami/mariadb &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --namespace databases &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -f values.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Administrator credentials can be retrieved from the generated 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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Username: root
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Password: $(kubectl get secret --namespace databases mariadb -o jsonpath=&amp;#34;{.data.mariadb-root-password}&amp;#34; | base64 -d)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="redis"&gt;Redis
&lt;/h2&gt;&lt;p&gt;Example configuration:&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;architecture&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;standalone&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;enabled&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&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;master&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;nodeSelector&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;przeznaczenie&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;heavy-worker&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;persistence&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;enabled&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;storageClass&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;local-path&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;size&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5Gi&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;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;cpu&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;50m&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;128Mi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;limits&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;512Mi&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;metrics&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;enabled&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;serviceMonitor&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;enabled&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;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;databases&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Installation is exactly what you would expect:&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;helm install redis bitnami/redis &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --namespace databases &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -f values.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At this point the cluster is starting to look like an actual environment instead of an empty playground.&lt;/p&gt;
&lt;p&gt;As an exercise, I recommend installing a few well-known applications and checking how they are packaged, configured and exposed. There is no need to rediscover America when so many useful things are already available in public Helm repositories.&lt;/p&gt;
&lt;h2 id="updating-an-application"&gt;Updating an application
&lt;/h2&gt;&lt;p&gt;Sooner or later, you will change something in &lt;code&gt;values.yaml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead of uninstalling and installing the application again, upgrade the existing release:&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;helm upgrade headlamp headlamp/headlamp &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --namespace headlamp &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -f values.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Helm applies the new configuration while preserving release history. If something goes sideways, that history becomes useful very quickly.&lt;/p&gt;
&lt;h2 id="removing-applications"&gt;Removing applications
&lt;/h2&gt;&lt;p&gt;When an application outlives its usefulness — and why is it always Headlamp? — removing it is just as easy:&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;helm uninstall my-application
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If it was installed in a specific namespace, add it 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;helm uninstall my-application &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --namespace namespace-name
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="listing-installed-releases"&gt;Listing installed releases
&lt;/h2&gt;&lt;p&gt;This command shows releases from the current namespace:&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;helm list
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Which is technically correct and practically disappointing most of the time.&lt;/p&gt;
&lt;p&gt;Usually, this is what you want:&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;helm list -A
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It lists releases across all namespaces.&lt;/p&gt;
&lt;p&gt;And that is the nice thing about Helm: the basic workflow stays small.&lt;/p&gt;
&lt;p&gt;Add a repository, inspect the values, install a release, upgrade it when needed, list what is running, remove what is no longer useful. Underneath, Kubernetes still does Kubernetes things, but at least you do not have to hand-write every single manifest like it is some YAML-based monastic ritual.&lt;/p&gt;</description></item><item><title>2026-06-29</title><link>https://dev.iostream.pl/posts/2026-06-29/</link><pubDate>Mon, 29 Jun 2026 19:15:00 +0200</pubDate><guid>https://dev.iostream.pl/posts/2026-06-29/</guid><description>&lt;h1 id="buffer-overflow"&gt;Buffer Overflow
&lt;/h1&gt;&lt;p&gt;Today we begin in a slightly unusual place.&lt;/p&gt;
&lt;h2 id="interrupting-the-current-operation"&gt;Interrupting the Current Operation
&lt;/h2&gt;&lt;p&gt;If there is one key binding worth remembering before you even open your first file in Emacs, it is this one:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;C-g
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;While Emacs is executing some more complicated Lisp code, it may occasionally stop responding to user commands and give the impression that it has frozen.&lt;/p&gt;
&lt;p&gt;Nothing could be further from the truth.&lt;/p&gt;
&lt;p&gt;Emacs is not dead. It is merely busy executing that unfortunate piece of Lisp.&lt;/p&gt;
&lt;p&gt;There are two possible approaches:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;wait patiently until the Lisp interpreter finishes its work,&lt;/li&gt;
&lt;li&gt;or interrupt it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The latter is usually more practical.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;C-g&lt;/code&gt; interrupts the current operation. It works when Emacs is running a long command, waiting for minibuffer input, or otherwise pretending not to hear you.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;M-x keyboard-quit&lt;/code&gt; is the function bound to &lt;code&gt;C-g&lt;/code&gt; by default. Calling it manually is usually pointless: when Emacs is busy, you cannot invoke it, and when Emacs is not busy, there is nothing to interrupt. Still, the name is worth knowing if you ever decide to remap keys.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you forget everything else from this section, remember &lt;code&gt;C-g&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When something goes wrong, Emacs will usually listen.&lt;/p&gt;
&lt;p&gt;Lisp programmers are less predictable.&lt;/p&gt;
&lt;h1 id="what-does-this-key-do"&gt;What Does This Key Do?
&lt;/h1&gt;&lt;p&gt;Let us continue.&lt;/p&gt;
&lt;p&gt;Emacs contains an excellent built-in help system. One of its most useful features is the ability to ask:&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;What command is this key binding actually running?&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;To find out, use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;M-x describe-key&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;or, more conveniently, &lt;code&gt;C-h k&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Emacs will then ask you to press a key sequence, mouse button, or menu item.&lt;/p&gt;
&lt;p&gt;For example:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;C-h k C-p
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;opens a help buffer describing the command bound to &lt;code&gt;C-p&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="describe key" class="gallery-image" data-flex-basis="3381px" data-flex-grow="1408" height="45" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://dev.iostream.pl/posts/2026-06-29/emacs-1.png" width="634"&gt;&lt;/p&gt;
&lt;p&gt;A shortened version looks like this:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;C-p runs the command previous-line.
&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;It is bound to C-p and &amp;lt;up&amp;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;Move cursor vertically up ARG lines.
&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;This function is for interactive use only;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;in Lisp code use `forward-line&amp;#39; with negative argument instead.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Even this small fragment tells us quite a lot:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;which function is bound to the key,&lt;/li&gt;
&lt;li&gt;where the function comes from,&lt;/li&gt;
&lt;li&gt;which other keys invoke the same command,&lt;/li&gt;
&lt;li&gt;what the command does,&lt;/li&gt;
&lt;li&gt;whether it is meant for interactive use,&lt;/li&gt;
&lt;li&gt;and sometimes which related commands or variables are worth knowing about.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is one of Emacs&amp;rsquo; great strengths.&lt;/p&gt;
&lt;p&gt;The program is not merely configurable. It is also unusually good at explaining itself.&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;Bookmark:&lt;/strong&gt; if you have reached this point and started yawning, leave this article open. Tomorrow you will know exactly where to resume reading.&lt;/p&gt;
&lt;p&gt;You do not have to thank me.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h1 id="what-does-this-function-do"&gt;What Does This Function Do?
&lt;/h1&gt;&lt;p&gt;The same idea works in the other direction.&lt;/p&gt;
&lt;p&gt;If you know the name of a function and want to inspect it, use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;M-x describe-function&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;or simply &lt;code&gt;C-h f&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;C-h f find-file
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;opens a help buffer for the function used to visit files.&lt;/p&gt;
&lt;p&gt;&lt;img alt="describe function" class="gallery-image" data-flex-basis="1882px" data-flex-grow="784" height="32" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://dev.iostream.pl/posts/2026-06-29/emacs-2.png" width="251"&gt;&lt;/p&gt;
&lt;p&gt;A shortened fragment looks like this:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;find-file is an interactive function in `files.el`.
&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;It is bound to &amp;lt;open&amp;gt; and C-x C-f.
&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;Edit file FILENAME.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Switch to a buffer visiting file FILENAME, creating one if none already exists.
&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;You can visit files on remote machines by specifying something like:
&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;/ssh:SOME_REMOTE_MACHINE:FILE
&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;You can also visit local files as a different user by specifying:
&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;/sudo::FILE
&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;To visit a file without conversion and without automatically choosing
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;a major mode, use M-x find-file-literally.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is why reading Emacs help buffers is often surprisingly useful.&lt;/p&gt;
&lt;p&gt;Even a command as ordinary as &lt;code&gt;find-file&lt;/code&gt; turns out to do more than merely open files.&lt;/p&gt;
&lt;p&gt;Several useful details appear immediately:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Wildcards&lt;/strong&gt; — file name patterns can be expanded, allowing multiple files to be opened at once.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remote files&lt;/strong&gt; — paths such as &lt;code&gt;/ssh:some-host:some-file&lt;/code&gt; open files over SSH.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Files as another user&lt;/strong&gt; — paths such as &lt;code&gt;/sudo::some-file&lt;/code&gt; allow editing files with elevated privileges.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The last option is useful for editing configuration files that require administrative access.&lt;/p&gt;
&lt;p&gt;Other possible uses are left to the reader&amp;rsquo;s imagination and the local security department.&lt;/p&gt;
&lt;p&gt;At this point, Dear Reader, I assume you have already typed a single &lt;code&gt;*&lt;/code&gt; in some directory full of random files and discovered that Emacs can open rather more things than expected:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;images,&lt;/li&gt;
&lt;li&gt;PDFs,&lt;/li&gt;
&lt;li&gt;OpenOffice and LibreOffice documents,&lt;/li&gt;
&lt;li&gt;documents produced by that office suite which now apparently wishes to be known as a co-pilot,&lt;/li&gt;
&lt;li&gt;EPUB and MOBI files,&lt;/li&gt;
&lt;li&gt;archives,&lt;/li&gt;
&lt;li&gt;and many other formats.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It probably will not play your movies, but it can certainly inspect archives.&lt;/p&gt;
&lt;p&gt;What to do with all those joyfully opened files will be discussed a little later.&lt;/p&gt;
&lt;h1 id="buffers-and-frames"&gt;Buffers and Frames
&lt;/h1&gt;&lt;p&gt;Dividing the working area of a frame into two or more windows was invented mainly so that several buffers could be displayed side by side.&lt;/p&gt;
&lt;p&gt;Anyone who writes code probably understands the value of that immediately.&lt;/p&gt;
&lt;p&gt;The King of Editors can split a frame both horizontally and vertically. The resulting windows can be split again, although not forever. Once a window becomes too small, Emacs will politely refuse and display an appropriate message.&lt;/p&gt;
&lt;p&gt;Useful key bindings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;C-x 2&lt;/code&gt; — split the current window horizontally&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C-x 3&lt;/code&gt; — split the current window vertically&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C-x 1&lt;/code&gt; — delete all other windows and return to a single-window layout&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C-x 0&lt;/code&gt; — delete the current window&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C-x 5 2&lt;/code&gt; — create a new frame&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C-x 5 1&lt;/code&gt; — delete other frames&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C-x 5 0&lt;/code&gt; — delete the current frame&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C-x o&lt;/code&gt; — move focus to the next window&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C-x S-o&lt;/code&gt; — move focus to the previous window&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A very important detail:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;C-x 1&lt;/code&gt; removes window splits, but it does not kill any buffers. The buffers remain open; only one of them is currently visible.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Arbitrary frame splitting" class="gallery-image" data-flex-basis="222px" data-flex-grow="92" height="870" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://dev.iostream.pl/posts/2026-06-29/emacs-3.png" srcset="https://dev.iostream.pl/posts/2026-06-29/emacs-3_hu_a4277411b90ad2d4.png 800w, https://dev.iostream.pl/posts/2026-06-29/emacs-3.png 806w" width="806"&gt;&lt;/p&gt;
&lt;h1 id="working-with-multiple-buffers"&gt;Working with Multiple Buffers
&lt;/h1&gt;&lt;p&gt;Emacs can open as many buffers as available memory allows.&lt;/p&gt;
&lt;p&gt;Because of that, there is usually no point in starting another Emacs instance every time you want to open another file. When you need another file, open it in the same Emacs session.&lt;/p&gt;
&lt;p&gt;Emacs was designed for working with many buffers at once. Running several separate instances is possible, but in everyday use it is rarely necessary.&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;Yes, this is the &amp;ldquo;a little later&amp;rdquo; mentioned above.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;If a file is no longer needed, kill its buffer with:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;C-x k
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;By default, Emacs asks what to do with buffers that contain unsaved changes.&lt;/p&gt;
&lt;p&gt;You can move to the previous or next buffer with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;C-x Left&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C-x Right&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also switch directly to a named buffer using:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;C-x b
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If the buffer visits a file, its name is usually the same as the file name.&lt;/p&gt;
&lt;p&gt;If you do not remember the exact name, press &lt;code&gt;TAB&lt;/code&gt;. Emacs will display possible completions in a temporary window. You can also start typing and let completion narrow the list.&lt;/p&gt;
&lt;p&gt;Completion, selection, and minibuffer behavior are, of course, topics for another volume of the Emacs saga.&lt;/p&gt;
&lt;p&gt;To list all buffers, use:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;C-x C-b
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This opens a buffer containing the buffer list. You can move into that buffer and choose one of the listed entries.&lt;/p&gt;
&lt;p&gt;The buffer list does not automatically refresh every time a new buffer is created. This makes sense once you realize that the buffer list is itself just another buffer.&lt;/p&gt;
&lt;p&gt;Press:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;g
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;to refresh it.&lt;/p&gt;
&lt;p&gt;What else can be done inside the buffer list?&lt;/p&gt;
&lt;p&gt;As homework, press:&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-text" data-lang="text"&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;and read the help page.&lt;/p&gt;
&lt;h1 id="scrolling-another-window"&gt;Scrolling Another Window
&lt;/h1&gt;&lt;p&gt;One of the more useful Emacs features is the ability to scroll the contents of another window while continuing to work in the current one.&lt;/p&gt;
&lt;p&gt;No switching back and forth.&lt;/p&gt;
&lt;p&gt;No mouse.&lt;/p&gt;
&lt;p&gt;No unnecessary ceremony.&lt;/p&gt;
&lt;p&gt;If you are writing in one window and need to inspect something above or below in another window, Emacs has you covered.&lt;/p&gt;
&lt;p&gt;This is especially useful when both windows display different parts of the same file and you want to check what regrettable code you wrote earlier.&lt;/p&gt;
&lt;p&gt;Useful key bindings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;C-M-v&lt;/code&gt; — scroll the other window down&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C-M-S-v&lt;/code&gt; — scroll the other window up&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Alternative bindings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;M-PageDown&lt;/code&gt; — scroll the other window down&lt;/li&gt;
&lt;li&gt;&lt;code&gt;M-PageUp&lt;/code&gt; — scroll the other window up&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is one of those features that sounds minor until you start using it. After a few days, working without it feels unnecessarily clumsy.&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;A note that will age badly and is not really on topic:&lt;/strong&gt; at the moment, southern Poland is experiencing temperatures that feel entirely incompatible with human life. Some people of questionable judgment still insist that climate change does not exist, and that trains in the seventeenth century were also stuffy.&lt;/p&gt;
&lt;p&gt;Drink enough water.&lt;/p&gt;
&lt;p&gt;This is not a joke.&lt;/p&gt;
&lt;p&gt;Otherwise, the seventeenth-century train will arrive for you personally.&lt;/p&gt;

 &lt;/blockquote&gt;</description></item><item><title>2026-06-28</title><link>https://dev.iostream.pl/posts/2026-06-28/</link><pubDate>Sun, 28 Jun 2026 16:08:00 +0200</pubDate><guid>https://dev.iostream.pl/posts/2026-06-28/</guid><description>&lt;h1 id="listbox-anyone"&gt;Listbox, anyone?
&lt;/h1&gt;&lt;p&gt;Many popular UI toolkits have their listbox control implemented to be as simple in use as possible. The most common operations are: creating the control, adding the data rows, deleting and editing data, handling the selection. Almost all toolkits provide single functions or suitable methods to realize above operations.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Gtk+&lt;/code&gt; is quite different: it requires more abstract way of thinking and additional objects to be created, but the lists and trees are more flexible and elegant.&lt;/p&gt;
&lt;h1 id="storing-the-data"&gt;Storing the data
&lt;/h1&gt;&lt;p&gt;Before we can display a single string, let&amp;rsquo;s first earn a PhD in data models.&lt;/p&gt;
&lt;p&gt;Unlike the the other toolkits, the gtk+ list allows to display a few values per a row. Gtk+ list also distinguishes between the types of data, while other lists treat its row data as a single string.&lt;/p&gt;
&lt;p&gt;In fact, the &lt;code&gt;TreeView&lt;/code&gt; widget will be used to display elements. The model lets the display widget know about data types to be used in the control, it also determines the type of the control (if it should be a list or a tree). Besides, the model is used to store all the data (it is common to find a &amp;ldquo;store&amp;rdquo; term instead of &amp;ldquo;model&amp;rdquo; in various papers or in someone&amp;rsquo;s code)&lt;/p&gt;
&lt;p&gt;A store/model can be created by calling a proper constructor with at least one data type (to be used in the list) as argument:&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;store &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ListStore(type1, &lt;span style="color:#f92672"&gt;...&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Example:&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;store &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ListStore(int, str, str)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Created store will contain one integer value and two strings per row. Although pygtk reference recommends to use build-in GObject types (for instance, gobject.TYPE_INT and gobject.TYPE_STRING instead of int and str respectively), python built-in types are also allowed (GObject names really matter in C or C++)&lt;/p&gt;
&lt;h1 id="adding-the-data-rows"&gt;Adding the data rows
&lt;/h1&gt;&lt;p&gt;The method append from Gtk.ListStore class allows to append one row at the end of the store. Sequence with all values should be passed as a parameter:&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;store&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([val1, &lt;span style="color:#f92672"&gt;...&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Example:&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;store&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([&lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;str1&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;str2&amp;#34;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="columns-and-renderers"&gt;Columns and renderers
&lt;/h1&gt;&lt;p&gt;Control widget should know about columns it should display, their order and how to present data from the store. Gtk+ provides the column widget (Gtk.TreeViewColumn) to manage displaying the cells of data and renderer widgets (Gtk.CellRenderer and its derivatives) to realize rendering of particular cells.&lt;/p&gt;
&lt;p&gt;Column widget is usually created by calling a constructor of &lt;code&gt;Gtk.TreeViewColumn&lt;/code&gt; with a header title passed as the argument:&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;column &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;TreeViewColumn(title)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Example:&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;column &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;TreeViewColumn(&lt;span style="color:#e6db74"&gt;&amp;#34;Column header&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Column header is visible by default — passing an empty string to the constructor will not get rid of it.&lt;/p&gt;
&lt;p&gt;Cell renderer should be one of &lt;code&gt;Gtk.CellRendererAccel&lt;/code&gt;, &lt;code&gt;Gtk.CellRendererCombo&lt;/code&gt;, &lt;code&gt;Gtk.CellRendererPixbuf&lt;/code&gt;, &lt;code&gt;Gtk.CellRendererProgress&lt;/code&gt;, &lt;code&gt;Gtk.CellRendererSpin&lt;/code&gt;, &lt;code&gt;Gtk.CellRendererText&lt;/code&gt;, &lt;code&gt;Gtk.CellRendererToggle&lt;/code&gt;. The renderer object is usually created by calling the constructor with no parameters&lt;/p&gt;
&lt;p&gt;Example:&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;renderer &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CellRendererText()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Renderers should be added to the column widgets: pack_start and pack_end methods are used to do the packing.&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;column&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack_start(renderer)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;or&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;column&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack_end(renderer)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Example:&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;column1 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;TreeViewColumn(&lt;span style="color:#e6db74"&gt;&amp;#34;Column1&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;renderer1 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CellRendererText()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;renderer2 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CellRendererText()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;renderer3 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CellRendererText()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;column1&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack_start(renderer1)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;column1&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack_start(renderer2)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;column1&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack_end(renderer3)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The column widget must know which data column from the model should be connected to any of these renderers. This could be done by calling set_attributes method:&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;column&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_attributes(renderer, &lt;span style="color:#f92672"&gt;...&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;All optional parameters are &amp;ldquo;key = value&amp;rdquo; pairs, where key is the data type and value is number of column in the store&lt;/p&gt;
&lt;p&gt;Example:&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;column1&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_attributes(renderer1, text &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;column1&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_attributes(renderer2, text &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;column1&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_attributes(renderer3, text &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="treeview-control"&gt;TreeView control
&lt;/h1&gt;&lt;p&gt;The last step is to create a control and add all columns. Constructor of the control takes the store as first parameter:&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;listControl &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;TreeView(model&lt;span style="color:#f92672"&gt;=&lt;/span&gt;store)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The method append_column adds given column to the tree/list control:&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;listControl&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append_column(column)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Summary: at least, four objects should be created to have the tree/list control working: a model which determines types of data and stores all the rows, a renderer, which takes care of displaying the cells, a column, which glues together model columns and suitable renderers and a treeview control itself.&lt;/p&gt;
&lt;h1 id="working-examples"&gt;Working examples
&lt;/h1&gt;&lt;p&gt;Following examples illustrate the creation and displaying of the lists.&lt;/p&gt;
&lt;h2 id="very-simple-list-control"&gt;Very simple list control
&lt;/h2&gt;&lt;p&gt;The simplest list control contains only one column and displays only one value per row&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:#75715e"&gt;#!/usr/bin/env python3&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;import&lt;/span&gt; gi
&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;gi&lt;span style="color:#f92672"&gt;.&lt;/span&gt;require_version(&lt;span style="color:#e6db74"&gt;&amp;#34;Gtk&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;3.0&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;from&lt;/span&gt; gi.repository &lt;span style="color:#f92672"&gt;import&lt;/span&gt; Gtk
&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;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ListBoxViewer1&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;destroyCallback&lt;/span&gt;(self, widget, data&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;main_quit()
&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;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;__init__&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ListStore(str)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([&lt;span style="color:#e6db74"&gt;&amp;#34;String 1&amp;#34;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([&lt;span style="color:#e6db74"&gt;&amp;#34;String 2&amp;#34;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([&lt;span style="color:#e6db74"&gt;&amp;#34;String 3&amp;#34;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([&lt;span style="color:#e6db74"&gt;&amp;#34;String 4&amp;#34;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([&lt;span style="color:#e6db74"&gt;&amp;#34;String 5&amp;#34;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;TreeViewColumn(&lt;span style="color:#e6db74"&gt;&amp;#34;Sample column&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;textRenderer &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CellRendererText()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack_start(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;textRenderer, &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; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_attributes(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;textRenderer, text &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; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mainWindow &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Window(type&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;WindowType&lt;span style="color:#f92672"&gt;.&lt;/span&gt;TOPLEVEL)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mainWindow&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_title(&lt;span style="color:#e6db74"&gt;&amp;#39;ListBoxViewer1&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listBox &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;TreeView(model&lt;span style="color:#f92672"&gt;=&lt;/span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listBox&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append_column(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mainWindow&lt;span style="color:#f92672"&gt;.&lt;/span&gt;add(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listBox)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mainWindow&lt;span style="color:#f92672"&gt;.&lt;/span&gt;connect(&lt;span style="color:#e6db74"&gt;&amp;#39;delete_event&amp;#39;&lt;/span&gt;, self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;destroyCallback)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mainWindow&lt;span style="color:#f92672"&gt;.&lt;/span&gt;show_all()
&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;if&lt;/span&gt; __name__ &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lbv1 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; ListBoxViewer1()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;main()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This example covers the theory of creating listboxes with additional routines necessary to run minimal gtk+ program. Additional method is added to provide termination of this example when its window has been closed.&lt;/p&gt;
&lt;p&gt;All list-related routines are explained below:&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;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ListStore(str)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([&lt;span style="color:#e6db74"&gt;&amp;#34;String 1&amp;#34;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([&lt;span style="color:#e6db74"&gt;&amp;#34;String 2&amp;#34;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([&lt;span style="color:#e6db74"&gt;&amp;#34;String 3&amp;#34;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([&lt;span style="color:#e6db74"&gt;&amp;#34;String 4&amp;#34;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([&lt;span style="color:#e6db74"&gt;&amp;#34;String 5&amp;#34;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The model is created: it can hold one string value per a row. Then it is populated by 5 rows.&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;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;textRenderer &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CellRendererText()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Text renderer is created.&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;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;TreeViewColumn(&lt;span style="color:#e6db74"&gt;&amp;#34;Sample column&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack_start(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;textRenderer, &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;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_attributes(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;textRenderer, text &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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A column is created. Then the renderer is packed into the column, and the column is informed which column and what type of data from the store will be used.&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;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listBox &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;TreeView(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listBox&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append_column(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Main widget is created. The column is appended to the list.&lt;/p&gt;
&lt;p&gt;It looks like the following:&lt;/p&gt;
&lt;p&gt;&lt;img alt="ListboxViewer 1" class="gallery-image" data-flex-basis="224px" data-flex-grow="93" height="333" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://dev.iostream.pl/posts/2026-06-28/listbox-1.png" width="312"&gt;&lt;/p&gt;
&lt;p&gt;Explanation of the next examples contains only the routines related to list control; other routines did not change if not mentioned.&lt;/p&gt;
&lt;h2 id="two-values-per-row"&gt;Two values per row
&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-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ListStore(str, int)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([&lt;span style="color:#e6db74"&gt;&amp;#34;String 1&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;18000&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([&lt;span style="color:#e6db74"&gt;&amp;#34;String 2&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1500&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([&lt;span style="color:#e6db74"&gt;&amp;#34;String 3&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;900&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([&lt;span style="color:#e6db74"&gt;&amp;#34;String 4&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;60&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([&lt;span style="color:#e6db74"&gt;&amp;#34;String 5&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now the store holds a string an integer pair per a row.&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;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;TreeViewColumn(&lt;span style="color:#e6db74"&gt;&amp;#34;Sample column&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;textRenderer &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CellRendererText()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;numRenderer &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CellRendererText()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;numRenderer&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_property(&lt;span style="color:#e6db74"&gt;&amp;#39;xalign&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1.0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A column and two cell renderers are created. The set_property routine with xalign property to be set tells the renderer to align right the values in the row. Value of 0.0 means left alignment, 0.5 centers the text and 1.0 means right alignment.&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;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack_start(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;textRenderer, &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;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack_start(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;numRenderer, &lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_attributes(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;textRenderer, text &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;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_attributes(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;numRenderer, text &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The two renderers are packed into the column. The string value renderer will expand the cell if possible. Proper renderers are assigned to proper data columns from the store.&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;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listBox &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;TreeView(store&lt;span style="color:#f92672"&gt;=&lt;/span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listBox&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append_column(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The control is created and the column is appended to the control.&lt;/p&gt;
&lt;p&gt;The following picture shows the result of this example:&lt;/p&gt;
&lt;p&gt;&lt;img alt="ListboxViewer 2" class="gallery-image" data-flex-basis="265px" data-flex-grow="110" height="240" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://dev.iostream.pl/posts/2026-06-28/listbox-2.png" width="265"&gt;&lt;/p&gt;
&lt;h2 id="two-columns-in-the-control"&gt;Two columns in the control
&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-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn1 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;TreeViewColumn(&lt;span style="color:#e6db74"&gt;&amp;#34;Ascii text&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn2 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;TreeViewColumn(&lt;span style="color:#e6db74"&gt;&amp;#34;Numeric value&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;textRenderer &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CellRendererText()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;numRenderer &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CellRendererText()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;numRenderer&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_property(&lt;span style="color:#e6db74"&gt;&amp;#39;xalign&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1.0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn1&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack_start(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;textRenderer, &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;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack_start(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;numRenderer, &lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn1&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_attributes(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;textRenderer, text &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;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_attributes(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;numRenderer, text &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Every column packs one of the renderes. It looks like the following:&lt;/p&gt;
&lt;p&gt;&lt;img alt="ListboxViewer 3" class="gallery-image" data-flex-basis="222px" data-flex-grow="92" height="219" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://dev.iostream.pl/posts/2026-06-28/listbox-3.png" width="203"&gt;&lt;/p&gt;
&lt;h2 id="adding-pixmaps-to-the-list"&gt;Adding pixmaps to the list
&lt;/h2&gt;&lt;p&gt;Now, creating the store is like below:&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;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sheetIcon &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;gdk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pixbuf_new_from_file(&lt;span style="color:#e6db74"&gt;&amp;#34;sheet.xpm&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ListStore(str, int, Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;gdk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Pixbuf)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([&lt;span style="color:#e6db74"&gt;&amp;#34;String 1&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;18000&lt;/span&gt;, self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sheetIcon])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([&lt;span style="color:#e6db74"&gt;&amp;#34;String 2&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1500&lt;/span&gt;, self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sheetIcon])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([&lt;span style="color:#e6db74"&gt;&amp;#34;String 3&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;900&lt;/span&gt;, self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sheetIcon])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([&lt;span style="color:#e6db74"&gt;&amp;#34;String 4&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;60&lt;/span&gt;, self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sheetIcon])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listStore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append([&lt;span style="color:#e6db74"&gt;&amp;#34;String 5&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;, self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sheetIcon])
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The pixbuf is added in every row. Of course, it may be different in every row. This is only the example.&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;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;iconRenderer &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CellRendererPixbuf()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The icon requires a special kind of cell renderer.&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;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn1&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack_start(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;iconRenderer, &lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn1&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_attributes(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;iconRenderer, pixbuf &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pixbuf cell renderer is now packed into the first column together with the text renderer.&lt;/p&gt;
&lt;p&gt;It looks like the following:&lt;/p&gt;
&lt;p&gt;&lt;img alt="ListboxViewer 4" class="gallery-image" data-flex-basis="125px" data-flex-grow="52" height="465" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://dev.iostream.pl/posts/2026-06-28/listbox-4.png" width="244"&gt;&lt;/p&gt;
&lt;h2 id="display-the-same-values-twice-or-more-times"&gt;Display the same values twice or more times
&lt;/h2&gt;&lt;p&gt;Displaying more values per row is achieved by multiplicating the renderes:&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;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn1 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;TreeViewColumn(&lt;span style="color:#e6db74"&gt;&amp;#34;Ascii text&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn2 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;TreeViewColumn(&lt;span style="color:#e6db74"&gt;&amp;#34;Numeric value&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;textRenderer &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CellRendererText()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;textRenderer2 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CellRendererText()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;numRenderer &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CellRendererText()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;numRenderer2 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CellRendererText()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;numRenderer3 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CellRendererText()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;iconRenderer &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CellRendererPixbuf()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;iconRenderer2 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Gtk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CellRendererPixbuf()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;numRenderer&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_property(&lt;span style="color:#e6db74"&gt;&amp;#39;xalign&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1.0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;numRenderer2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_property(&lt;span style="color:#e6db74"&gt;&amp;#39;xalign&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0.0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;numRenderer2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_property(&lt;span style="color:#e6db74"&gt;&amp;#39;xalign&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0.5&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;textRenderer2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_property(&lt;span style="color:#e6db74"&gt;&amp;#39;xalign&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1.0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn1&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack_start(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;iconRenderer, &lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn1&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack_start(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;textRenderer, &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;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack_start(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;numRenderer, &lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack_start(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;numRenderer2, &lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack_start(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;iconRenderer2, &lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack_start(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;numRenderer3, &lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack_start(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;textRenderer2, &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;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn1&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_attributes(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;iconRenderer, pixbuf &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn1&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_attributes(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;textRenderer, text &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;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_attributes(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;numRenderer, text &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_attributes(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;numRenderer2, text &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_attributes(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;numRenderer3, text &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_attributes(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;textRenderer2, text &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;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;listColumn2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_attributes(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;iconRenderer2, pixbuf &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Also, the left, right and center alignment of text is shown.&lt;/p&gt;
&lt;p&gt;It looks like the following:&lt;/p&gt;
&lt;p&gt;&lt;img alt="ListboxViewer 5" class="gallery-image" data-flex-basis="209px" data-flex-grow="87" height="511" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://dev.iostream.pl/posts/2026-06-28/listbox-5.png" width="447"&gt;&lt;/p&gt;</description></item><item><title>2026-06-26</title><link>https://dev.iostream.pl/posts/2026-06-26/</link><pubDate>Fri, 26 Jun 2026 11:15:00 +0200</pubDate><guid>https://dev.iostream.pl/posts/2026-06-26/</guid><description>&lt;h1 id="why-do-we-need-a-registry"&gt;Why Do We Need a Registry?
&lt;/h1&gt;&lt;p&gt;Sooner or later, every Kubernetes cluster starts running applications that were built locally.&lt;/p&gt;
&lt;p&gt;At first, using Docker Hub seems perfectly reasonable.&lt;/p&gt;
&lt;p&gt;Then the first custom application appears.&lt;/p&gt;
&lt;p&gt;Then another.&lt;/p&gt;
&lt;p&gt;Eventually every deployment references images that exist only on your workstation.&lt;/p&gt;
&lt;p&gt;Clearly, this is not a sustainable solution.&lt;/p&gt;
&lt;p&gt;A private container registry solves this problem by providing a permanent home for your own images. Since the home lab already has plenty of storage available, hosting the registry locally is both the simplest and the fastest option.&lt;/p&gt;
&lt;h1 id="persistentvolume-and-persistentvolumeclaim"&gt;PersistentVolume and PersistentVolumeClaim
&lt;/h1&gt;&lt;p&gt;One of the original design goals of Kubernetes was that Pods should be ephemeral.&lt;/p&gt;
&lt;p&gt;Applications come and go.&lt;/p&gt;
&lt;p&gt;Containers are recreated.&lt;/p&gt;
&lt;p&gt;Nodes may disappear.&lt;/p&gt;
&lt;p&gt;Unfortunately, databases tend to have rather strong opinions about losing their contents.&lt;/p&gt;
&lt;p&gt;To bridge the gap between ephemeral workloads and persistent storage, Kubernetes introduced two API objects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PersistentVolume (PV)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PersistentVolumeClaim (PVC)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="persistentvolume"&gt;PersistentVolume
&lt;/h2&gt;&lt;p&gt;A &lt;strong&gt;PersistentVolume&lt;/strong&gt; represents an actual storage resource available inside the cluster.&lt;/p&gt;
&lt;p&gt;Unlike Pods, a PersistentVolume exists independently of any application using it. Deleting a Pod does not automatically remove the underlying storage.&lt;/p&gt;
&lt;p&gt;Think of it as the physical disk—or, more precisely, a piece of storage that Kubernetes knows how to manage.&lt;/p&gt;
&lt;h2 id="persistentvolumeclaim"&gt;PersistentVolumeClaim
&lt;/h2&gt;&lt;p&gt;A &lt;strong&gt;PersistentVolumeClaim&lt;/strong&gt; is exactly what its name suggests: a request for storage.&lt;/p&gt;
&lt;p&gt;Rather than asking for a specific disk, an application simply requests:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a certain amount of space,&lt;/li&gt;
&lt;li&gt;an access mode,&lt;/li&gt;
&lt;li&gt;and, optionally, a storage class.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Pod never mounts a PersistentVolume directly. Instead, it references a PVC and lets Kubernetes handle the rest.&lt;/p&gt;
&lt;h2 id="binding"&gt;Binding
&lt;/h2&gt;&lt;p&gt;Whenever a PersistentVolumeClaim is created, Kubernetes searches for a suitable PersistentVolume that satisfies the requested parameters.&lt;/p&gt;
&lt;p&gt;If one is found, the claim becomes permanently bound to that volume.&lt;/p&gt;
&lt;p&gt;From the application&amp;rsquo;s point of view, none of this complexity exists.&lt;/p&gt;
&lt;p&gt;The Pod simply mounts the PVC as a directory inside the container.&lt;/p&gt;
&lt;h2 id="access-modes"&gt;Access Modes
&lt;/h2&gt;&lt;p&gt;Access modes describe &lt;strong&gt;how Kubernetes nodes are allowed to mount a volume&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Four access modes currently exist:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ReadWriteOnce (RWO)&lt;/strong&gt; — the volume may be mounted for reading and writing by only &lt;strong&gt;one node&lt;/strong&gt; at a time. Multiple Pods may still use it, provided they all run on that node.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ReadWriteMany (RWX)&lt;/strong&gt; — the volume may be mounted simultaneously by multiple nodes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ReadOnlyMany (ROX)&lt;/strong&gt; — multiple nodes may mount the volume, but only for reading.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ReadWriteOncePod (RWOP)&lt;/strong&gt; — introduced more recently, this mode guarantees that exactly one Pod in the entire cluster has read-write access to the volume.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; the underlying storage backend must actually support the requested access mode. Kubernetes cannot magically turn a local SSD into a distributed filesystem.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h1 id="where-should-the-data-live"&gt;Where Should the Data Live?
&lt;/h1&gt;&lt;p&gt;By default, &lt;strong&gt;k3s&lt;/strong&gt; ships with the &lt;strong&gt;Local Path Provisioner&lt;/strong&gt;, which stores dynamically created PersistentVolumes under:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/var/lib/rancher/k3s/storage
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Personally, I prefer keeping persistent application data somewhere more obvious, such as:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/srv/storage
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To do that, create the directory on every node:&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;sudo mkdir -p /srv/storage
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo chmod &lt;span style="color:#ae81ff"&gt;777&lt;/span&gt; /srv/storage
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The permissions are intentionally generous. This is a home lab, not Fort Knox.&lt;/p&gt;
&lt;p&gt;Next, edit the &lt;code&gt;local-path-config&lt;/code&gt; ConfigMap:&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;kubectl -n kube-system edit configmap local-path-config
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The interesting part looks like this:&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;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;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;config.json&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:#e6db74"&gt; {
&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;nodePathMap&amp;#34;:[
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&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:#e6db74"&gt; &amp;#34;node&amp;#34;:&amp;#34;DEFAULT_PATH_FOR_NON_LISTED_NODES&amp;#34;,
&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;paths&amp;#34;:[&amp;#34;/var/lib/rancher/k3s/storage&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&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:#e6db74"&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; }&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Simply replace:&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:#e6db74"&gt;&amp;#34;paths&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;[&lt;span style="color:#e6db74"&gt;&amp;#34;/var/lib/rancher/k3s/storage&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;with:&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:#e6db74"&gt;&amp;#34;paths&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;[&lt;span style="color:#e6db74"&gt;&amp;#34;/srv/storage&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Newly created PersistentVolumes will now live there instead.&lt;/p&gt;
&lt;h1 id="deploying-the-registry"&gt;Deploying the Registry
&lt;/h1&gt;&lt;p&gt;The registry will be available as:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;registry.lab.local
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Since there is no local DNS server yet, every machine—including your workstation—should receive the following &lt;code&gt;/etc/hosts&lt;/code&gt; entry:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;10.10.10.24 registry.lab.local
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The registry should preferably run on the most capable machine in the cluster.&lt;/p&gt;
&lt;p&gt;In my case, that is &lt;code&gt;iridium&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Assign it an appropriate label:&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;kubectl label node iridium registry&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The Deployment manifest below uses that label via a &lt;code&gt;nodeSelector&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;At first glance, a registry seems like a simple application.&lt;/p&gt;
&lt;p&gt;Then you remember it needs persistent storage.&lt;/p&gt;
&lt;p&gt;And a Service.&lt;/p&gt;
&lt;p&gt;And an Ingress.&lt;/p&gt;
&lt;p&gt;And perhaps its own namespace.&lt;/p&gt;
&lt;p&gt;And a ConfigMap.&lt;/p&gt;
&lt;p&gt;So, in the end, it turns out to require exactly six Kubernetes objects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Namespace&lt;/li&gt;
&lt;li&gt;PersistentVolumeClaim&lt;/li&gt;
&lt;li&gt;ConfigMap&lt;/li&gt;
&lt;li&gt;Deployment&lt;/li&gt;
&lt;li&gt;Service&lt;/li&gt;
&lt;li&gt;Ingress&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Their complete definitions are shown below.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(YAML manifest unchanged.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Save everything as:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;registry.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;and deploy it using:&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;kubectl apply -f registry.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="interesting-bits"&gt;Interesting Bits
&lt;/h2&gt;&lt;p&gt;The PVC requests persistent storage:&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;accessModes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;ReadWriteOnce&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The registry is pinned to the node labelled earlier:&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;nodeSelector&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;registry&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The ingress disables request size limits:&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;nginx.ingress.kubernetes.io/proxy-body-size&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Without this annotation, pushing sufficiently large images eventually results in sadness, disappointment, and extensive arm waving.&lt;/p&gt;
&lt;p&gt;The registry configuration itself is stored in a ConfigMap rather than a long list of environment variables.&lt;/p&gt;
&lt;p&gt;Since the registry already expects a YAML configuration file, using a ConfigMap keeps the deployment considerably cleaner.&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;This setup stores all registry data on the local filesystem of a single node. That is perfectly adequate for a home lab. Production environments usually rely on distributed storage instead.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h1 id="running-without-tls"&gt;Running Without TLS
&lt;/h1&gt;&lt;p&gt;If the registry temporarily runs over plain HTTP, Docker must be told to trust it.&lt;/p&gt;
&lt;p&gt;Create:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/etc/docker/daemon.json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;containing:&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;insecure-registries&amp;#34;&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;registry.lab.local&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Likewise, every k3s node should receive:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/etc/rancher/k3s/registries.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;containing:&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;mirrors&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;registry.lab.local&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;endpoint&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;http://registry.lab.local&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, restart k3s:&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;sudo systemctl restart k3s &lt;span style="color:#f92672"&gt;||&lt;/span&gt; sudo systemctl restart k3s-agent
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Don&amp;rsquo;t forget to update the ConfigMap as well if you&amp;rsquo;re switching from HTTPS to HTTP.&lt;/p&gt;
&lt;h1 id="verification"&gt;Verification
&lt;/h1&gt;&lt;p&gt;The usual suspects come 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-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get pods -n registry
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get pvc -n registry
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get ingress -n registry
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get svc -n registry
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl describe ingress registry -n registry
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get configmap -n registry
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If the PVC reports:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;STATUS: Bound
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;everything is proceeding nicely:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the PersistentVolume exists,&lt;/li&gt;
&lt;li&gt;the claim has been bound,&lt;/li&gt;
&lt;li&gt;the Pod is successfully using it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="smoke-test"&gt;Smoke Test
&lt;/h1&gt;&lt;p&gt;Let&amp;rsquo;s put the registry to work.&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 pull php:8.4.22-zts-trixie
&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;docker tag php:8.4.22-zts-trixie &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; registry.lab.local/php:8.4.22
&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;docker push registry.lab.local/php:8.4.22
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To make sure the image really came from the registry rather than your local cache, remove it 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-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker image rm registry.lab.local/php:8.4.22
&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;docker pull registry.lab.local/php:8.4.22
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, query the registry 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-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl https://registry.lab.local/v2/php/tags/list
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;which should return:&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:#f92672"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;:&lt;span style="color:#e6db74"&gt;&amp;#34;php&amp;#34;&lt;/span&gt;,&lt;span style="color:#f92672"&gt;&amp;#34;tags&amp;#34;&lt;/span&gt;:[&lt;span style="color:#e6db74"&gt;&amp;#34;8.4.22&amp;#34;&lt;/span&gt;]}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Or list every repository:&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;curl -s https://registry.lab.local/v2/_catalog | jq
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Congratulations.&lt;/p&gt;
&lt;p&gt;Your registry is officially alive.&lt;/p&gt;
&lt;h1 id="registry-ui"&gt;Registry UI
&lt;/h1&gt;&lt;p&gt;A registry works perfectly well without a graphical interface.&lt;/p&gt;
&lt;p&gt;Humans, however, tend to appreciate one.&lt;/p&gt;
&lt;p&gt;The excellent &lt;strong&gt;Docker Registry UI&lt;/strong&gt; provides browsing, searching, and deleting images from a web browser.&lt;/p&gt;
&lt;p&gt;As before, add:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;10.10.10.24 registry-ui.lab.local
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;to every &lt;code&gt;/etc/hosts&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Deploy the accompanying Deployment, Service, and Ingress manifests (shown below), save them as:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;registry-ui.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;and apply them:&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;kubectl apply -f registry-ui.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The interface is intentionally simple.&lt;/p&gt;
&lt;p&gt;It won&amp;rsquo;t win any design awards, but it makes exploring your registry significantly more pleasant than remembering API endpoints.&lt;/p&gt;
&lt;h1 id="wrapping-up"&gt;Wrapping Up
&lt;/h1&gt;&lt;p&gt;At this point, the cluster has become largely self-sufficient.&lt;/p&gt;
&lt;p&gt;Applications can now be built locally, pushed into the private registry, and deployed without relying on Docker Hub.&lt;/p&gt;
&lt;p&gt;It may not look particularly impressive yet, but from now on every new service added to the cluster can remain entirely within the home lab.&lt;/p&gt;
&lt;p&gt;Which, after all, was the idea from the very beginning.&lt;/p&gt;</description></item><item><title>2026-06-25</title><link>https://dev.iostream.pl/posts/2026-06-25/</link><pubDate>Thu, 25 Jun 2026 11:15:00 +0200</pubDate><guid>https://dev.iostream.pl/posts/2026-06-25/</guid><description>&lt;h1 id="installation"&gt;Installation
&lt;/h1&gt;&lt;p&gt;There isn&amp;rsquo;t much to say about installing Emacs.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Every respectable Linux distribution includes Emacs in its package repositories. Simply use your distribution&amp;rsquo;s package manager.&lt;/li&gt;
&lt;li&gt;The same is true for the BSD family.&lt;/li&gt;
&lt;li&gt;On macOS, you can install it using &lt;code&gt;Homebrew&lt;/code&gt;, &lt;code&gt;MacPorts&lt;/code&gt;, or download a native build from &lt;a class="link" href="https://emacsformacosx.com/" target="_blank" rel="noopener"
 &gt;https://emacsformacosx.com/&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;On Windows, simply download the official installer (&lt;code&gt;.exe&lt;/code&gt;) or the ZIP archive and extract it somewhere sensible.&lt;/li&gt;
&lt;li&gt;On Android, install the official &lt;code&gt;.apk&lt;/code&gt; package and allow applications from unknown sources in the system settings.&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;A small note:&lt;/strong&gt; if you have no interest in running the graphical version, most Unix-like systems also provide an &lt;code&gt;emacs-nox&lt;/code&gt; package.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;Of course, you can always build Emacs from source.&lt;/p&gt;
&lt;p&gt;But why would you, when you could spend that hour starting another flame war on Twitter (formerly &amp;ldquo;X&amp;rdquo;), Reddit, or simply improving your thumb&amp;rsquo;s scrolling endurance on social media?&lt;/p&gt;
&lt;p&gt;Personally, I&amp;rsquo;d rather spend that hour weeding the vegetable garden.&lt;/p&gt;
&lt;p&gt;Usenet has long since passed into history, and no equally entertaining place for operating-system flame wars has ever truly replaced it.&lt;/p&gt;
&lt;h1 id="getting-started"&gt;Getting Started
&lt;/h1&gt;&lt;h2 id="launching-emacs"&gt;Launching Emacs
&lt;/h2&gt;&lt;p&gt;Launching the Finest Editor Known to Humankind is surprisingly simple.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On graphical desktops, Emacs should appear in your applications menu like any other program.&lt;/li&gt;
&lt;li&gt;On Windows, if you extracted the ZIP archive instead of running the installer, start &lt;code&gt;bin\runemacs.exe&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If you prefer a purely terminal-based experience, simply run:&lt;/li&gt;
&lt;/ul&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;emacs -nw
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is where the Emacs journey truly begins.&lt;/p&gt;
&lt;p&gt;The first launch of an unmodified (&lt;em&gt;vanilla&lt;/em&gt;) Emacs under X11, Windows, or macOS opens a window featuring the GNU antelope, a welcome screen, and a few introductory notes.&lt;/p&gt;
&lt;p&gt;Curiously, what most desktop environments call a &lt;em&gt;window&lt;/em&gt;, Emacs calls a &lt;strong&gt;frame&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The reason is historical.&lt;/p&gt;
&lt;p&gt;Long before graphical user interfaces became commonplace, Emacs already knew how to divide a terminal screen into multiple independent editing areas called &lt;strong&gt;windows&lt;/strong&gt;. When graphical support arrived, the terminology remained unchanged: the top-level GUI object became a &lt;em&gt;frame&lt;/em&gt;, while the individual editing regions inside it continued to be called &lt;em&gt;windows&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Some traditions are simply too old to change.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Emacs main window" class="gallery-image" data-flex-basis="230px" data-flex-grow="96" data-title-escaped="Emacs main window" height="908" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://dev.iostream.pl/posts/2026-06-25/emacs-1.png" srcset="https://dev.iostream.pl/posts/2026-06-25/emacs-1_hu_1d06649b4d62bc31.png 800w, https://dev.iostream.pl/posts/2026-06-25/emacs-1.png 872w" title="Emacs main window" width="872"&gt;&lt;/p&gt;
&lt;h1 id="meet-the-interface"&gt;Meet the Interface
&lt;/h1&gt;&lt;p&gt;A default Emacs frame consists of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a menu bar,&lt;/li&gt;
&lt;li&gt;a toolbar (whose appearance ranges from &amp;ldquo;functional&amp;rdquo; to &amp;ldquo;deeply unfortunate&amp;rdquo;, although fortunately it can be hidden),&lt;/li&gt;
&lt;li&gt;one or more editing buffers,&lt;/li&gt;
&lt;li&gt;and the command line, better known as the &lt;strong&gt;minibuffer&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="buffers"&gt;Buffers
&lt;/h2&gt;&lt;p&gt;A &lt;strong&gt;buffer&lt;/strong&gt; is the place where text is edited.&lt;/p&gt;
&lt;p&gt;Unlike most editors, however, an Emacs buffer is not necessarily associated with a file.&lt;/p&gt;
&lt;p&gt;Buffers may contain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;source code,&lt;/li&gt;
&lt;li&gt;documentation,&lt;/li&gt;
&lt;li&gt;shell sessions,&lt;/li&gt;
&lt;li&gt;email,&lt;/li&gt;
&lt;li&gt;directory listings,&lt;/li&gt;
&lt;li&gt;Git status,&lt;/li&gt;
&lt;li&gt;help pages,&lt;/li&gt;
&lt;li&gt;or virtually anything else.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This abstraction is one of the core ideas behind Emacs.&lt;/p&gt;
&lt;p&gt;Every buffer contains an editing area together with a &lt;strong&gt;mode line&lt;/strong&gt;, which displays useful information such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the buffer name,&lt;/li&gt;
&lt;li&gt;whether the buffer has unsaved changes,&lt;/li&gt;
&lt;li&gt;the current cursor position,&lt;/li&gt;
&lt;li&gt;and the active editing mode.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="major-modes"&gt;Major Modes
&lt;/h2&gt;&lt;p&gt;Most editing behavior in Emacs is controlled by &lt;strong&gt;major modes&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A major mode typically corresponds to the type of document being edited.&lt;/p&gt;
&lt;p&gt;Whether you&amp;rsquo;re writing C code, XML, Markdown, Lisp, YAML, or something entirely your own, the selected mode determines:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;syntax highlighting,&lt;/li&gt;
&lt;li&gt;indentation rules,&lt;/li&gt;
&lt;li&gt;available commands,&lt;/li&gt;
&lt;li&gt;keyboard shortcuts,&lt;/li&gt;
&lt;li&gt;and countless editor behaviors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Creating your own modes is also entirely possible, should inspiration strike.&lt;/p&gt;
&lt;h2 id="the-minibuffer"&gt;The Minibuffer
&lt;/h2&gt;&lt;p&gt;The &lt;strong&gt;minibuffer&lt;/strong&gt; serves as the communication channel between Emacs and its user.&lt;/p&gt;
&lt;p&gt;Emacs displays prompts and status messages there, while the user enters commands, filenames, search patterns, and countless other parameters.&lt;/p&gt;
&lt;p&gt;The minibuffer supports:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;command history,&lt;/li&gt;
&lt;li&gt;TAB completion,&lt;/li&gt;
&lt;li&gt;and context-sensitive suggestions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It may look like a single line of text, but it is one of the most heavily used parts of Emacs.&lt;/p&gt;
&lt;h2 id="windows-and-frames"&gt;Windows and Frames
&lt;/h2&gt;&lt;p&gt;A frame may be divided into multiple &lt;strong&gt;windows&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Each window displays a buffer.&lt;/p&gt;
&lt;p&gt;Interestingly, multiple windows may display the very same buffer simultaneously, each with its own cursor position and viewport.&lt;/p&gt;
&lt;p&gt;Only the minibuffer is shared across the entire frame.&lt;/p&gt;
&lt;h1 id="keys-and-commands"&gt;Keys and Commands
&lt;/h1&gt;&lt;p&gt;Emacs documentation uses a notation that initially looks mysterious but quickly becomes second nature.&lt;/p&gt;
&lt;p&gt;The most common forms are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;C-x&lt;/strong&gt; — hold the &lt;strong&gt;Control&lt;/strong&gt; key while pressing &lt;code&gt;x&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-x C-f&lt;/strong&gt; — execute the first key sequence, release the regular keys (you may keep holding Control if you like), then execute the second.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;M-x&lt;/strong&gt; — hold the &lt;strong&gt;Meta&lt;/strong&gt; key. On most PC keyboards this is simply the left &lt;strong&gt;Alt&lt;/strong&gt; key. I strongly recommend getting used to the left Alt immediately, since the right one is often reserved for national characters or other platform-specific input methods.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-M-x&lt;/strong&gt; — press both Control and Meta together.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;S-x&lt;/strong&gt; — press Shift together with the specified key.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally, one notation deserves its own paragraph.&lt;/p&gt;
&lt;h2 id="m-x"&gt;M-x
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;M-x&lt;/code&gt; executes an Emacs command.&lt;/p&gt;
&lt;p&gt;Press &lt;code&gt;M-x&lt;/code&gt;, release the keys, type the command name into the minibuffer, and press Enter.&lt;/p&gt;
&lt;p&gt;Almost everything Emacs can do—apart from simply inserting text—is ultimately implemented as a Lisp function.&lt;/p&gt;
&lt;p&gt;Some of those functions appear in menus.&lt;/p&gt;
&lt;p&gt;Some are bound to keyboard shortcuts.&lt;/p&gt;
&lt;p&gt;All of them can be executed directly via &lt;code&gt;M-x&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;choosing &lt;strong&gt;File → Open&amp;hellip;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;pressing &lt;code&gt;C-x C-f&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;executing &lt;code&gt;M-x find-file&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;all invoke exactly the same underlying function.&lt;/p&gt;
&lt;p&gt;&lt;img alt="M-x find-file" class="gallery-image" data-flex-basis="335px" data-flex-grow="139" data-title-escaped="M-x find-file" height="630" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://dev.iostream.pl/posts/2026-06-25/emacs-2.png" srcset="https://dev.iostream.pl/posts/2026-06-25/emacs-2_hu_9996e68af439c3f.png 800w, https://dev.iostream.pl/posts/2026-06-25/emacs-2.png 881w" title="M-x find-file" width="881"&gt;&lt;/p&gt;
&lt;h1 id="useful-keyboard-shortcuts"&gt;Useful Keyboard Shortcuts
&lt;/h1&gt;&lt;p&gt;The following commands quickly become second nature:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;C-x C-c&lt;/strong&gt; — exit Emacs. Generally considered a questionable life decision, although shutting down the computer is usually accepted as a valid excuse.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-x C-f&lt;/strong&gt; — open an existing file or create a new one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-x C-s&lt;/strong&gt; — save the current buffer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-x C-w&lt;/strong&gt; — save the current buffer under a different name.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-x k&lt;/strong&gt; — kill (close) the current buffer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-x Left&lt;/strong&gt; — previous buffer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-x Right&lt;/strong&gt; — next buffer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-x C-b&lt;/strong&gt; — list all buffers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-x h&lt;/strong&gt; — select the entire buffer.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="editing-text"&gt;Editing Text
&lt;/h1&gt;&lt;p&gt;Unlike some other editors, Emacs does not have separate &amp;ldquo;insert&amp;rdquo; and &amp;ldquo;command&amp;rdquo; modes.&lt;/p&gt;
&lt;p&gt;Open a file.&lt;/p&gt;
&lt;p&gt;Start typing.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s all there is to it.&lt;/p&gt;
&lt;p&gt;Modern keyboards provide cursor keys, Home, End, Page Up, and Page Down, and Emacs fully supports them.&lt;/p&gt;
&lt;p&gt;For historical reasons—and because terminals were not always so well equipped—traditional Emacs key bindings are also available:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;C-a&lt;/strong&gt; — beginning of line&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-e&lt;/strong&gt; — end of line&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-f&lt;/strong&gt; — forward one character&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-b&lt;/strong&gt; — backward one character&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-p&lt;/strong&gt; — previous line&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-n&lt;/strong&gt; — next line&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;M-v&lt;/strong&gt; — previous page&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-v&lt;/strong&gt; — next page&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;M-w&lt;/strong&gt; — copy selected text&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-w&lt;/strong&gt; — cut selected text&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-y&lt;/strong&gt; — paste&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;M-y&lt;/strong&gt; — immediately after &lt;code&gt;C-y&lt;/code&gt;, cycle through previous clipboard entries&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-k&lt;/strong&gt; — kill text from the cursor to the end of the line. Pressing it twice at the beginning of a line removes the entire line.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-x h&lt;/strong&gt; — select the whole buffer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-_&lt;/strong&gt; (or &lt;code&gt;C-x u&lt;/code&gt;) — undo, with unlimited history.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Graphical Emacs naturally supports selecting text with the mouse.&lt;/p&gt;
&lt;p&gt;Keyboard users, however, can set the beginning of a selection using &lt;code&gt;C-SPC&lt;/code&gt;, move the cursor to the desired position, and then perform any operation on the selected region.&lt;/p&gt;
&lt;p&gt;It is one of those habits that soon becomes completely automatic.&lt;/p&gt;
&lt;p&gt;One unexpected benefit of learning Emacs is that the keyboard shortcuts tend to follow you.&lt;/p&gt;
&lt;p&gt;Bash, the Python REPL, GDB, and many other command-line programs borrow their line-editing behavior from GNU Readline, whose default key bindings are deliberately Emacs-like.&lt;/p&gt;
&lt;p&gt;Even if you eventually decide that Emacs is not your editor of choice, the time spent learning &lt;code&gt;C-a&lt;/code&gt;, &lt;code&gt;C-e&lt;/code&gt;, &lt;code&gt;C-k&lt;/code&gt;, or &lt;code&gt;M-f&lt;/code&gt; will continue to pay dividends every time you open a terminal.&lt;/p&gt;
&lt;p&gt;In fact, many people use Emacs key bindings every day without realizing where they originally came from.&lt;/p&gt;</description></item><item><title>2026-06-24</title><link>https://dev.iostream.pl/posts/2026-06-24/</link><pubDate>Wed, 24 Jun 2026 11:15:00 +0200</pubDate><guid>https://dev.iostream.pl/posts/2026-06-24/</guid><description>&lt;h1 id="k3s-ingress"&gt;k3s: Ingress
&lt;/h1&gt;&lt;p&gt;What exactly is an Ingress?&lt;/p&gt;
&lt;p&gt;In the Kubernetes ecosystem, an &lt;strong&gt;Ingress&lt;/strong&gt; is an API object that manages external access to applications running inside a cluster. It acts as an intelligent HTTP and HTTPS router, eliminating the need to create a separate load balancer for every service.&lt;/p&gt;
&lt;h2 id="how-does-ingress-work"&gt;How Does Ingress Work?
&lt;/h2&gt;&lt;p&gt;Traditional service exposure methods, such as &lt;code&gt;NodePort&lt;/code&gt; or &lt;code&gt;LoadBalancer&lt;/code&gt;, typically require assigning an external IP address to each service. This quickly becomes tedious and difficult to manage as the cluster grows.&lt;/p&gt;
&lt;p&gt;Ingress centralizes external access and operates at Layer 7 (the application layer) of the OSI model:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Single entry point&lt;/strong&gt; — only one public IP address needs to be exposed to the outside world.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rule-based routing&lt;/strong&gt; — traffic is forwarded to the appropriate service based on the requested URL path (for example, &lt;code&gt;my.domain/something&lt;/code&gt;) or hostname (for example, &lt;code&gt;something.my.domain&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSL/TLS termination&lt;/strong&gt; — HTTPS certificates can be handled centrally, removing that responsibility from individual applications.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ingress-vs-ingress-controller"&gt;Ingress vs. Ingress Controller
&lt;/h2&gt;&lt;p&gt;This distinction is important.&lt;/p&gt;
&lt;p&gt;An Ingress object itself is merely a collection of routing rules.&lt;/p&gt;
&lt;p&gt;For those rules to have any effect, the cluster must run an &lt;strong&gt;Ingress Controller&lt;/strong&gt; — a reverse proxy responsible for processing incoming traffic and applying the rules defined in Ingress manifests.&lt;/p&gt;
&lt;p&gt;Popular examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NGINX Ingress Controller&lt;/li&gt;
&lt;li&gt;Traefik&lt;/li&gt;
&lt;li&gt;HAProxy Ingress&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This guide uses the &lt;strong&gt;NGINX Ingress Controller&lt;/strong&gt;.&lt;/p&gt;
&lt;h1 id="domain-names"&gt;Domain Names
&lt;/h1&gt;&lt;p&gt;Since the cluster will eventually host various web applications, it makes sense to identify services by DNS names rather than IP addresses.&lt;/p&gt;
&lt;p&gt;The examples below use:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.lab.local
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;as the local domain.&lt;/p&gt;
&lt;p&gt;This allows services to be accessed using names such as:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;grafana.lab.local
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;argocd.lab.local
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;whoami.lab.local
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;which is considerably more pleasant than remembering port numbers.&lt;/p&gt;
&lt;h1 id="certificates"&gt;Certificates
&lt;/h1&gt;&lt;p&gt;Sooner or later, running everything without TLS becomes annoying.&lt;/p&gt;
&lt;p&gt;For a home lab environment, generating your own Certificate Authority and issuing certificates locally is perfectly acceptable.&lt;/p&gt;
&lt;h2 id="root-certificate-authority"&gt;Root Certificate Authority
&lt;/h2&gt;&lt;p&gt;Create a dedicated CA:&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;mkdir -p certs
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd certs
&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;openssl genrsa -out lab-ca.key &lt;span style="color:#ae81ff"&gt;4096&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;openssl req -x509 -new -nodes &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -key lab-ca.key &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -sha256 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -days &lt;span style="color:#ae81ff"&gt;3650&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; -out lab-ca.crt &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -subj &lt;span style="color:#e6db74"&gt;&amp;#34;/CN=Lab Local CA&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="wildcard-certificate"&gt;Wildcard Certificate
&lt;/h2&gt;&lt;p&gt;A wildcard certificate can secure every host under:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;*.lab.local
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Create the configuration file &lt;code&gt;lab-local.cnf&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[req]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;default_bits = 2048
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;prompt = no
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;default_md = sha256
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;distinguished_name = dn
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;req_extensions = req_ext
&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;[dn]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;CN = *.lab.local
&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;[req_ext]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;subjectAltName = @alt_names
&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;[alt_names]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;DNS.1 = *.lab.local
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;DNS.2 = lab.local
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Generate the key, request, and certificate:&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;openssl genrsa -out lab-local.key &lt;span style="color:#ae81ff"&gt;2048&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;openssl req -new &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -key lab-local.key &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -out lab-local.csr &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -config lab-local.cnf
&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;openssl x509 -req &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -in lab-local.csr &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -CA lab-ca.crt &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -CAkey lab-ca.key &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -CAcreateserial &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -out lab-local.crt &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -days &lt;span style="color:#ae81ff"&gt;825&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; -sha256 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -extensions req_ext &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -extfile lab-local.cnf
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="namespace-for-the-ingress-controller"&gt;Namespace for the Ingress Controller
&lt;/h1&gt;&lt;p&gt;The ingress controller will live in its own namespace.&lt;/p&gt;
&lt;p&gt;Create &lt;code&gt;ingress-namespace.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;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;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;Namespace&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;ingress-nginx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Apply it:&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;kubectl apply -f ingress-namespace.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="tls-secret"&gt;TLS Secret
&lt;/h1&gt;&lt;p&gt;The certificate must be stored inside Kubernetes as a TLS 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-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n ingress-nginx create secret tls lab-local-tls &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --cert&lt;span style="color:#f92672"&gt;=&lt;/span&gt;lab-local.crt &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --key&lt;span style="color:#f92672"&gt;=&lt;/span&gt;lab-local.key
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The secret is named:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;lab-local-tls
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;and can be referenced as:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ingress-nginx/lab-local-tls
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;throughout the cluster configuration.&lt;/p&gt;
&lt;h1 id="installing-the-ca-certificate-on-your-workstation"&gt;Installing the CA Certificate on Your Workstation
&lt;/h1&gt;&lt;p&gt;The root CA must also be trusted by your workstation:&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;sudo cp lab-ca.crt /usr/local/share/ca-certificates/lab-ca.crt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo update-ca-certificates
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Firefox users should additionally import the CA certificate manually through the browser settings.&lt;/p&gt;
&lt;h1 id="deploying-the-ingress-controller"&gt;Deploying the Ingress Controller
&lt;/h1&gt;&lt;p&gt;A complete ingress controller deployment requires several Kubernetes resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ServiceAccount&lt;/li&gt;
&lt;li&gt;IngressClass&lt;/li&gt;
&lt;li&gt;ConfigMap&lt;/li&gt;
&lt;li&gt;ClusterRole&lt;/li&gt;
&lt;li&gt;ClusterRoleBinding&lt;/li&gt;
&lt;li&gt;Deployment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The container image used in this setup is:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;registry.k8s.io/ingress-nginx/controller:v1.14.0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;which provides the familiar NGINX reverse proxy and load balancer functionality.&lt;/p&gt;
&lt;p&gt;The deployment manifest is shown below.&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;# ingress.yaml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# (manifest unchanged from the original article)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="noteworthy-configuration"&gt;Noteworthy Configuration
&lt;/h2&gt;&lt;p&gt;The ingress controller is pinned to the control-plane node:&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;nodeSelector&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;kubernetes.io/hostname&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;iridium&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is perfectly acceptable for a small home lab.&lt;/p&gt;
&lt;p&gt;The controller also uses the wildcard certificate by default:&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:#ae81ff"&gt;default-ssl-certificate=ingress-nginx/lab-local-tls&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;which means every ingress can immediately benefit from TLS.&lt;/p&gt;
&lt;h1 id="verifying-the-installation"&gt;Verifying the Installation
&lt;/h1&gt;&lt;p&gt;Start with the usual checks:&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;kubectl get pods -n ingress-nginx -o wide
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get rs -n ingress-nginx -o wide
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, add a test host entry:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;10.10.10.24 test.lab.local
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;to your workstation&amp;rsquo;s &lt;code&gt;/etc/hosts&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then run:&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;curl http://test.lab.local
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Expected result:&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-html" data-lang="html"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;html&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;head&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style="color:#f92672"&gt;title&lt;/span&gt;&amp;gt;404 Not Found&amp;lt;/&lt;span style="color:#f92672"&gt;title&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style="color:#f92672"&gt;head&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;body&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;center&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style="color:#f92672"&gt;h1&lt;/span&gt;&amp;gt;404 Not Found&amp;lt;/&lt;span style="color:#f92672"&gt;h1&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style="color:#f92672"&gt;center&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;hr&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style="color:#f92672"&gt;center&lt;/span&gt;&amp;gt;nginx&amp;lt;/&lt;span style="color:#f92672"&gt;center&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;body&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;html&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Surprisingly, this is good news.&lt;/p&gt;
&lt;p&gt;The DNS lookup works.&lt;/p&gt;
&lt;p&gt;The ingress controller works.&lt;/p&gt;
&lt;p&gt;NGINX works.&lt;/p&gt;
&lt;p&gt;There simply isn&amp;rsquo;t any application behind the hostname yet.&lt;/p&gt;
&lt;p&gt;The infrastructure is doing exactly what it should.&lt;/p&gt;
&lt;h2 id="tls-verification"&gt;TLS Verification
&lt;/h2&gt;&lt;p&gt;Next, test HTTPS:&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;curl -v https://test.lab.local
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The interesting part is not the final HTTP response, but the TLS negotiation itself.&lt;/p&gt;
&lt;p&gt;Look for output similar to:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;SSL connection using TLSv1.3
&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;subject: CN=*.lab.local
&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;issuer: CN=Lab Local CA
&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;subjectAltName: &amp;#34;test.lab.local&amp;#34; matches cert&amp;#39;s &amp;#34;*.lab.local&amp;#34;
&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;SSL certificate verified via OpenSSL
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If these messages appear, the certificate chain is functioning correctly and the wildcard certificate is being served as expected.&lt;/p&gt;
&lt;p&gt;Finally, open the URL in Firefox and confirm that the browser displays the expected lock icon and certificate information.&lt;/p&gt;
&lt;h1 id="conclusion"&gt;Conclusion
&lt;/h1&gt;&lt;p&gt;At this point:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DNS works,&lt;/li&gt;
&lt;li&gt;the ingress controller is running,&lt;/li&gt;
&lt;li&gt;TLS works,&lt;/li&gt;
&lt;li&gt;wildcard certificates work,&lt;/li&gt;
&lt;li&gt;external traffic reaches the cluster.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No actual applications are exposed yet, but the foundation is now in place.&lt;/p&gt;
&lt;p&gt;The cluster is finally ready to host something more interesting than infrastructure components.&lt;/p&gt;
&lt;p&gt;Which, as every Kubernetes administrator eventually discovers, is usually the easy part.&lt;/p&gt;</description></item><item><title>2026-06-23</title><link>https://dev.iostream.pl/posts/2026-06-23/</link><pubDate>Tue, 23 Jun 2026 11:15:00 +0200</pubDate><guid>https://dev.iostream.pl/posts/2026-06-23/</guid><description>&lt;h1 id="installing-k3s"&gt;Installing k3s
&lt;/h1&gt;
 &lt;blockquote&gt;
 &lt;p&gt;Warning:&lt;/p&gt;
&lt;p&gt;This article describes the preparation, installation, and initial configuration of a Kubernetes cluster.&lt;/p&gt;
&lt;p&gt;You are reading at your own risk. There is a non-zero chance that your life will never be quite the same afterward.&lt;/p&gt;
&lt;p&gt;All procedures described here have been tested.&lt;/p&gt;
&lt;p&gt;Also, no animals were harmed during the writing of this article. Unless we count two flies, six mosquitoes, and one unfortunate snail removed from a home cucumber plantation.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h1 id="hardware"&gt;Hardware
&lt;/h1&gt;&lt;p&gt;The cluster consists of the following machines:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Hostname&lt;/th&gt;
					&lt;th&gt;IP Address&lt;/th&gt;
					&lt;th&gt;SBC&lt;/th&gt;
					&lt;th&gt;Architecture&lt;/th&gt;
					&lt;th&gt;RAM&lt;/th&gt;
					&lt;th&gt;Storage&lt;/th&gt;
					&lt;th&gt;Operating System&lt;/th&gt;
					&lt;th&gt;Intended Role&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;palladium&lt;/td&gt;
					&lt;td&gt;10.10.10.20&lt;/td&gt;
					&lt;td&gt;Odroid N2&lt;/td&gt;
					&lt;td&gt;arm64&lt;/td&gt;
					&lt;td&gt;4 GB&lt;/td&gt;
					&lt;td&gt;128 GB&lt;/td&gt;
					&lt;td&gt;Armbian&lt;/td&gt;
					&lt;td&gt;tiny-int&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;vanadium&lt;/td&gt;
					&lt;td&gt;10.10.10.21&lt;/td&gt;
					&lt;td&gt;Raspberry Pi 5&lt;/td&gt;
					&lt;td&gt;arm64&lt;/td&gt;
					&lt;td&gt;8 GB&lt;/td&gt;
					&lt;td&gt;512 GB&lt;/td&gt;
					&lt;td&gt;Ubuntu Server&lt;/td&gt;
					&lt;td&gt;ci-cd&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;iridium&lt;/td&gt;
					&lt;td&gt;10.10.10.24&lt;/td&gt;
					&lt;td&gt;Odroid M2&lt;/td&gt;
					&lt;td&gt;arm64&lt;/td&gt;
					&lt;td&gt;16 GB&lt;/td&gt;
					&lt;td&gt;512 GB&lt;/td&gt;
					&lt;td&gt;Armbian&lt;/td&gt;
					&lt;td&gt;control-plane&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;ruthenium&lt;/td&gt;
					&lt;td&gt;10.10.10.25&lt;/td&gt;
					&lt;td&gt;Orange Pi 5 Plus&lt;/td&gt;
					&lt;td&gt;arm64&lt;/td&gt;
					&lt;td&gt;16 GB&lt;/td&gt;
					&lt;td&gt;512 GB&lt;/td&gt;
					&lt;td&gt;Armbian&lt;/td&gt;
					&lt;td&gt;heavy-worker&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;tantalum&lt;/td&gt;
					&lt;td&gt;10.10.10.26&lt;/td&gt;
					&lt;td&gt;Raspberry Pi 500&lt;/td&gt;
					&lt;td&gt;arm64&lt;/td&gt;
					&lt;td&gt;16 GB&lt;/td&gt;
					&lt;td&gt;256 GB&lt;/td&gt;
					&lt;td&gt;Ubuntu Server&lt;/td&gt;
					&lt;td&gt;observability&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All IP addresses are assigned by the home router using static DHCP reservations based on MAC addresses.&lt;/p&gt;
&lt;h1 id="operating-system-and-required-software"&gt;Operating System and Required Software
&lt;/h1&gt;&lt;p&gt;All nodes run either Ubuntu Server or Armbian based on Ubuntu Noble.&lt;/p&gt;
&lt;p&gt;Versions may differ slightly, but package management remains identical across the cluster.&lt;/p&gt;
&lt;p&gt;The first step is ensuring that every machine can resolve the names of every other machine.&lt;/p&gt;
&lt;p&gt;At the moment there is no local DNS server, so each host requires an &lt;code&gt;/etc/hosts&lt;/code&gt; entry similar to the following:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;10.10.10.20 palladium
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;10.10.10.21 vanadium
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;10.10.10.24 iridium
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;10.10.10.25 ruthenium
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;10.10.10.26 tantalum
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It is also worth adding the same entries to the workstation or laptop used to manage the cluster.&lt;/p&gt;
&lt;p&gt;Windows users may perform the traditional rite of passage and figure out where Microsoft decided to hide the hosts file this decade.&lt;/p&gt;
&lt;h2 id="useful-tools"&gt;Useful Tools
&lt;/h2&gt;&lt;p&gt;It is highly recommended to install &lt;code&gt;kubectl&lt;/code&gt; on your workstation.&lt;/p&gt;
&lt;p&gt;The official installation instructions can be found here:&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/" target="_blank" rel="noopener"
 &gt;https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;On every cluster node, install a basic set of utilities:&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;sudo apt update &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; sudo apt install -y &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; curl &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wget &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vim &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; git &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; htop &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; net-tools &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; iproute2 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; iptables &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ca-certificates &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; gnupg &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lsb-release &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jq &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; socat &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; conntrack &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; open-iscsi &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; nfs-common &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tmux &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dnsutils &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tcpdump &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lsof &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; nc
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you prefer the Emacs lifestyle but do not enjoy &amp;ldquo;the Sixth Editor&amp;rdquo;, replace &lt;code&gt;vim&lt;/code&gt; with &lt;code&gt;mg&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You may also want:&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;sudo apt install mc
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;if navigating directories through a dual-panel interface reminds you of happier times.&lt;/p&gt;
&lt;p&gt;On &lt;code&gt;iridium&lt;/code&gt;, I additionally install:&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;sudo apt install -y sqlite3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Swap was already disabled during the preparation of the machines, but it is worth double-checking before proceeding.&lt;/p&gt;
&lt;h2 id="kernel-modules"&gt;Kernel Modules
&lt;/h2&gt;&lt;p&gt;Enable the required kernel modules:&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;cat &lt;span style="color:#e6db74"&gt;&amp;lt;&amp;lt;EOF | sudo tee /etc/modules-load.d/k3s.conf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;overlay
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;br_netfilter
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;EOF&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;sudo modprobe overlay
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo modprobe br_netfilter
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then configure the required kernel parameters:&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;cat &lt;span style="color:#e6db74"&gt;&amp;lt;&amp;lt;EOF | sudo tee /etc/sysctl.d/k3s.conf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;net.bridge.bridge-nf-call-iptables=1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;net.bridge.bridge-nf-call-ip6tables=1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;net.ipv4.ip_forward=1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;EOF&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;sudo sysctl --system
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="cgroups"&gt;cgroups
&lt;/h2&gt;&lt;p&gt;It is also worth checking whether cgroups are properly enabled:&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;cat /proc/cgroups
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Some systems may require additional kernel parameters in &lt;code&gt;/boot/cmdline.txt&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="kubernetes"&gt;Kubernetes
&lt;/h1&gt;&lt;p&gt;The chosen distribution is &lt;strong&gt;k3s&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It is fully compatible with &amp;ldquo;serious&amp;rdquo; Kubernetes while requiring significantly fewer resources, making it an excellent choice for home labs, edge computing, and IoT deployments.&lt;/p&gt;
&lt;p&gt;Small, lightweight, easy to install, and surprisingly capable. What more could one ask for?&lt;/p&gt;
&lt;h2 id="control-plane"&gt;Control Plane
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;iridium&lt;/code&gt; has been selected as the control-plane node because it is the most powerful machine in the cluster.&lt;/p&gt;
&lt;p&gt;The remaining nodes will join as workers with specific responsibilities.&lt;/p&gt;
&lt;p&gt;Install k3s on &lt;code&gt;iridium&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-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -sfL https://get.k3s.io | sh -s - server &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --node-name iridium &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --node-ip 10.10.10.24 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --advertise-address 10.10.10.24 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --write-kubeconfig-mode &lt;span style="color:#ae81ff"&gt;644&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; --disable traefik
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Run this command directly on the machine, either through SSH or a local terminal session.&lt;/p&gt;
&lt;h3 id="what-do-these-flags-do"&gt;What Do These Flags Do?
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--node-ip&lt;/code&gt; — forces a specific IP address and therefore a specific network interface.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--advertise-address&lt;/code&gt; — makes the API server reachable from the LAN.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--disable traefik&lt;/code&gt; — k3s ships with Traefik as its default ingress controller, but this cluster will use NGINX later.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--write-kubeconfig-mode 644&lt;/code&gt; — creates the kubeconfig file with world-readable permissions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&amp;rsquo;s it.&lt;/p&gt;
&lt;p&gt;Really.&lt;/p&gt;
&lt;p&gt;After installation, verify that everything is running correctly:&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;sudo systemctl status k3s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;More importantly:&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;kubectl get nodes
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;should produce something similar to:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;iridium Ready control-plane
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;An even better command is:&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;kubectl get nodes -w
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;-w&lt;/code&gt; option continuously watches for changes.&lt;/p&gt;
&lt;p&gt;This is useful because a node that repeatedly transitions between &lt;code&gt;Ready&lt;/code&gt; and some other state usually indicates an incomplete kernel configuration.&lt;/p&gt;
&lt;p&gt;The most common culprits are missing &lt;code&gt;overlay&lt;/code&gt; or &lt;code&gt;br_netfilter&lt;/code&gt; modules.&lt;/p&gt;
&lt;p&gt;If everything looks healthy, retrieve the cluster configuration:&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;cat /etc/rancher/k3s/k3s.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Copy it to your workstation.&lt;/p&gt;
&lt;p&gt;You may store it as:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;~/.kube/config
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;or keep multiple cluster configurations separately.&lt;/p&gt;
&lt;p&gt;For Bash:&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;export KUBECONFIG&lt;span style="color:#f92672"&gt;=&lt;/span&gt;~/.kube/my-cluster-config
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For Fish:&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-fish" data-lang="fish"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;set&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;-x&lt;/span&gt; KUBECONFIG ~/.kube/my-cluster-config
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Before using the file, edit the server address:&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;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;clusters&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;cluster&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;certificate-authority-data&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;server&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;https://127.0.0.1:6443&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Replace:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;127.0.0.1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;with:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;10.10.10.24
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You should now be able to run:&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;kubectl get nodes
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;directly from your workstation and obtain the same results.&lt;/p&gt;
&lt;h2 id="joining-worker-nodes"&gt;Joining Worker Nodes
&lt;/h2&gt;&lt;p&gt;The node registration token can be found on the control-plane node:&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;sudo cat /var/lib/rancher/k3s/server/node-token
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Use it on each worker node:&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;curl -sfL https://get.k3s.io | &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;K3S_URL&lt;span style="color:#f92672"&gt;=&lt;/span&gt;https://10.10.10.24:6443 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;K3S_TOKEN&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;YOUR_TOKEN_HERE&amp;#39;&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;sh -s - agent &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --node-name vanadium &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --node-ip 10.10.10.21
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This example joins &lt;code&gt;vanadium&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Remember to adjust both the node name and IP address accordingly.&lt;/p&gt;
&lt;p&gt;Run the command directly on the target machine.&lt;/p&gt;
&lt;p&gt;Once all nodes have joined, verify the cluster:&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;kubectl get nodes -o wide
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Every node should eventually report:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Ready
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Simple?&lt;/p&gt;
&lt;p&gt;Simple.&lt;/p&gt;
&lt;p&gt;A little time-consuming, perhaps, but there are no lengthy computations involved.&lt;/p&gt;
&lt;p&gt;Only now is it safe to reach for your favorite book.&lt;/p&gt;</description></item><item><title>2026-06-20</title><link>https://dev.iostream.pl/posts/2026-06-20/</link><pubDate>Sat, 20 Jun 2026 08:15:00 +0200</pubDate><guid>https://dev.iostream.pl/posts/2026-06-20/</guid><description>&lt;h1 id="what-is-emacs-and-do-i-need-it"&gt;What Is Emacs, and Do I Need It?
&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;GNU Emacs is a text editor originally created by Richard Stallman for the PDP-10 platform.&lt;/li&gt;
&lt;li&gt;GNU Emacs is a universal development environment available on Unix and Unix-like systems, Microsoft Windows, macOS, and many others.&lt;/li&gt;
&lt;li&gt;GNU Emacs is an operating system disguised as a text editor.&lt;/li&gt;
&lt;li&gt;GNU Emacs is a philosophy and a way of life.&lt;/li&gt;
&lt;li&gt;GNU Emacs is a religion.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All right, let&amp;rsquo;s start over.&lt;/p&gt;
&lt;h2 id="a-brief-history"&gt;A Brief History
&lt;/h2&gt;&lt;p&gt;Richard Stallman, while working at MIT, became increasingly frustrated with proprietary software. Not only was it often full of bugs, but there was usually little anyone outside the vendor could do to fix them. Bug reports rarely led to meaningful improvements, and users were expected to simply accept whatever they were given.&lt;/p&gt;
&lt;p&gt;Eventually, Stallman left MIT and launched the GNU Project, whose goal was to create a completely free Unix-like operating system.&lt;/p&gt;
&lt;p&gt;Emacs itself began as a collection of macros for the TECO editor. After a rapid evolution, it became a standalone application and eventually one of the most influential pieces of software in the free software ecosystem. A large community of developers, thousands of articles, books, tutorials, and decades of active use suggest that Emacs still has a long future ahead of it.&lt;/p&gt;
&lt;p&gt;Not everyone, however, was happy with the project&amp;rsquo;s leadership style. The most significant fork occurred when developers from Lucid, who wanted to use Emacs as the foundation of their development environment, split off and created Lucid Emacs. This project later evolved into XEmacs.&lt;/p&gt;
&lt;p&gt;Incidentally, the &amp;ldquo;X&amp;rdquo; in XEmacs has nothing to do with whether the editor can run under the X Window System. Both GNU Emacs and XEmacs work perfectly well with or without X11.&lt;/p&gt;
&lt;p&gt;Over the years, various other forks appeared, often for technical or political reasons. While some introduced interesting ideas, none managed to surpass GNU Emacs itself. XEmacs, despite its innovations, gradually lost momentum. A long period without significant releases and declining community activity eventually left the project effectively dormant. Its website still feels like a time capsule from the late 1990s, apparently untouched by mobile devices, TLS certificates, or modern web design.&lt;/p&gt;
&lt;h2 id="what-is-emacs"&gt;What Is Emacs?
&lt;/h2&gt;&lt;h3 id="a-text-editor"&gt;A Text Editor
&lt;/h3&gt;&lt;p&gt;At its core, Emacs is a text editor.&lt;/p&gt;
&lt;p&gt;This remains the primary goal of the project: creating an exceptionally capable environment for editing text of all kinds. Source code, notes, books, articles, documentation, LaTeX manuscripts, configuration files—everything is ultimately text, and Emacs is designed to handle all of it.&lt;/p&gt;
&lt;h3 id="a-lisp-interpreter-and-runtime-environment"&gt;A Lisp Interpreter and Runtime Environment
&lt;/h3&gt;&lt;p&gt;One of Emacs&amp;rsquo; defining features is its built-in Lisp interpreter.&lt;/p&gt;
&lt;p&gt;Through Emacs Lisp, users can extend the editor to perform virtually any task imaginable. Email clients, news readers, calendars, web browsers, file managers, games, and countless other tools are available either as part of the standard distribution or through third-party packages.&lt;/p&gt;
&lt;p&gt;Need an AI assistant? Someone has almost certainly written one already.&lt;/p&gt;
&lt;p&gt;Installing extensions is typically as simple as fetching the appropriate package from a repository and enabling it.&lt;/p&gt;
&lt;h3 id="a-development-environment"&gt;A Development Environment
&lt;/h3&gt;&lt;p&gt;For many users, Emacs serves primarily as an integrated development environment.&lt;/p&gt;
&lt;p&gt;Support exists for an astonishing number of programming languages. Syntax highlighting, code navigation, language servers, debugging tools, build systems, version control integration, documentation generation, shells, terminals, and countless other development tools are readily available.&lt;/p&gt;
&lt;p&gt;Whether you write C++, Go, Python, Rust, Lisp, or something more obscure, chances are that someone has already built excellent Emacs support for it.&lt;/p&gt;
&lt;h3 id="an-environment-for-markup-languages"&gt;An Environment for Markup Languages
&lt;/h3&gt;&lt;p&gt;Emacs is equally comfortable working with markup and document-oriented formats.&lt;/p&gt;
&lt;p&gt;XML, HTML, YAML, Markdown, Org Mode, TeX, LaTeX, DocBook, and many others are all well supported. Validation, transformation, navigation, and editing tools are often available out of the box.&lt;/p&gt;
&lt;p&gt;The live preview capabilities available for TeX and LaTeX workflows remain particularly impressive even today.&lt;/p&gt;
&lt;h3 id="anything-you-can-convince-lisp-to-do"&gt;Anything You Can Convince Lisp to Do
&lt;/h3&gt;&lt;p&gt;Ultimately, Emacs is whatever you can persuade Emacs Lisp to become.&lt;/p&gt;
&lt;p&gt;This flexibility is both its greatest strength and one of the reasons why Emacs users occasionally develop a reputation for eccentricity.&lt;/p&gt;
&lt;h2 id="what-emacs-is-not"&gt;What Emacs Is Not
&lt;/h2&gt;&lt;h3 id="a-universal-solution"&gt;A Universal Solution
&lt;/h3&gt;&lt;p&gt;Emacs is an incredibly powerful tool, but it is not magic.&lt;/p&gt;
&lt;p&gt;Installing Emacs will not automatically teach you a programming language, system administration, networking, or software architecture. It can help you work more effectively, but it cannot replace knowledge or experience.&lt;/p&gt;
&lt;h2 id="is-emacs-for-me"&gt;Is Emacs for Me?
&lt;/h2&gt;&lt;p&gt;Perhaps.&lt;/p&gt;
&lt;p&gt;Emacs may be a good fit if:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You want a consistent environment for editing, programming, writing, note-taking, and other text-oriented tasks.&lt;/li&gt;
&lt;li&gt;You enjoy highly configurable tools.&lt;/li&gt;
&lt;li&gt;You appreciate software that can evolve alongside your workflow.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Emacs may not be a good fit if:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Editing configuration files makes you uncomfortable.&lt;/li&gt;
&lt;li&gt;You expect every feature to be immediately discoverable through graphical menus.&lt;/li&gt;
&lt;li&gt;You are unwilling to invest time learning new tools.&lt;/li&gt;
&lt;li&gt;Your primary computing activity consists of scrolling through social media feeds on a phone.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Emacs has a steep learning curve, but it rewards patience. For many users it eventually becomes much more than an editor; it becomes the environment in which a significant portion of their computing life takes place.&lt;/p&gt;
&lt;p&gt;Whether that sounds exciting or alarming is entirely up to you.&lt;/p&gt;</description></item></channel></rss>