<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
	>

<channel>
	<title>I do what I can</title>
	<atom:link href="https://mteixeira.wordpress.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://mteixeira.wordpress.com</link>
	<description>Random thoughts from Mauricio Teixeira...</description>
	<lastBuildDate>Sun, 03 May 2026 23:37:20 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>http://wordpress.com/</generator>
<site xmlns="com-wordpress:feed-additions:1">8361501</site><cloud domain='mteixeira.wordpress.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
<image>
		<url>https://s0.wp.com/i/webclip.png</url>
		<title>I do what I can</title>
		<link>https://mteixeira.wordpress.com</link>
	</image>
	<atom:link rel="search" type="application/opensearchdescription+xml" href="https://mteixeira.wordpress.com/osd.xml" title="I do what I can" />
	<atom:link rel='hub' href='https://mteixeira.wordpress.com/?pushpress=hub'/>
	<item>
		<title>My 10-inch home lab rack</title>
		<link>https://mteixeira.wordpress.com/2026/05/03/my-10-inch-home-lab-rack/</link>
					<comments>https://mteixeira.wordpress.com/2026/05/03/my-10-inch-home-lab-rack/#respond</comments>
		
		<dc:creator><![CDATA[badnetmask]]></dc:creator>
		<pubDate>Sun, 03 May 2026 23:37:20 +0000</pubDate>
				<category><![CDATA[homelab]]></category>
		<category><![CDATA[3dprinting]]></category>
		<category><![CDATA[lenovo]]></category>
		<guid isPermaLink="false">http://mteixeira.wordpress.com/?p=1823</guid>

					<description><![CDATA[At some point I decided I wanted to get into the 10&#8243; home lab rack bandwagon. Not that I really needed, but I saw so many cool ideas that made me want to have a little bit of eye candy for myself. This blog post is a little bit about what I had, my thought [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">At some point I decided I wanted to get into the 10&#8243; home lab rack bandwagon. Not that I really needed, but I saw so many cool ideas that made me want to have a little bit of eye candy for myself. This blog post is a little bit about what I had, my thought process, and what I ended up with. As a teaser, here&#8217;s a picture of the (<em>maybe</em>) final build.</p>


<div class="wp-block-image">
<figure class="aligncenter size-medium"><a href="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151455876.jpg" target="_blank" rel=" noreferrer noopener"><img width="300" height="225" data-attachment-id="1826" data-permalink="https://mteixeira.wordpress.com/2026/05/03/my-10-inch-home-lab-rack/pxl_20260503_151455876/" data-orig-file="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151455876.jpg" data-orig-size="2000,1500" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="PXL_20260503_151455876" data-image-description="" data-image-caption="" data-large-file="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151455876.jpg?w=700" src="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151455876.jpg?w=300" alt="Picture of the front of the rack, mostly printed in black, with some details that resemble carbon fiber. The shelves from top to bottom: (1) a honeycomb grid with the sign that reads &quot;Lab Rax&quot; in red, (2) a 12-port patch panel with 2 HDMI + 8 RJ45 + 2 HDMI, (3) a blank honeycomb grid, (4) a tray with a Unifi US-8-60W switch, (5, 6 and 7) a tray with a Lenovo M720q with the host name on a red sticker." class="wp-image-1826" srcset="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151455876.jpg?w=300 300w, https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151455876.jpg?w=600 600w, https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151455876.jpg?w=150 150w" sizes="(max-width: 300px) 100vw, 300px" /></a></figure>
</div>


<ol><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2026/05/03/my-10-inch-home-lab-rack/#what-and-why">What and why</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2026/05/03/my-10-inch-home-lab-rack/#bom-bill-of-materials">BOM (Bill of Materials)</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2026/05/03/my-10-inch-home-lab-rack/#printing-and-initial-assembly">Printing and initial assembly</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2026/05/03/my-10-inch-home-lab-rack/#cabling-decisions">Cabling decisions</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2026/05/03/my-10-inch-home-lab-rack/#the-final-assembly">The final assembly</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2026/05/03/my-10-inch-home-lab-rack/#final-words">Final words</a></li></ol>



<h2 id="what-and-why" class="wp-block-heading">What and why</h2>



<p class="wp-block-paragraph">I follow multiple people on <a href="https://fedigroups.social/@homelab">Mastodon</a> and YouTube that are part of the home lab community (I&#8217;m not much into the Reddit community though). I also follow a lot of <a href="https://hachyderm.io/tags/homelab">hashtags</a>, and watch the eventual algorithm-pushed video (yeah, I know). For whatever reason the 10-inch racks have become more and more common, probably because more people are buying the tiny computers and/or SBC (single-board-computers) and they want to get themselves more organized, without spending too much space.</p>



<p class="wp-block-paragraph">Not wanting to spend too much space is certainly my case: I already have a standard 9U 19&#8243; rack attached to a wall somewhere in the house that holds a lot of equipment, most importantly my network gear (sorry, can&#8217;t post a picture, it would reveal way much more information than I am willing to share). The current 19&#8243; rack has (for now) <a href="https://www.printables.com/model/69176-19-1u-rack-with-moduler-trays-for-raspberry-pis">4 Raspberry Pi in one printed 1U tray</a>, which I have been slowly <a href="/2026/03/18/current-state-of-my-home-lab/">phasing out</a> in favor of a new set of <a href="https://hachyderm.io/@badnetmask/115698872870594708">Lenovo M720Q Tiny</a>. But it already is very full of other things, so I keep the Lenovos <a href="https://www.amazon.com/dp/B09MD1D3KV">in an printer stand</a> in my home office (the printer is somewhere else), so I wanted to build something compact and visually appealing.</p>



<p class="wp-block-paragraph">I did a quick <a href="https://hachyderm.io/@badnetmask/116432248383092547">pulse check on Mastodon</a>, as well as on one of the <a href="https://bitflip.show/">Discord groups</a> I hang around. And those discussions gave me lots of good ideas from <a href="https://techhub.social/@ironicbadger/116444088294340954">Alex Kretzschmar</a>, <a href="https://social.wildeboer.net/@jwildeboer/115593229274171323">Jan Wildeboer</a>, <a href="https://transitory.social/notes/adq0jup5b7">Rachel</a> and <a href="https://infosec.exchange/@Fishd/116432484168104601">Fishd</a> (plus some other people who never publicly posted their racks).</p>



<p class="wp-block-paragraph">After lots of consideration, instead of buying a pre-built rack, I decided to print my own. After all I have <a href="https://bambulab.com/en-us/x1">a very nice 3D printer</a>, and I would have to print all the trays anyway, so why not print the entire rack. In the end I decided to go with the <a href="https://makerworld.com/en/models/1464819-lab-rax-10-server-rack-bolted-version-5u">Lab Rax 10&#8243; Server Rack &#8211; Bolted Version &#8211; 5U</a>, because it looked really nice, easy to print, and has a strong community around it.</p>



<p class="wp-block-paragraph">And by the way: a shout out to my lovely wife! She was present in every single step of this project, helping me select the parts and keeping my good (bad) taste in check all the time. We had a lot of fun working on this together!</p>



<p class="wp-block-paragraph"><em>DISCLAIMER: Most of the Amazon links across this post are affiliate links that will give a portion of the purchase to <a href="https://techhub.social/@ironicbadger">Alex Kretzschmar</a>, who is partially to blame for some of the choices for this rack. I suggest you check out <a href="https://blog.ktz.me/">his blog</a> and <a href="https://bitflip.show/">his podcast</a>, it&#8217;s all good quality material. But you can buy whatever you want, wherever you want. These are the ones I got that you can use as an example for your project, since it took me a while to research everything.</em></p>



<h2 id="bom-bill-of-materials" class="wp-block-heading">BOM (Bill of Materials)</h2>



<p class="wp-block-paragraph">These are the 3D model files I used.</p>



<ul class="wp-block-list">
<li><a href="https://makerworld.com/en/models/1464819-lab-rax-10-server-rack-bolted-version-5u">Lab Rax 10&#8243; Server Rack &#8211; Bolted Version &#8211; 5U</a>
<ul class="wp-block-list">
<li>I chose the bolted one for my own convenience, but lots of people recommend <a href="https://makerworld.com/en/models/1294480-lab-rax-10-server-rack-5u">the original one with brass inserts</a>.</li>
</ul>
</li>



<li><a href="https://makerworld.com/en/models/2406553-10-inch-rack-1-2-u-keystone-panels">10 Inch Rack 1/2 U Keystone Panels</a></li>



<li><a href="https://makerworld.com/en/models/1304233-1-2u-ventilation-panel-for-10-rack">1/2U Ventilation Panel for 10&#8243; Rack</a></li>



<li><a href="https://makerworld.com/en/models/1393047-unifi-switch-us-8-60w-10-inch-rackmount">UniFi Switch US-8-60W 10 &#8211; Inch Rackmount</a>
<ul class="wp-block-list">
<li>What I like about this one the most is that they created a little cut so you can see the blinking lights (which are on the top of this switch).</li>
</ul>
</li>



<li><a href="https://makerworld.com/en/models/1795986-v2-1-1-parametric-10-inch-rack-shelf">V2.1.1 Parametric 10 inch rack shelf</a>
<ul class="wp-block-list">
<li>There are many shelf models, but I could not find one exactly the way I wanted, so I created my own using this customizable model. Here are the parameters I used.</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">And this is the list of hardware involved.</p>



<ul class="wp-block-list">
<li><a href="https://bambulab.com/en-us/x1">Bambu Lab X1 Carbon</a></li>



<li><a href="https://us.store.bambulab.com/products/petg-hf">Bambu Lab Black PETG HF (33102)</a>
<ul class="wp-block-list">
<li>Purchased from the cool people at <a href="https://ecovate3d.com/">Ecovate 3D in Raleigh</a>. If you&#8217;re in the region, they&#8217;re an awesome source for anything 3D printing related.</li>
</ul>
</li>



<li><a href="https://us.store.bambulab.com/products/bambu-3d-effect-plate">Bambu Lab Carbon Fiber 3D Effect Plate</a></li>



<li><a href="https://amzn.to/48BQjkx">M6-1x12mm Button Head Socket Cap Screws &#8211; 50Pack</a></li>



<li><a href="https://amzn.to/4d1AS6G">M6-1 (50 Pack) Hex Head Nuts</a></li>



<li><a href="https://amzn.to/4tcNEFo">10Gbps CAT7 Coupler RJ45 Keystone</a></li>



<li><a href="https://www.amazon.com/dp/B07214QPBD/ref=cm_sw_r_as_gl_api_gl_i_QVYRQP4CYRG821ESA03J?linkCode=ml1&amp;tag=alexktz-20&amp;linkId=f50e979a8f2c71847aec65a4d1a22fd1">HDMI Keystone Jack</a></li>



<li><a href="https://amzn.to/4epVdF4">8K HDMI Cable 1.5ft</a></li>



<li>I already had a lot of 1.5ft CAT6 cables in a box, so no link for those.</li>
</ul>



<h2 id="printing-and-initial-assembly" class="wp-block-heading">Printing and initial assembly</h2>



<p class="wp-block-paragraph">I wasn&#8217;t in a hurry, so I printed everything over the course of about two weeks. The frame was split in two parts, each one took about 5h30m to print, plus 2h for each of the 4 grates, then each Lenovo tray took about 7h, the UniFi tray about 5 hours, then the smallest parts about 1h each. I guess that counts for about 47h of printing time.</p>



<p class="wp-block-paragraph">I have not printed any of the top, bottom or side panels yet, because I am a bit concerned about the air flow. I&#8217;ll have to keep an eye on the temperature of each device for a while before I make a final decision. And if I do put the panels, most likely I will do <a href="https://makerworld.com/en/models/1862143-lab-rax-kumiko-style-side-panel-customizable">some kind of open panel</a>, and put <a href="https://amzn.to/428fis6">a decent fan</a> on the top one.</p>



<p class="wp-block-paragraph">On the total time count above I&#8217;m ignoring all the time I spent to <a href="https://hachyderm.io/@badnetmask/116477353258454336">dial in the PETG HF</a>. Honestly that was the hardest part of the entire project. In the end I had to create a very precise dance of printing, then washing the plate (warm water and soap) and <a href="https://amzn.to/4cXFSsN">drying the filament</a> for 3 hours before being able to print the next part.</p>



<p class="wp-block-paragraph">After a lot of sweat and tears I had the first part done.</p>



<figure data-carousel-extra='{&quot;blog_id&quot;:8361501,&quot;permalink&quot;:&quot;https://mteixeira.wordpress.com/2026/05/03/my-10-inch-home-lab-rack/&quot;}'  class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex"><div class="wp-block-image">
<figure class="aligncenter size-medium"><a href="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260429_012911995-edited.jpg" target="_blank"><img width="300" height="225" data-attachment-id="1849" data-permalink="https://mteixeira.wordpress.com/2026/05/03/my-10-inch-home-lab-rack/pxl_20260429_012911995-edited/" data-orig-file="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260429_012911995-edited.jpg" data-orig-size="1657,1243" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="PXL_20260429_012911995 (Edited)" data-image-description="" data-image-caption="" data-large-file="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260429_012911995-edited.jpg?w=700" data-id="1849" src="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260429_012911995-edited.jpg?w=300" alt="Picture of the front of the rack, fully assembled, but without any equipment." class="wp-image-1849" srcset="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260429_012911995-edited.jpg?w=300 300w, https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260429_012911995-edited.jpg?w=600 600w, https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260429_012911995-edited.jpg?w=150 150w" sizes="(max-width: 300px) 100vw, 300px" /></a></figure>
</div>

<div class="wp-block-image">
<figure class="aligncenter size-medium"><a href="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260429_012926559-edited.jpg" target="_blank"><img width="300" height="225" data-attachment-id="1848" data-permalink="https://mteixeira.wordpress.com/2026/05/03/my-10-inch-home-lab-rack/pxl_20260429_012926559-edited/" data-orig-file="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260429_012926559-edited.jpg" data-orig-size="1713,1286" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="PXL_20260429_012926559 (Edited)" data-image-description="" data-image-caption="" data-large-file="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260429_012926559-edited.jpg?w=700" data-id="1848" src="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260429_012926559-edited.jpg?w=300" alt="Picture of the side of the rack, fully assembled, but without any equipment. You can clearly see that all shelves have a honeycomb pattern, and the depth difference between the switch tray and the Lenovo trays." class="wp-image-1848" srcset="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260429_012926559-edited.jpg?w=300 300w, https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260429_012926559-edited.jpg?w=600 600w, https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260429_012926559-edited.jpg?w=150 150w" sizes="(max-width: 300px) 100vw, 300px" /></a></figure>
</div></figure>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">And before anyone calls me out: yes, the last Lenovo tray is not the same size as the others. I miscalculated and printed it 3 mm longer than it was needed, so I corrected in on the others, but didn&#8217;t care to print a new one. There&#8217;s just a small silicone drop behind the machine to prevent it from sliding.</p>



<h2 id="cabling-decisions" class="wp-block-heading">Cabling decisions</h2>



<p class="wp-block-paragraph">I&#8217;m still not sure if this is the rack&#8217;s final form, because the cabling in the back got a little bit messy. Most likely I will need to print some kind of cable organizer to hold them tidy in the back.</p>



<p class="wp-block-paragraph">But I did have to make some interesting decisions in terms of cabling that might help you understand why I bought some of the parts I listed above.</p>



<p class="wp-block-paragraph">Since this is my office, there are a few other devices connected to the switch, and that&#8217;s why I use all of the 8 ports. There&#8217;s a chance I might need more ports, so either I would need to add another 8-port switch, or buy a 16-port. Either way that would force me to review the patch panel situation, or maybe even expand the rack height (I think I can fit up to 8U in the space, and there&#8217;s also <a href="https://makerworld.com/en/models/1917602-lab-rax-0-5u-1-2u-top-corner">this cool top patch panel</a>).</p>



<p class="wp-block-paragraph">I have one single-port <a href="https://amzn.to/4thRD3B">GL.iNET Comet KVM</a>, which I use to manage the machines if they require maintenance. It is currently resting behind the switch, on top of the top-most Lenovo. So, instead of having a bunch of cables dangling from the back, or having to deal with the mess of changing ports around, I used the HDMI keystone jacks to plug the KVM input on the first port of the patch panel, then the other ports are connected to each one of the Lenovo. So, if I need to access any one of them, I just need to use an &#8220;HDMI patch cord&#8221;. One thing I would like to improve is how to manage the keyboard: for now the USB-C cable that comes out of the KVM is dangling in the back, so when I need to connect to one of the machines I just pull the cable and connect to the USB-C port in the front of the Lenovo.</p>



<p class="wp-block-paragraph">Also, I took this chance to borrow a page out of <a href="https://techhub.social/@ironicbadger/115676406869536341">Alex&#8217;s bag of tricks</a>, and I bought myself one <a href="https://amzn.to/3R0ZO6J">Anker Prime Charging Station</a>, along with the necessary <a href="https://amzn.to/4t5Ny29">USB-to-Lenovo cables</a> for a poor man&#8217;s kind of PDU, and to save a lot of space by not having to keep all those Lenovo power bricks. And honestly, that&#8217;s better than buying the <a href="https://store.ui.com/us/en/category/integrations-power-tech/collections/unifi-power-tech-power-distribution/products/usp-pdu-pro">UniFi monster PDU</a>.</p>



<h2 id="the-final-assembly" class="wp-block-heading">The final assembly</h2>



<p class="wp-block-paragraph">You have already seen the teaser, so I will give you one extra picture of the final assembly, that includes the HDMI patch cable, where you can also see some of the other peripherals attached to the lab, and get an idea about how it sits on my printer stand. Please excuse the cable mess in the back.</p>



<figure data-carousel-extra='{&quot;blog_id&quot;:8361501,&quot;permalink&quot;:&quot;https://mteixeira.wordpress.com/2026/05/03/my-10-inch-home-lab-rack/&quot;}'  class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-2 is-layout-flex wp-block-gallery-is-layout-flex"><div class="wp-block-image">
<figure class="aligncenter size-medium"><a href="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151455876.jpg" target="_blank" rel=" noreferrer noopener"><img loading="lazy" width="300" height="225" data-attachment-id="1826" data-permalink="https://mteixeira.wordpress.com/2026/05/03/my-10-inch-home-lab-rack/pxl_20260503_151455876/" data-orig-file="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151455876.jpg" data-orig-size="2000,1500" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="PXL_20260503_151455876" data-image-description="" data-image-caption="" data-large-file="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151455876.jpg?w=700" data-id="1826" src="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151455876.jpg?w=300" alt="Picture of the front of the rack, mostly printed in black, with some details that resemble carbon fiber. The shelves from top to bottom: (1) a honeycomb grid with the sign that reads &quot;Lab Rax&quot; in red, (2) a 12-port patch panel with 2 HDMI + 8 RJ45 + 2 HDMI, (3) a blank honeycomb grid, (4) a tray with a Unifi US-8-60W switch, (5, 6 and 7) a tray with a Lenovo M720q with the host name on a red sticker." class="wp-image-1826" srcset="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151455876.jpg?w=300 300w, https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151455876.jpg?w=600 600w, https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151455876.jpg?w=150 150w" sizes="(max-width: 300px) 100vw, 300px" /></a></figure>
</div>

<div class="wp-block-image">
<figure class="aligncenter size-medium"><a href="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151629417.jpg" target="_blank"><img loading="lazy" width="225" height="300" data-attachment-id="1861" data-permalink="https://mteixeira.wordpress.com/2026/05/03/my-10-inch-home-lab-rack/pxl_20260503_151629417/" data-orig-file="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151629417.jpg" data-orig-size="1500,2000" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="PXL_20260503_151629417" data-image-description="" data-image-caption="" data-large-file="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151629417.jpg?w=700" data-id="1861" src="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151629417.jpg?w=225" alt="Another picture of the rack fully assembled. In this one you can see an HDMI patch cord crossing from one port to another, as well as a USB-C cable coming from the back and into one of the Lenovo. On the shelf below the rack you can see the Anker charging station and a UniFi access point." class="wp-image-1861" srcset="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151629417.jpg?w=225 225w, https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151629417.jpg?w=450 450w, https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151629417.jpg?w=113 113w" sizes="(max-width: 225px) 100vw, 225px" /></a></figure>
</div></figure>



<h2 id="final-words" class="wp-block-heading">Final words</h2>



<p class="wp-block-paragraph">This was no doubt my most fun 3D printing project so far. It took many hours, lots of sweat and tears, but the end result is stunning, if I may say so myself.</p>



<p class="wp-block-paragraph">I hope this gives you inspiration, and you come around to print (or build) your own rack. And if you do, please share either on a blog or a Mastodon post, and let me know about it. It would make me really happy to know that I have contributed to your project one way or another.</p>



<p class="wp-block-paragraph">If you have any questions/comments/suggestion/feedback, or just would like to chat, feel free to <a href="https://hachyderm.io/@badnetmask">reach out to me on Mastodon</a>, or post a comment below. I will try my best to reply back to you with something useful.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://mteixeira.wordpress.com/2026/05/03/my-10-inch-home-lab-rack/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1823</post-id>
		<media:content url="https://2.gravatar.com/avatar/be97b85b50b810ababecb8b92f87be16dfd6bd605465ecf97707b3d363e8fef3?s=96&#38;d=https%3A%2F%2F2.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=PG" medium="image">
			<media:title type="html">netmask</media:title>
		</media:content>

		<media:content url="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151455876.jpg?w=300" medium="image">
			<media:title type="html">Picture of the front of the rack, mostly printed in black, with some details that resemble carbon fiber. The shelves from top to bottom: (1) a honeycomb grid with the sign that reads &#034;Lab Rax&#034; in red, (2) a 12-port patch panel with 2 HDMI + 8 RJ45 + 2 HDMI, (3) a blank honeycomb grid, (4) a tray with a Unifi US-8-60W switch, (5, 6 and 7) a tray with a Lenovo M720q with the host name on a red sticker.</media:title>
		</media:content>

		<media:content url="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260429_012911995-edited.jpg?w=300" medium="image">
			<media:title type="html">Picture of the front of the rack, fully assembled, but without any equipment.</media:title>
		</media:content>

		<media:content url="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260429_012926559-edited.jpg?w=300" medium="image">
			<media:title type="html">Picture of the side of the rack, fully assembled, but without any equipment. You can clearly see that all shelves have a honeycomb pattern, and the depth difference between the switch tray and the Lenovo trays.</media:title>
		</media:content>

		<media:content url="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151455876.jpg?w=300" medium="image">
			<media:title type="html">Picture of the front of the rack, mostly printed in black, with some details that resemble carbon fiber. The shelves from top to bottom: (1) a honeycomb grid with the sign that reads &#034;Lab Rax&#034; in red, (2) a 12-port patch panel with 2 HDMI + 8 RJ45 + 2 HDMI, (3) a blank honeycomb grid, (4) a tray with a Unifi US-8-60W switch, (5, 6 and 7) a tray with a Lenovo M720q with the host name on a red sticker.</media:title>
		</media:content>

		<media:content url="https://mteixeira.wordpress.com/wp-content/uploads/2026/05/pxl_20260503_151629417.jpg?w=225" medium="image">
			<media:title type="html">Another picture of the rack fully assembled. In this one you can see an HDMI patch cord crossing from one port to another, as well as a USB-C cable coming from the back and into one of the Lenovo. On the shelf below the rack you can see the Anker charging station and a UniFi access point.</media:title>
		</media:content>
	</item>
		<item>
		<title>VS Code Multi-root Workspaces with Devcontainers</title>
		<link>https://mteixeira.wordpress.com/2026/04/07/vs-code-multi-root-workspaces-with-devcontainers/</link>
					<comments>https://mteixeira.wordpress.com/2026/04/07/vs-code-multi-root-workspaces-with-devcontainers/#respond</comments>
		
		<dc:creator><![CDATA[badnetmask]]></dc:creator>
		<pubDate>Tue, 07 Apr 2026 17:06:01 +0000</pubDate>
				<category><![CDATA[tutorial]]></category>
		<category><![CDATA[code]]></category>
		<category><![CDATA[coding]]></category>
		<category><![CDATA[containers]]></category>
		<category><![CDATA[copilot]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[forgejo]]></category>
		<category><![CDATA[git]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[podman]]></category>
		<category><![CDATA[vscode]]></category>
		<guid isPermaLink="false">http://mteixeira.wordpress.com/?p=1730</guid>

					<description><![CDATA[I heavily use Visual Studio Code both professionally and for my personal projects. For a long time I have been using Dev Containers, and since recently I started using Multi-root Workspaces. Even more recently I decided to figure out a way to use both at the same time, but it was really hard to find [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I heavily use Visual Studio Code both professionally and for my personal projects. For a long time I have been using Dev Containers, and since recently I started using Multi-root Workspaces. Even more recently I decided to figure out a way to use both at the same time, but it was really hard to find a tutorial anywhere, so I decided to write this blog post. As a teaser, here&#8217;s a screenshot of the final result, after you follow the steps described here.</p>



<figure class="wp-block-image size-large"><a href="https://mteixeira.wordpress.com/wp-content/uploads/2026/04/screenshot-from-2026-04-07-09-02-07-1.png"><img loading="lazy" width="1024" height="208" data-attachment-id="1769" data-permalink="https://mteixeira.wordpress.com/2026/04/07/vs-code-multi-root-workspaces-with-devcontainers/screenshot-from-2026-04-07-09-02-07-2/" data-orig-file="https://mteixeira.wordpress.com/wp-content/uploads/2026/04/screenshot-from-2026-04-07-09-02-07-1.png" data-orig-size="2264,462" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}" data-image-title="Screenshot From 2026-04-07 09-02-07" data-image-description="" data-image-caption="" data-large-file="https://mteixeira.wordpress.com/wp-content/uploads/2026/04/screenshot-from-2026-04-07-09-02-07-1.png?w=700" src="https://mteixeira.wordpress.com/wp-content/uploads/2026/04/screenshot-from-2026-04-07-09-02-07-1.png?w=1024" alt="A partial screenshot of VS Code showing the end result after you follow the steps in this blog post. On the left, in the file explorer, you can see all the repos in the workspace. On the middle column you can see the top of the devcontainer.json file with the repos mounted, and on the right you can see the top of the code-workspace file with the repo listing." class="wp-image-1769" srcset="https://mteixeira.wordpress.com/wp-content/uploads/2026/04/screenshot-from-2026-04-07-09-02-07-1.png?w=1024 1024w, https://mteixeira.wordpress.com/wp-content/uploads/2026/04/screenshot-from-2026-04-07-09-02-07-1.png?w=2048 2048w, https://mteixeira.wordpress.com/wp-content/uploads/2026/04/screenshot-from-2026-04-07-09-02-07-1.png?w=150 150w, https://mteixeira.wordpress.com/wp-content/uploads/2026/04/screenshot-from-2026-04-07-09-02-07-1.png?w=300 300w, https://mteixeira.wordpress.com/wp-content/uploads/2026/04/screenshot-from-2026-04-07-09-02-07-1.png?w=768 768w, https://mteixeira.wordpress.com/wp-content/uploads/2026/04/screenshot-from-2026-04-07-09-02-07-1.png?w=1440 1440w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<ol><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2026/04/07/vs-code-multi-root-workspaces-with-devcontainers/#introduction">Introduction</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2026/04/07/vs-code-multi-root-workspaces-with-devcontainers/#choosing-where-to-store-the-files">Choosing where to store the files</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2026/04/07/vs-code-multi-root-workspaces-with-devcontainers/#initializing-the-workspace-1">Initializing the workspace the &#8220;clickOps&#8221; way</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2026/04/07/vs-code-multi-root-workspaces-with-devcontainers/#initializing-the-workspace-the-coding-way">Initializing the workspace the &#8220;coding&#8221; way</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2026/04/07/vs-code-multi-root-workspaces-with-devcontainers/#initializing-the-dev-container">Initializing the Dev Container</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2026/04/07/vs-code-multi-root-workspaces-with-devcontainers/#adding-the-other-repos-to-the-dev-container">Adding the other repos to the Dev Container</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2026/04/07/vs-code-multi-root-workspaces-with-devcontainers/#make-it-happen">Make it happen</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2026/04/07/vs-code-multi-root-workspaces-with-devcontainers/#a-note-about-github-copilot">A note about GitHub Copilot</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2026/04/07/vs-code-multi-root-workspaces-with-devcontainers/#conclusion">Conclusion</a></li></ol>



<h2 id="introduction" class="wp-block-heading">Introduction</h2>



<p class="wp-block-paragraph">Let&#8217;s get one thing out of the way: one of the many reasons I use all these features is because of <a href="https://docs.github.com/en/copilot">GitHub Copilot</a>. The more context you provide to it, the more precise it will be (I added a note about that at the end of the post). There are multiple other reasons why someone could make use of this feature so, in my humble opinion, you should not disregard this blog post just because of this stated fact. I just wanted to put this piece of information out in the open.</p>



<p class="wp-block-paragraph">And another important thing: this blog was not AI generated. It&#8217;s 100% human written.</p>



<p class="wp-block-paragraph">With all that said, let me give you more context about this post: I heavily use <a href="https://code.visualstudio.com/">Visual Studio Code</a> both professionally and for my personal projects (for short: <em>VS Code</em> or simply <em>Code</em>). For a long time I have been using <a href="https://code.visualstudio.com/docs/devcontainers/containers">Dev Containers</a> for many reasons, but the two main ones are: isolation from my base OS and reproducibility (in case I have to rebuild or jump to a different machine). A couple of months ago I discovered the existence of <a href="https://code.visualstudio.com/docs/editing/workspaces/multi-root-workspaces">Multi-root Workspaces</a>, and it was clearly something I have been missing for a long time (even though it&#8217;s not a new feature).</p>



<p class="wp-block-paragraph">Both at work and at home I need to deal with multiple repositories. Either because each repository stores a different piece/aspect of the larger project I am working on, or because I need to cross-reference different repositories to use them to build something else. Using Multi-root Workspaces makes my life easier because I can put multiple repositories in the same place/view, without having to mix them up in the filesystem or removing their &#8220;independence&#8221;. I have worked with <a href="https://git-scm.com/book/en/v2/Git-Tools-Submodules">Git submodules</a> before, and it was a real mess (for me).</p>



<h2 id="choosing-where-to-store-the-files" class="wp-block-heading">Choosing where to store the files</h2>



<p class="wp-block-paragraph">Both workspaces and dev containers require configuration files that can be tracked via Git. I find it a good exercise to do so, because it gives me a history of the changes made and, as I said before, makes it easier to move to a different machine. So the first thing you need to do is choose where you&#8217;re going to store your configuration files. You have two choices, and I will give my personal take on each one of them:</p>



<ol class="wp-block-list">
<li><strong>One of the existing repositories &#8211;</strong> this might be a good choice if you want to make your life simpler, or if all of the other repositories in the same workspace are direct dependencies to the one you&#8217;re working at.</li>



<li><strong>A brand new repository &#8211;</strong> I use this one because, for me, this is the cleanest way, and allows me to shift things around. Think of it as a desk: you can change everything on top of the desk based on what you&#8217;re working on, but the way you arrange your space remains the same no matter which task you&#8217;re performing.</li>
</ol>



<p class="wp-block-paragraph">With that in mind, this is how I organized the example directories for this blog post:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
~/tmp/blog $ tree
.
├── cloned-repo1
├── cloned-repo2
├── homelab
├── homelab-ansible
├── homelab-kubernetes
├── homelab-terraform
└── personal-project

8 directories, 0 files
</pre></div>


<p class="wp-block-paragraph">I chose &#8220;homelab&#8221; as my place to store the configuration files. So I went ahead and created the repository on my <a href="https://mteixeira.wordpress.com/2025/02/03/my-self-hosted-forgejo-runner-setup/">self-hosted Forgejo</a>, then cloned it locally. All the other directories are examples of cloned repositories which I am already working on (names changed for demonstration purposes).</p>



<h2 id="initializing-the-workspace-1" class="wp-block-heading">Initializing the workspace the &#8220;clickOps&#8221; way</h2>



<p class="wp-block-paragraph">The workspace can be initialized by clicking around on the menus, or manually (like I will explain below). If you want to do this by clicking on the menus:</p>



<ol class="wp-block-list">
<li>Open your main/configuration directory in VS Code (in my case it&#8217;s &#8220;<code>homelab</code>&#8220;). </li>



<li>Go to &#8220;<code>File</code>&#8221; -&gt; &#8220;<code>Add Folder to Workspace...</code>&#8220;, select the folder, then click &#8220;<code>Add</code>&#8220;.</li>



<li>As soon as you add the first folder you will see the top of the explorer window will switch to &#8220;<code>Untitled (Workspace)</code>&#8220;.</li>



<li>Repeat adding all the other folders.</li>



<li>When done, go to &#8220;<code>File</code>&#8221; -&gt; &#8220;<code>Save Workspace As...</code>&#8220;, make sure you are on the first directory/folder you selected, then give it a file name, which will be your new workspace name.</li>
</ol>



<p class="wp-block-paragraph">If at this point VS Code asks if you want to trust the workspace, you have to, otherwise the Dev Containers won&#8217;t work.</p>



<h2 id="initializing-the-workspace-the-coding-way" class="wp-block-heading">Initializing the workspace the &#8220;coding&#8221; way</h2>



<p class="wp-block-paragraph">If you want to write code to initialize workspace, this is the way.</p>



<p class="wp-block-paragraph">Open your main/configuration directory in VS Code (in my case it&#8217;s &#8220;<code>homelab</code>&#8220;). Right-click on an empty space in the explorer window then select &#8220;<code>New File...</code>&#8220;. Create a new file named &#8220;<code>homelab.code-workspace</code>&#8221; (the name doesn&#8217;t really matter, as long as it ends with &#8220;<code>.code-workspace</code>&#8220;). Inside that file you will add the following content:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
{
  "folders": &#91;
    { "name": "homelab", "path": "." },
    { "name": "cloned-repo1", "path": "../cloned-repo1" },
    { "name": "cloned-repo2", "path": "../cloned-repo2" },
    { "name": "homelab-ansible", "path": "../homelab-ansible" },
    { "name": "homelab-kubernetes", "path": "../homelab-kubernetes" },
    { "name": "homelab-terraform", "path": "../homelab-terraform" },
    { "name": "personal-project", "path": "../personal-project" }
  ]
}
</pre></div>


<p class="wp-block-paragraph">Notice that the paths here are all &#8220;<code>../</code>&#8221; because in my example everything is under the same parent directory. It doesn&#8217;t really matter. You can simply point the path to anywhere else you see fit (the variables &#8220;<code>${userHome}</code>&#8221; or &#8220;<code>${env:HOME}</code>&#8221; also work).</p>



<p class="wp-block-paragraph">Also, the folder names are just how they will appear in your explorer window, so they can be anything you want. You can use, for example &#8220;<code>github/cloned-repo1</code>&#8221; or &#8220;<code>gitlab/cloned-repo2</code>&#8220;</p>



<p class="wp-block-paragraph">As soon as you save the file you will notice a &#8220;Open Workspace&#8221; button on the bottom right corner. You can click on it. If the button is gone or you can find it, go to the menu &#8220;<code>File -&gt; Open Workspace from File...</code>&#8221; and select the file you just created.</p>



<p class="wp-block-paragraph">VS Code will reopen in workspace mode, and you will see the list of all the directories you just told it to map. It will also ask if you trust this workspace. You have to, otherwise the Dev Containers won&#8217;t work.</p>



<h2 id="initializing-the-dev-container" class="wp-block-heading">Initializing the Dev Container</h2>



<ol class="wp-block-list">
<li>At this point you should have your workspace open. Make sure you have the workspace file selected in the file explorer. This is important to make sure the Dev Container will be created in the correct place.</li>



<li>Now hit &#8220;<code>CTRL+SHIFT+P</code>&#8221; and search for &#8220;<code>Dev Containers: Add Dev Container Configuration Files...</code>&#8220;.</li>



<li>VS Code will complain that it can&#8217;t open your other folders, but that&#8217;s not a problem. We will tell it how to open them inside the container later, so just click &#8220;<code>Continue</code>&#8220;.</li>



<li>On the next dialog select &#8220;<code>Add Configuration to Workspace</code>&#8220;.</li>



<li>The next dialog will ask for a template file. I decided to go with &#8220;<code>Ubuntu</code>&#8220;, for simplicity. I actually use a custom-built container, but that&#8217;s a conversation for a different blog post.</li>



<li>On the next dialog select &#8220;<code>noble</code>&#8221; (that will install Ubuntu 24.04).</li>



<li>And on the following &#8220;<code>Select Features</code>&#8221; drop down you can select whatever you want, or nothing. When done, just click &#8220;<code>OK</code>&#8220;.</li>



<li>The last dialog will confirm if you want to include &#8220;<code>.github/dependabot.yaml</code>&#8220;, which is a good practice if you are hosting in GitHub. Go with it. One more file won&#8217;t make any difference. In my case <a href="https://mteixeira.wordpress.com/2025/02/16/running-renovate-on-self-hosted-forgejo/">I am using Renovate</a>, so it doesn&#8217;t make any difference either. When done, click &#8220;<code>OK</code>&#8220;.</li>
</ol>



<p class="wp-block-paragraph"><strong>DO NOT CLICK</strong> the small popup on the bottom right corner of your screen saying that VS Code detected a Dev Container, and asking if you want to re-open the workspace using it. The configuration is not ready yet. You may, if you want, click on the little &#8220;<code>X</code>&#8221; at the corner of the dialog to close it.</p>



<h2 id="adding-the-other-repos-to-the-dev-container" class="wp-block-heading">Adding the other repos to the Dev Container</h2>



<p class="wp-block-paragraph">This is the &#8220;not so fun&#8221; part, because it requires some manual configuration. It so happens that, because of the isolation that we so much want, you need to tell the container where the other repositories are. For the purposes of this blog they are all in the same parent directory, but in practice it does not matter at all. They could be anywhere in your filesystem, as long as they are accessible by the container during startup time. So, in the &#8220;<code>decontainer.json</code>&#8221; file, you need to add the configuration lines below. Anywhere inside the brackets should be fine (just make sure you add the appropriate comma where it&#8217;s needed, after all this is just a JSON file).</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
	"mounts": &#91;
		"source=${localWorkspaceFolder}/../cloned-repo1,target=/workspaces/cloned-repo1,type=bind",
		"source=${localWorkspaceFolder}/../cloned-repo2,target=/workspaces/cloned-repo2,type=bind",
		"source=${localWorkspaceFolder}/../homelab-ansible,target=/workspaces/homelab-ansible,type=bind",
		"source=${localWorkspaceFolder}/../homelab-kubernetes,target=/workspaces/homelab-kubernetes,type=bind",
		"source=${localWorkspaceFolder}/../homelab-terraform,target=/workspaces/homelab-terraform,type=bind",
		"source=${localWorkspaceFolder}/../personal-project,target=/workspaces/personal-project,type=bind"
	]
</pre></div>


<p class="wp-block-paragraph">There are a few things you should notice:</p>



<ul class="wp-block-list">
<li>You obviously don&#8217;t need to specify the current repo because that&#8217;s the container&#8217;s root filesystem, so it will be mounted automatically.</li>



<li>The special variable &#8220;<code>${localWorkspaceFolder}</code>&#8221; is a type of &#8220;<code>pwd</code>&#8220;. It just replaces with the current full path of where the workspace file is saved (more on that later). In this case the &#8220;<code>../</code>&#8221; points to the fact that all directories are under the same parent. You can simply point the source to anywhere else you see fit (the variables &#8220;<code>${userHome}</code>&#8221; or &#8220;<code>${env:HOME}</code>&#8221; also work).</li>



<li>The target name after &#8220;<code>/workspaces</code>&#8221; is how the directory will show up inside your container, so it can be anything you want. You can use, for example &#8220;<code>github/cloned-repo1</code>&#8221; or &#8220;gitlab/cloned-repo2&#8221;. Just make sure the &#8220;<code>target</code>&#8221; here actually matches the &#8220;<code>path</code>&#8221; specified in the workspace definition.</li>
</ul>



<h2 id="make-it-happen" class="wp-block-heading">Make it happen</h2>



<p class="wp-block-paragraph">Now hit &#8220;<code>CTRL+SHIFT+P</code>&#8221; and select &#8220;<code>Dev Containers: Rebuild and Reopen in Container...</code>&#8220;. You will be presented with a very ominous message complaining that the container might not work because of the folder structure. That&#8217;s fine. The container will mount the things that need to be mounted. Just click &#8220;<code>Continue</code>&#8220;.</p>



<p class="wp-block-paragraph">At this point it will use Docker (or Podman) to build a new container. Since there isn&#8217;t a lot going on, it should be really fast, but if it&#8217;s taking a while there is a small &#8220;<code>show log</code>&#8221; dialog down at the bottom right.</p>



<p class="wp-block-paragraph"><em>Et voilà!</em> At this point your workspace will be open in a Dev Container, with all your other repos, safely isolated from your main OS.</p>



<h2 id="a-note-about-github-copilot" class="wp-block-heading">A note about GitHub Copilot</h2>



<p class="wp-block-paragraph">As I mentioned, one of the reasons why I am doing this is to be able to provide a lot of context to Copilot, so let me tell you a bit more about that.</p>



<ul class="wp-block-list">
<li>You can ask Copilot to search and corelate code across all the repositories.</li>



<li>If one or more repositories have &#8220;<code>copilot-instructions.md</code>&#8221; files, it will concatenate all of them and use as part of your prompts.</li>



<li>If one or more repositories have agents or skills files, it will use them when working with files inside those particular directories.</li>



<li>If you want agents or skills files that will work across the workspace, just add it to the top-level directory, and make a note on the instructions file to use them when working on the other repositories. They will be all concatenated (or at least that&#8217;s how it seems to be working for me).</li>
</ul>



<h2 id="conclusion" class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">I don&#8217;t know if there are better ways to achieve this, but this is the only way I could figure it out since, like I said, I could not find any blog/video about trying to do the same thing. But so far it has been working really well for me.</p>



<p class="wp-block-paragraph">I sincerely hope that you can make good use of everything that was demonstrated here!</p>



<p class="wp-block-paragraph">If you have any questions/comments (criticism?) or just would like to chat, feel free to&nbsp;<a href="https://hachyderm.io/@badnetmask">reach out to me on Mastodon</a>, or post a comment below. I will try my best to reply back to you with something useful.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://mteixeira.wordpress.com/2026/04/07/vs-code-multi-root-workspaces-with-devcontainers/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1730</post-id>
		<media:content url="https://2.gravatar.com/avatar/be97b85b50b810ababecb8b92f87be16dfd6bd605465ecf97707b3d363e8fef3?s=96&#38;d=https%3A%2F%2F2.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=PG" medium="image">
			<media:title type="html">netmask</media:title>
		</media:content>

		<media:content url="https://mteixeira.wordpress.com/wp-content/uploads/2026/04/screenshot-from-2026-04-07-09-02-07-1.png?w=1024" medium="image">
			<media:title type="html">A partial screenshot of VS Code showing the end result after you follow the steps in this blog post. On the left, in the file explorer, you can see all the repos in the workspace. On the middle column you can see the top of the devcontainer.json file with the repos mounted, and on the right you can see the top of the code-workspace file with the repo listing.</media:title>
		</media:content>
	</item>
		<item>
		<title>Current state of my home lab</title>
		<link>https://mteixeira.wordpress.com/2026/03/18/current-state-of-my-home-lab/</link>
					<comments>https://mteixeira.wordpress.com/2026/03/18/current-state-of-my-home-lab/#respond</comments>
		
		<dc:creator><![CDATA[badnetmask]]></dc:creator>
		<pubDate>Thu, 19 Mar 2026 01:18:27 +0000</pubDate>
				<category><![CDATA[homelab]]></category>
		<category><![CDATA[azure]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[framework]]></category>
		<category><![CDATA[kubernetes]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[proxmox]]></category>
		<category><![CDATA[raspberrypi]]></category>
		<category><![CDATA[talos]]></category>
		<category><![CDATA[unifi]]></category>
		<guid isPermaLink="false">http://mteixeira.wordpress.com/?p=1715</guid>

					<description><![CDATA[I&#8217;m planning to downsize, so I guess this is as good time as any to document the current state of my home lab. I suppose the diagram below is self-explanatory, and I don&#8217;t want to give much details about what I am running. The point of this diagram is just to demonstrate the wide mix [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I&#8217;m planning to downsize, so I guess this is as good time as any to document the current state of my home lab. I suppose the diagram below is self-explanatory, and I don&#8217;t want to give much details about what I am running.</p>



<p class="wp-block-paragraph">The point of this diagram is just to demonstrate the wide mix of technologies that I use to run my various tests, as well as my internal productivity workloads. I use different hardware, software and cloud platforms, all managed with a mix of FluxCD, Terraform, Ansible and good old Bash scripts, all with as much as GitOps as possible.</p>



<p class="wp-block-paragraph">For the time being, I am planning to trim down a bit, because I have a lot of unused capacity.</p>



<p class="wp-block-paragraph">I hope this gives you some ideas for your next adventures. And if you need any help with anything described here, feel free to ask your question in the comments below, or <a href="https://hachyderm.io/@badnetmask">reach out to me on Mastodon</a>!</p>



<figure class="wp-block-image size-large"><a href="https://mteixeira.wordpress.com/wp-content/uploads/2026/03/homelab-dark-3.png"><img loading="lazy" width="1024" height="633" data-attachment-id="1727" data-permalink="https://mteixeira.wordpress.com/2026/03/18/current-state-of-my-home-lab/homelab-dark-4/" data-orig-file="https://mteixeira.wordpress.com/wp-content/uploads/2026/03/homelab-dark-3.png" data-orig-size="4800,2970" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="homelab-dark" data-image-description="" data-image-caption="" data-large-file="https://mteixeira.wordpress.com/wp-content/uploads/2026/03/homelab-dark-3.png?w=700" src="https://mteixeira.wordpress.com/wp-content/uploads/2026/03/homelab-dark-3.png?w=1024" alt="" class="wp-image-1727" srcset="https://mteixeira.wordpress.com/wp-content/uploads/2026/03/homelab-dark-3.png?w=1024 1024w, https://mteixeira.wordpress.com/wp-content/uploads/2026/03/homelab-dark-3.png?w=2048 2048w, https://mteixeira.wordpress.com/wp-content/uploads/2026/03/homelab-dark-3.png?w=150 150w, https://mteixeira.wordpress.com/wp-content/uploads/2026/03/homelab-dark-3.png?w=300 300w, https://mteixeira.wordpress.com/wp-content/uploads/2026/03/homelab-dark-3.png?w=768 768w, https://mteixeira.wordpress.com/wp-content/uploads/2026/03/homelab-dark-3.png?w=1440 1440w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>
]]></content:encoded>
					
					<wfw:commentRss>https://mteixeira.wordpress.com/2026/03/18/current-state-of-my-home-lab/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1715</post-id>
		<media:content url="https://2.gravatar.com/avatar/be97b85b50b810ababecb8b92f87be16dfd6bd605465ecf97707b3d363e8fef3?s=96&#38;d=https%3A%2F%2F2.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=PG" medium="image">
			<media:title type="html">netmask</media:title>
		</media:content>

		<media:content url="https://mteixeira.wordpress.com/wp-content/uploads/2026/03/homelab-dark-3.png?w=1024" medium="image" />
	</item>
		<item>
		<title>Booting Talos on a Proxmox VM</title>
		<link>https://mteixeira.wordpress.com/2026/02/01/booting-talos-on-a-proxmox-vm/</link>
					<comments>https://mteixeira.wordpress.com/2026/02/01/booting-talos-on-a-proxmox-vm/#respond</comments>
		
		<dc:creator><![CDATA[badnetmask]]></dc:creator>
		<pubDate>Sun, 01 Feb 2026 18:07:37 +0000</pubDate>
				<category><![CDATA[homelab]]></category>
		<category><![CDATA[hardware]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[kubernetes]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[talos]]></category>
		<guid isPermaLink="false">http://mteixeira.wordpress.com/?p=1691</guid>

					<description><![CDATA[A while ago I had issues with enabling secure boot with Talos Linux on a Framework Desktop, so I posted a blog about it. Then, after a while, I had similar problems with the Lenovo M720Q Tiny, so I blogged about it as well. Today I wanted to spin up a small control-plane-only node on [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">A while ago I had issues with enabling secure boot with <a href="https://www.talos.dev/">Talos Linux</a> on a Framework Desktop, <a href="https://mteixeira.wordpress.com/2025/09/28/booting-talos-linux-on-a-framework-desktop/">so I posted a blog about it</a>. Then, after a while, I had similar problems with the Lenovo M720Q Tiny, <a href="https://mteixeira.wordpress.com/2025/12/26/booting-talos-linux-on-a-lenovo-m720q-tiny/">so I blogged about it as well</a>. Today I wanted to spin up a small control-plane-only node on my <a href="https://www.proxmox.com/">Proxmox cluster</a>, and again it was not obvious how to enable secure boot, so this is what this blog post is about.</p>



<ol><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2026/02/01/booting-talos-on-a-proxmox-vm/#introduction">Introduction</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2026/02/01/booting-talos-on-a-proxmox-vm/#note-about-creating-the-vm">Note about creating the VM</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2026/02/01/booting-talos-on-a-proxmox-vm/#the-boot-problem">The boot problem</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2026/02/01/booting-talos-on-a-proxmox-vm/#configuring-the-secure-boot">Configuring the secure boot</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2026/02/01/booting-talos-on-a-proxmox-vm/#conclusion">Conclusion</a></li></ol>



<h2 id="introduction" class="wp-block-heading">Introduction</h2>



<p class="wp-block-paragraph">I&#8217;ve been running my Talos Linux Kubernetes cluster with 3 nodes (1 Framework Desktop + 2 Lenovo M720Q Tiny), but I&#8217;ve been thinking about making some changes to the topology (which I will blog about later), so that required me to spin up a new control-plane node. In order to free up some hardware, I decided to do it as a VM on a Proxmox cluster.</p>



<p class="wp-block-paragraph">DISCLAIMERS:</p>



<ul class="wp-block-list">
<li>No AI or LLM was used during the research of this problem, or for writing this blog post.
<ul class="wp-block-list">
<li>I did, however, copy and paste some text from my previous post.</li>
</ul>
</li>



<li>The content here is provided AS IS, without any warranties that it will work for you or will not damage your computer.</li>



<li>This is purely a demonstration of MY personal experience, with the expectations that it will help someone else.</li>



<li>Please, don’t blame me if this bricks your computer or cause any data corruption on your Proxmox installation.</li>
</ul>



<h2 id="note-about-creating-the-vm" class="wp-block-heading">Note about creating the VM</h2>



<p class="wp-block-paragraph">I just wanted to make a few observations about the process of creating the VM. <a href="https://docs.siderolabs.com/talos/v1.12/platform-specific-installations/virtualized-platforms/proxmox">Talos already provides some good documentation</a>, but it seems like this was targeted at Proxmox 8.x, while I am using 9.x. This means some of the options moved to different locations, which is fine, you will probably will figure those out. However I had to make some changes to make this &#8220;perfectly right&#8221; (or at least the way I wanted):</p>



<ol class="wp-block-list">
<li>The document is not clear about the disk size. The <a href="https://docs.siderolabs.com/talos/v1.12/getting-started/system-requirements">System Requirements</a> page indicates a minimum of 10GiB for a control-plane is required, but that did not work for me. When bootstrapping Cilium, I ran out of EPHEMERAL space, so I increased the disk to 32GiB as suggested by default (it&#8217;s thin-provision anyway), and set the ephemeral disk to 20GiB.</li>



<li>The document also mentions that your boot image should contain the QEMU guest agent, but it doesn&#8217;t mention the fact that you have to actually click on the &#8220;Qemy Agent&#8221; box, next to the &#8220;Machine&#8221; type, when creating the VM.</li>
</ol>



<h2 id="the-boot-problem" class="wp-block-heading">The boot problem</h2>



<p class="wp-block-paragraph">Similar to the previous problems with the <a href="https://mteixeira.wordpress.com/2025/09/28/booting-talos-linux-on-a-framework-desktop/">Framework</a> and the <a href="https://mteixeira.wordpress.com/2025/12/26/booting-talos-linux-on-a-lenovo-m720q-tiny/">Lenovo</a>, as soon as I booted I was presented with this very &#8220;user friendly&#8221; error message:</p>



<pre class="wp-block-preformatted">BdsDXE: failed to load Boot0002 "UEFI QEMU DVD-ROM QM00003 " from PciRoot(0x0)/Pci (0x1F,0x2)/Sata(0x1,0xFFFF,0x0) : Access Denied</pre>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">I didn&#8217;t worry because I knew exactly what this was about.</p>



<h2 id="configuring-the-secure-boot" class="wp-block-heading">Configuring the secure boot</h2>



<p class="wp-block-paragraph">Knowing what to do, I just had to find the correct sequence in Proxmox.</p>



<ol class="wp-block-list">
<li>Reset the VM.</li>



<li>When you see the Proxmox logo hit ESC.</li>



<li>You will see the &#8220;Please select boot device&#8221; menu, from there you select &#8220;EFI Firmware Setup&#8221;.</li>



<li>The you go &#8220;Device Manager&#8221; -&gt; &#8220;Secure Boot Configuration&#8221;.</li>



<li>Set the &#8220;Secure Boot Mode&#8221; to &#8220;Custom Mode&#8221;, then select &#8220;Custom Secure Boot Options&#8221;.</li>



<li>Select &#8220;KEK Options&#8221; -&gt; &#8220;Enroll KEK&#8221; -&gt; &#8220;Enroll KEK using File&#8221; -&gt; select the CDROM device -&gt; &#8220;EFI&#8221; -&gt; &#8220;keys&#8221; -&gt; &#8220;uki-signing-cert.der&#8221; -&gt; &#8220;Commit Changes and Exit&#8221;.</li>



<li>Same thing for &#8220;DB Options&#8221; -&gt; &#8220;Enroll Signature&#8221; -&gt; &#8220;Enroll Signature Using File&#8221; -&gt; select the CDROM device -&gt; &#8220;EFI&#8221; -&gt; &#8220;keys&#8221; -&gt; &#8220;uki-signing-cert.der&#8221; -&gt; &#8220;Commit Changes and Exit&#8221;.</li>



<li>Now hit ESC until you see &#8220;Reset&#8221;.</li>



<li>The VM will reboot straight into the CD/DVD.</li>
</ol>



<p class="wp-block-paragraph">At this point you should be able to <a href="https://www.talos.dev/v1.12/talos-guides/install/bare-metal-platforms/secureboot/">install Talos as usual</a>, and have a fully functional system.</p>



<h2 id="conclusion" class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">I still have to contact someone on Sidero Labs, or rummage through their forums, to understand why this problem exists in every single hardware. Clearly they&#8217;re not signing their images with a key that is accepted by the BIOS vendors, I just don&#8217;t understand why, and if they have plans to fix that or not.</p>



<p class="wp-block-paragraph">Anyway. I hope this has been useful for you! And if you need any help with anything described here, feel free to ask your question in the comments below, or <a href="https://hachyderm.io/@badnetmask">reach out to me on Mastodon</a>!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://mteixeira.wordpress.com/2026/02/01/booting-talos-on-a-proxmox-vm/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1691</post-id>
		<media:content url="https://2.gravatar.com/avatar/be97b85b50b810ababecb8b92f87be16dfd6bd605465ecf97707b3d363e8fef3?s=96&#38;d=https%3A%2F%2F2.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=PG" medium="image">
			<media:title type="html">netmask</media:title>
		</media:content>
	</item>
		<item>
		<title>Booting Talos Linux on a Lenovo M720Q Tiny</title>
		<link>https://mteixeira.wordpress.com/2025/12/26/booting-talos-linux-on-a-lenovo-m720q-tiny/</link>
					<comments>https://mteixeira.wordpress.com/2025/12/26/booting-talos-linux-on-a-lenovo-m720q-tiny/#comments</comments>
		
		<dc:creator><![CDATA[badnetmask]]></dc:creator>
		<pubDate>Fri, 26 Dec 2025 16:09:59 +0000</pubDate>
				<category><![CDATA[homelab]]></category>
		<category><![CDATA[hardware]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[kubernetes]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[talos]]></category>
		<guid isPermaLink="false">http://mteixeira.wordpress.com/?p=1675</guid>

					<description><![CDATA[A couple of weeks ago I acquired 3 x Lenovo M720Q Tiny, which I wanted to add as extra nodes to my home lab Talos Linux Kubernetes cluster. They also has UEFI secure boot problems, just like my Framework Desktop had a couple of months ago. So I decided to write this post to help [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">A couple of weeks ago I acquired <a href="https://www.lenovo.com/us/outletus/en/p/desktops/thinkcentre/m-series-tiny/thinkcentre-m720q/10t70028us">3 x Lenovo M720Q Tiny</a>, which I wanted to add <a href="https://hachyderm.io/@badnetmask/115698872870594708">as extra nodes to my home lab Talos Linux Kubernetes cluster</a>. They also has UEFI secure boot problems, just like <a href="https://mteixeira.wordpress.com/2025/09/28/booting-talos-linux-on-a-framework-desktop/">my Framework Desktop had a couple of months ago</a>. So I decided to write this post to help other people who might face the same problem (hopefully it works for other Lenovo machines as well).</p>



<p class="wp-block-paragraph"><em>Updated on 2026-04-15 &#8211; Fix the BIOS menu sequence missing one step. Also added an extra step to return to &#8220;Deployed Mode&#8221;.</em></p>



<ol><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/12/26/booting-talos-linux-on-a-lenovo-m720q-tiny/#introduction">Introduction</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/12/26/booting-talos-linux-on-a-lenovo-m720q-tiny/#the-boot-problem">The boot problem</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/12/26/booting-talos-linux-on-a-lenovo-m720q-tiny/#configuring-the-secure-boot">Configuring the secure boot</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/12/26/booting-talos-linux-on-a-lenovo-m720q-tiny/#conclusion">Conclusion</a></li></ol>



<h2 id="introduction" class="wp-block-heading">Introduction</h2>



<p class="wp-block-paragraph">Running my Talos Linux Kubernetes cluster with a single node has been working fine so far. It&#8217;s good enough for me because the machine is powerful enough to run everything I need, and still has a lot of power for future growth. However I wanted to have better resilience, and the ability to fail-over at least some of the apps during a node maintenance (not all of them require high uptime).</p>



<p class="wp-block-paragraph">So I decided to get the Lenovo M720Q Tiny for a few reasons: it&#8217;s small, and it&#8217;s very low power. The icing on the cake is that I bought them refurbished, and they were very cheap. I won&#8217;t guarantee anything, but <em>I think</em> the instructions here should work for any other model of the Lenovo Tiny. I am just guessing here, because the BIOS looks very similar to my old Lenovo laptops (I don&#8217;t have any to compare right now).</p>



<p class="wp-block-paragraph">DISCLAIMERS:</p>



<ul class="wp-block-list">
<li>No AI or LLM was used during the research of this problem, or for writing this blog post.
<ul class="wp-block-list">
<li>I did, however, copy and paste some text from my previous post.</li>
</ul>
</li>



<li>The content here is provided AS IS, without any warranties that it will work for you or will not damage your computer.</li>



<li>This is purely a demonstration of MY personal experience, with the expectations that it will help someone else.</li>



<li>Please, don’t blame me if this bricks your computer or cause any data corruption.</li>
</ul>



<h2 id="the-boot-problem" class="wp-block-heading">The boot problem</h2>



<p class="wp-block-paragraph">Just like it happened with my <a href="https://mteixeira.wordpress.com/2025/09/28/booting-talos-linux-on-a-framework-desktop/">Framework Desktop before</a>, as soon as I booted the Lenovo with the Talos ISO on a USB stick, I was greeted with the following message:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
Secure Boot Violation - Invalid Signature detected. Check Secure Boot Policy in Setup
</pre></div>


<p class="wp-block-paragraph">Even though I already have an IP KVM, I&#8217;m not gonna post any screenshots here. I want this post to be easily searchable.</p>



<p class="wp-block-paragraph">Anyway, I knew exactly what to do. I just had to figure out the &#8220;how&#8221;.</p>



<h2 id="configuring-the-secure-boot" class="wp-block-heading">Configuring the secure boot</h2>



<p class="wp-block-paragraph">I did not have to fumble around to figure out what I needed, so let&#8217;s get to it:</p>



<ol class="wp-block-list">
<li>Once you power it up and you see the Lenovo logo, start hitting F1 to get into the BIOS.</li>



<li>Then go to &#8220;Security&#8221; -&gt; &#8220;Secure Boot&#8221;, and select the option &#8220;Reset Platform to Setup Mode&#8221;.</li>



<li>It will ask if you really with to do that, hit the &#8220;Yes&#8221;.</li>



<li>It will come back to the same screen, so hit ESC then go to &#8220;Exit&#8221; then &#8220;Save Changes and Exit&#8221;, then &#8220;Yes&#8221;.</li>



<li>When it reboots and you see the Lenovo logo again, start hitting F12 until you see the boot menu, then select the USB with the Talos boot image.</li>



<li>In the talos menu you will select &#8220;Enroll Secure Boot keys: auto&#8221;.</li>



<li>Don&#8217;t touch anything! Just let it work.</li>



<li>Once complete, it will reboot. The next step is optional, but highly desirable for the best security.
<ul class="wp-block-list">
<li>Go back into the BIOS, then &#8220;Security&#8221; -&gt; &#8220;Secure Boot&#8221;, and select the option &#8220;Enter Deployed Mode&#8221;, then save and exit.</li>



<li>Explanation: after enrolling the key the BIOS is put in &#8220;User Mode&#8221;, however that allows changing the keys from the OS level. Changing to &#8220;Deployed Mode&#8221; requires any key changes to be done via the boot sequence.</li>
</ul>
</li>



<li>On the next boot you can hit F12 again and boot into the Talos install.</li>
</ol>



<p class="wp-block-paragraph">At this point you should be able to&nbsp;<a href="https://www.talos.dev/v1.12/talos-guides/install/bare-metal-platforms/secureboot/">install Talos as usual</a>, and have a fully functional system.</p>



<h2 id="conclusion" class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">And there you go! Hopefully your Talos install will complete without any issues at all. You will also be able to easily execute any future version upgrades without having to connect a KVM to your Lenovo.</p>



<p class="wp-block-paragraph">And for the sake of everyone else in the future, I will contact&nbsp;<a href="https://www.talos.dev/">Talos</a>, the company, and show them this blog post. Hopefully they will add these steps to their documentation, but if not, at least it will be here for anyone else that ever needs it.</p>



<p class="wp-block-paragraph">I hope this has been useful for you! And if you need any help with anything described here, feel free to ask your question in the comments below, or&nbsp;<a href="https://hachyderm.io/@badnetmask">reach out to me on Mastodon</a>!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://mteixeira.wordpress.com/2025/12/26/booting-talos-linux-on-a-lenovo-m720q-tiny/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1675</post-id>
		<media:content url="https://2.gravatar.com/avatar/be97b85b50b810ababecb8b92f87be16dfd6bd605465ecf97707b3d363e8fef3?s=96&#38;d=https%3A%2F%2F2.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=PG" medium="image">
			<media:title type="html">netmask</media:title>
		</media:content>
	</item>
		<item>
		<title>Running ollama and llama.cpp on Talos Linux on an AMD Strix Halo CPU</title>
		<link>https://mteixeira.wordpress.com/2025/12/12/running-ollama-and-llama-cpp-on-talos-linux-on-an-amd-strix-halo-cpu/</link>
					<comments>https://mteixeira.wordpress.com/2025/12/12/running-ollama-and-llama-cpp-on-talos-linux-on-an-amd-strix-halo-cpu/#respond</comments>
		
		<dc:creator><![CDATA[badnetmask]]></dc:creator>
		<pubDate>Fri, 12 Dec 2025 22:53:31 +0000</pubDate>
				<category><![CDATA[homelab]]></category>
		<category><![CDATA[tutorial]]></category>
		<category><![CDATA[ai]]></category>
		<category><![CDATA[kubernetes]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[llamacpp]]></category>
		<category><![CDATA[llm]]></category>
		<category><![CDATA[ollama]]></category>
		<category><![CDATA[talos]]></category>
		<guid isPermaLink="false">http://mteixeira.wordpress.com/?p=1551</guid>

					<description><![CDATA[For about two months I&#8217;ve been running ollama and llama.cpp on a Talos Linux Kubernetes cluster, on top of a desktop PC with the AMD Strix Halo CPU (AMD Ryzen AI Max+ 395). This blog post is to show you how to install it, and talk about some of my frustrations along the way. Introduction [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">For about two months I&#8217;ve been running ollama and llama.cpp on a Talos Linux Kubernetes cluster, on top of a desktop PC with the AMD Strix Halo CPU (AMD Ryzen AI Max+ 395). This blog post is to show you how to install it, and talk about some of my frustrations along the way.</p>



<ol><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/12/12/running-ollama-and-llama-cpp-on-talos-linux-on-an-amd-strix-halo-cpu/#introduction">Introduction</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/12/12/running-ollama-and-llama-cpp-on-talos-linux-on-an-amd-strix-halo-cpu/#the-environment">The environment</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/12/12/running-ollama-and-llama-cpp-on-talos-linux-on-an-amd-strix-halo-cpu/#the-accelerated-video-library-problem">The accelerated video library problem</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/12/12/running-ollama-and-llama-cpp-on-talos-linux-on-an-amd-strix-halo-cpu/#configuring-the-hardware-and-installing-talos">Configuring the hardware and installing Talos</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/12/12/running-ollama-and-llama-cpp-on-talos-linux-on-an-amd-strix-halo-cpu/#installing-the-node-feature-discovery-tool">Installing the Node Feature Discovery tool</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/12/12/running-ollama-and-llama-cpp-on-talos-linux-on-an-amd-strix-halo-cpu/#installing-the-amd-gpu-driver-plugin">Installing the AMD GPU driver (plugin?)</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/12/12/running-ollama-and-llama-cpp-on-talos-linux-on-an-amd-strix-halo-cpu/#installing-ollama">Installing ollama</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/12/12/running-ollama-and-llama-cpp-on-talos-linux-on-an-amd-strix-halo-cpu/#installing-llama-cpp">Installing llama.cpp</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/12/12/running-ollama-and-llama-cpp-on-talos-linux-on-an-amd-strix-halo-cpu/#quick-benchmarks">Quick benchmarks</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/12/12/running-ollama-and-llama-cpp-on-talos-linux-on-an-amd-strix-halo-cpu/#conclusion">Conclusion</a></li></ol>



<h2 id="introduction" class="wp-block-heading">Introduction</h2>



<p class="wp-block-paragraph">When I decided that I wanted to run Kubernetes in my home lab, I also told myself that I wouldn&#8217;t want to be <em>yet another guy who blogs about k8s</em>. However, in recent weeks, I have had three separate conversations with three different people about this one particular topic, which makes me think not a lot of people talk about it, so I&#8217;ll give my 2c.</p>



<p class="wp-block-paragraph">For about two months I&#8217;ve been running ollama and llama.cpp on a Talos Linux Kubernetes cluster, on top of a Framework Desktop PC with the AMD Strix Halo CPU (AMD Ryzen AI Max+ 395 GPU). The reasons why I chose this particular combination might be a subject for a different blog post. Suffice to say, getting everything up and running was a bit challenging (<a href="https://mteixeira.wordpress.com/2025/09/28/booting-talos-linux-on-a-framework-desktop/">I even blogged about my boot problems with the Framework Desktop</a>), but I think I finally got it set up to a point which I can comfortably make use of it with very little maintenance, and I feel like some people might benefit from me sharing the knowledge the I acquired with that experience.</p>



<h2 id="the-environment" class="wp-block-heading">The environment</h2>



<p class="wp-block-paragraph">I&#8217;ll list what I have running here, so you can make a decision about whether or not you want to keep reading.</p>



<ul class="wp-block-list">
<li>AI/LLM tooling:
<ul class="wp-block-list">
<li><a href="https://ollama.com/">ollama</a> &#8211; Like it or not this is the <em>de facto</em> tool to run AI/LLM in a home lab or at a small scale. It is also the most supported tool by any other application that require external tooling for AI inference. I use it for various things like <a href="https://www.home-assistant.io/voice-pe/">Home Assistant Voice</a> and <a href="https://karakeep.app/">Karakeep</a>. I even maintain my own &#8220;ChatGPT&#8221; at home (the actual name of the tool is <a href="https://openwebui.com/">Open WebUI</a>).</li>



<li><a href="https://github.com/ggml-org/llama.cpp">llama.cpp</a> &#8211; Not as widely known, but it&#8217;s an AI/LLM tool that moves in a much faster pace than ollama. Not a lot of apps support talking to it specifically, but it has an <a href="https://openai.com/api/">OpenAI-compatible API</a> server, which allows some of them to talk to it without knowing exactly what it is. I had to run this tool for a while because ollama support for AMD is still lagging behind (more on that later).</li>
</ul>
</li>



<li>Kubernetes platform
<ul class="wp-block-list">
<li>IMHO (in my humble opinion) <a href="https://docs.siderolabs.com/talos/v1.11/overview/what-is-talos">Talos Linux</a> is the easiest way to get a Kubernetes cluster these days. &#8220;<em>What about k3s?</em>&#8220;, you might ask. Well, k3s/minikube/kind they all require you to install an OS (operating system) first, and you have to maintain it as well. Meanwhile Talos is it&#8217;s own operating system, running as an <a href="https://itsfoss.com/immutable-linux-distros/">OCI immutable image</a>, so it simplifies your cluster maintenance by a lot. I like to say that Talos allows me to run Kubernetes at home the same way I run it in a public cloud (like AWS EKS, GCP GKS or Azure AKS).</li>
</ul>
</li>



<li>Hardware
<ul class="wp-block-list">
<li>You can use whatever you want. Whatever I did here should apply to any piece of hardware that has the same CPU architecture. There are multiple choices in the market like the GMKtec and Minisforum. I don&#8217;t have any of those, but most of what I am going to talk about here should apply to them because everything I am using is related to the AMD processor itself, and they all have models with the same CPU/iGPU architecture that I have used here.</li>



<li>I have a <a href="https://frame.work/desktop">Framework Desktop</a>. it&#8217;s a small-but-powerful desktop running AMD Ryzen AI Max+ 395 (Strix Halo architecture), with 128GB RAM and 2x 1TB NVMe. It has an AMD Radeon 8060S iGPU (integrated GPU) which can dynamically allocate up to 96GB of the RAM to be used as VRAM. I chose this particular piece of hardware specifically because of their engineering choices of CPU and memory, all soldered on the motherboard, and the fact that the entire thing can be easily upgraded, just like a desktop. I&#8217;m not gonna lie: there are some challenges with the shared memory, which I&#8217;m gonna talk about later, but I the form factor, the size and the power utilized makes it a very worthwhile solution (it uses LESS power than my Razer laptop running an NVIDIA 3070).</li>
</ul>
</li>
</ul>



<h2 id="the-accelerated-video-library-problem" class="wp-block-heading">The accelerated video library problem</h2>



<p class="wp-block-paragraph">One big characteristic of this particular CPU architecture is the fact that you can dynamically allocate part of the RAM to be used as video RAM (VRAM). However, the combination of any kernels up to 6.16.9 (unsure of the exact version) and ROCm 6.x (AMD accelerated graphics driver, equivalent CUDA for NVIDIA) cause a bug that prevents dynamic VRAM allocation. At the time of this writing I am running <a href="https://docs.siderolabs.com/talos/v1.11/getting-started/what's-new-in-talos">Talos Linux version 1.11</a>, which runs on top of the Linux kernel version 6.12.43, so I am (tangentially) affected by this problem.</p>



<p class="wp-block-paragraph">This particular problem doesn&#8217;t exist in newer kernels (which will come with the future release of <a href="https://docs.siderolabs.com/talos/v1.12/getting-started/what's-new-in-talos">Talos 1.12</a>) combined with with ROCm 7.x (which is not yet supported by ollama). So, for the time being, I decided that I am going to use the Vulkan library. In theory ROCm should be better, but we&#8217;re dealing with limitations of the combination of newer hardware architecture with old software, so we just need to go with the flow for now.</p>



<p class="wp-block-paragraph">In theory you can use ROCm 6.x (much slower), but to do that you need to set a fixed VRAM size on the hardware level. For the Framework desktop this you can set the fixed VRAM size by going into the BIOS, then the &#8220;Advanced&#8221; menu, then &#8220;iGPU Memory Configuration&#8221;, change it to &#8220;Custom&#8221; and set the &#8220;iGPU Memory Size&#8221;. The amount here depends on what you wanna do, but 16GB should be sufficient for most cases. You can go all the way up to 96GB. You can also monitor the VRAM usage to decide when to re-allocate (subject for a future blog post). In my case, I started this way until the day ollama started supporting Vulkan, then I switched back to dynamic allocation.</p>



<h2 id="configuring-the-hardware-and-installing-talos" class="wp-block-heading">Configuring the hardware and installing Talos</h2>



<p class="wp-block-paragraph"><a href="https://mteixeira.wordpress.com/2025/09/28/booting-talos-linux-on-a-framework-desktop/">Apart from a problem to get Talos to boot in this particular machine</a>, you should just follow whatever method you want to use to install it. There is one VERY IMPORTANT step though: you need to use an image that contains the AMD <a href="https://docs.siderolabs.com/talos/v1.11/build-and-extend-talos/custom-images-and-development/system-extensions">system extensions</a>. There are multiple ways you can go about that:</p>



<ul class="wp-block-list">
<li>If you&#8217;re starting from scratch, or installing Talos without any tools, you should generate a new image using the <a href="https://docs.siderolabs.com/talos/v1.11/learn-more/image-factory">Image Factory</a>. Basically just go into <a href="https://factory.talos.dev/">the web site</a>, follow the prompts, then add the &#8220;<code>amdgpu</code>&#8221; and &#8220;<code>amd-ucode</code>&#8221; extensions. This will generate an image that you can use to boot from a USB drive, or use as install source for an install tool.</li>



<li>If you are using (or plan to use) a tool, like <a href="https://github.com/budimanjojo/talhelper">Talhelper</a> for example, you can just boot the system with a <a href="https://docs.siderolabs.com/talos/v1.11/platform-specific-installations/bare-metal-platforms/iso">normal boot image</a>, but then you have to <a href="https://budimanjojo.github.io/talhelper/latest/guides/#adding-talos-extensions-and-kernel-arguments">modify your configuration</a> to add these extensions: &#8220;<code>siderolabs/amdgpu</code>&#8221; and &#8220;<code>siderolabs/amd-ucode</code>&#8220;.</li>
</ul>



<h2 id="installing-the-node-feature-discovery-tool" class="wp-block-heading">Installing the Node Feature Discovery tool</h2>



<p class="wp-block-paragraph">Now we&#8217;re out of the hardware and into the software section of the install process.</p>



<p class="wp-block-paragraph">Before anything, you need to install the <a href="https://github.com/kubernetes-sigs/node-feature-discovery">Node Feature Discovery</a> add-on. Basically what it does is discovering the hardware on your node (based on a bunch of pre-defined rules) and exposing it to your Kubernetes cluster as node tags. This is not an exclusive Talos thing, this is a very important tool for any cluster install when you need to expose specialized hardware.</p>



<p class="wp-block-paragraph">In my case I used the <a href="https://kubernetes-sigs.github.io/node-feature-discovery/v0.18/deployment/helm.html">Helm install</a> method, but since this CPU architecture is very new, at the time of this writing <a href="https://gist.github.com/badnetmask/d1d9df4399cf85214be61254ca363b12">I needed to create a custom rule</a> that correctly detects the PCI device as an AMD GPU.</p>



<h2 id="installing-the-amd-gpu-driver-plugin" class="wp-block-heading">Installing the AMD GPU driver (plugin?)</h2>



<p class="wp-block-paragraph">&#8220;<em>I thought we already installed the driver during Talos install</em>&#8220;. Okay! You got me! Technically we are installing the Kubernetes plugin that allows workloads to have access to the AMD GPU device. There are two possible ways you can install the plugin:</p>



<ul class="wp-block-list">
<li>The <a href="https://instinct.docs.amd.com/projects/k8s-device-plugin/en/latest/">AMD GPU Device Plugin for Kubernetes</a> is by far the easiest way, and the one that will use the least resources in your cluster. I honestly recommend that you try that one first, before going down the same rabbit hole that I did (read the next bullet point). The document I linked talks about installing it using manifests, but there is also <a href="https://github.com/ROCm/k8s-device-plugin/tree/master/helm/amd-gpu">a Helm chart</a> for it. Pick whatever you prefer. Sadly I don&#8217;t have a values file to share, because the first time I used this plugin I didn&#8217;t know about the NFD custom rule I mentioned in the previous topic, so the config was ugly, and eventually I gave up on this method in favor of the next one, so even if I went back into my Git history, the config wouldn&#8217;t be valid. In case you need some customization to make it detect the iGPU, you can read what I did for my final install <a href="https://gist.github.com/badnetmask/d1d9df4399cf85214be61254ca363b12">here</a>.</li>



<li>The second method (which I am using) is with the <a href="https://instinct.docs.amd.com/projects/gpu-operator/en/latest/">AMD GPU Operator</a>. This method installs the same device plugin as the previous bullet point, but also adds more features on top of that. One of the additional features is the kernel driver installation, which is very convenient for most cases, but it wouldn&#8217;t work for Talos, so it had to be disabled. The other thing it enables is metrics collection and GPU partitioning/slicing, which are advanced topics that most people don&#8217;t care about, but I want to experiment with. Both of these things are advanced topics which I am yet to explore (and are the reasons why I chose this method), but I haven&#8217;t yet. If you want to go down that route, you might want to know that I installed it using <a href="https://instinct.docs.amd.com/projects/gpu-operator/en/latest/installation/kubernetes-helm.html">the Helm chart</a>, so you can take a look at <a href="https://gist.github.com/badnetmask/792219ef731bb3daf97f865bd5c7e528">my values file</a>. And due to how this video card works, I had to write <a href="https://gist.github.com/badnetmask/d1d9df4399cf85214be61254ca363b12">a custom rule to detect it</a>.</li>
</ul>



<h2 id="installing-ollama" class="wp-block-heading">Installing ollama</h2>



<p class="wp-block-paragraph">The easiest way I found to install ollama is by using <a href="https://github.com/otwld/ollama-helm">this 3rd-party Helm chart</a>, but it needs to be tweaked for our purposes. You can see all the tweaks I did in <a href="https://gist.github.com/badnetmask/ee552d596224de9258793c416a775d15">my values file</a>. There are a lot of comments there which will help you decide whether or not you need to tweak anything. The most important things here are:</p>



<ul class="wp-block-list">
<li>&#8220;<code>resources</code>&#8221; &#8211; the &#8220;<code>amd.com/gpu: 1</code>&#8221; parameter requests a node with the AMD GPU.</li>



<li>&#8220;<code>extraEnv</code>&#8221; &#8211; you need to enable <code>OLLAMA_VULKAN</code> (otherwise it will fallback to ROCm).</li>



<li>&#8220;<code>volumes</code>&#8221; and &#8220;<code>volumeMounts</code>&#8221; &#8211; you need to mount &#8220;<code>/dev/dri</code>&#8221; and &#8220;<code>/dev/kfd</code>&#8220;, which are the devices that give direct access to the video card.</li>
</ul>



<p class="wp-block-paragraph">Notice that, as I mentioned before, ollama comes pre-compiled and linked against ROCm 6.x, which has the memory allocation bug with AMD Strix Halo iGPUs. Gladly <a href="https://github.com/ollama/ollama/releases/tag/v0.12.11">ollama 1.12.11</a> introduced support for the Vulkan library, which is not as good as the ROCm library, but that is the thing which currently allows this application to be usable in my setup. Once ollama updates to ROCm 7.x, I will try again and see if there is any difference.</p>



<p class="wp-block-paragraph">Once the install is complete, ollama will be available inside your cluster on port 11434. As of any Kubernetes resource, it can be reachable via the hostname &#8220;<code>&lt;deployment&gt;.&lt;namespace&gt;.svc.cluster.local</code>&#8221; (or &#8220;<code>ollama.ollama.svc.cluster.local</code>&#8221; if you&#8217;re using the defaults). Optionally, you can expose it outside the cluster using an ingress controller or a Gateway API route.</p>



<h2 id="installing-llama-cpp" class="wp-block-heading">Installing llama.cpp</h2>



<p class="wp-block-paragraph">Installing llama.cpp is a chore, because I could not find a Helm chart, so I wrote <a href="https://gist.github.com/badnetmask/043dfce31ec7928d57ef667a2b829b35">this huge and cumbersome manifest</a>. I started using it as a point of comparison, before ollama had support for Vulkan, since lamma.cpp comes with Vulkan out of the box. Today I use it more for experiments, but my real workload use is on top of ollama, because they are basically performing at the same level. This is also the tool that I will need to play around with GPU partitioning/slicing.</p>



<p class="wp-block-paragraph">Be warned: only use it if you know what you&#8217;re doing and why you&#8217;re doing it. If you copy my manifest, be mindful that it is being provided AS IS, and it might not work at all. Don&#8217;t blame me if it doesn&#8217;t work. Don&#8217;t ask for support. Basically, it&#8217;s your own adventure, and yours alone.</p>



<h2 id="quick-benchmarks" class="wp-block-heading">Quick benchmarks</h2>



<p class="wp-block-paragraph">Just so you can confirm that everything is working, and you can have a baseline for future comparison (when ollama upgrades, or you make configuration changes), you can run a quick benchmark.</p>



<p class="wp-block-paragraph">Since I wanted to be able to compare with someone else&#8217;s install, I decided to use <a href="https://github.com/geerlingguy/ai-benchmarks">Jeff Geerling&#8217;s obench.sh</a>. If you want a quick-and-dirty way of running your own, here&#8217;s how I do it in my ollama deployment:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
host$ kubectl exec -n ollama -it deployment/ollama -- /bin/bash
ollama# apt update && apt install curl bc -y
ollama# curl  https://raw.githubusercontent.com/geerlingguy/ollama-benchmark/refs/heads/main/obench.sh -o obench.sh && chmod +x obench.sh
ollama# ./obench.sh -c 10 -m llama3.2:3b
(...)
Average Eval Rate: 87.14 tokens/second
</pre></div>


<p class="wp-block-paragraph">Note on the above: the power peak was 100W.</p>



<p class="wp-block-paragraph">And, in case you install llama.cpp and you want to benchmark that as well, here&#8217;s how I do it:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
host$ kubectl exec -n llama-cpp -it deployment/llama-cpp -- /bin/bash
llama-cpp$ ./llama-bench -m /data/Llama-3.2-3B-Instruct-Q4_K_M.gguf -n 128 -p 512,4096 -pg 4096,128 -ngl 99 -r 2
(...)
(at the one-before-the-end line, where test reads "tg128", you will find your token/s: 90.47 ± 0.99)
</pre></div>


<p class="wp-block-paragraph">Note on the above: the power peak was 112W</p>



<p class="wp-block-paragraph">And one last note: I literally copied/pasted the commands out of the <a href="https://github.com/geerlingguy/ai-benchmarks?tab=readme-ov-file">repository&#8217;s README</a>. You can tweak to use whatever you want.</p>



<h2 id="conclusion" class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">Installing AI tools on a Kubernetes cluster is complicated, but teaches you a lot about the ins-and-outs of how things work. It really gives you a lot more exposure to the behind-the-scenes than just running a plain Docker container on your laptop. There are a lot of pitfalls, and you need to understand what you&#8217;re doing, but once it&#8217;s running, there&#8217;s no maintenance other than version upgrades.</p>



<p class="wp-block-paragraph">I really hope you can make good use of everything that was demonstrated here!</p>



<p class="wp-block-paragraph">If you have any questions/comments (criticism?) or just would like to chat, feel free to&nbsp;<a href="https://hachyderm.io/@badnetmask">reach out to me on Mastodon</a>, or post a comment below. I will try my best to reply back to you with something useful.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://mteixeira.wordpress.com/2025/12/12/running-ollama-and-llama-cpp-on-talos-linux-on-an-amd-strix-halo-cpu/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1551</post-id>
		<media:content url="https://2.gravatar.com/avatar/be97b85b50b810ababecb8b92f87be16dfd6bd605465ecf97707b3d363e8fef3?s=96&#38;d=https%3A%2F%2F2.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=PG" medium="image">
			<media:title type="html">netmask</media:title>
		</media:content>
	</item>
		<item>
		<title>Monitoring the Kubernetes certificates on a Talos cluster</title>
		<link>https://mteixeira.wordpress.com/2025/12/07/monitoring-the-kubernetes-certificates-on-a-talos-cluster/</link>
					<comments>https://mteixeira.wordpress.com/2025/12/07/monitoring-the-kubernetes-certificates-on-a-talos-cluster/#respond</comments>
		
		<dc:creator><![CDATA[badnetmask]]></dc:creator>
		<pubDate>Sun, 07 Dec 2025 21:25:55 +0000</pubDate>
				<category><![CDATA[homelab]]></category>
		<category><![CDATA[tutorial]]></category>
		<category><![CDATA[certificates]]></category>
		<category><![CDATA[kubernetes]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[monitoring]]></category>
		<category><![CDATA[talos]]></category>
		<guid isPermaLink="false">http://mteixeira.wordpress.com/?p=1576</guid>

					<description><![CDATA[I&#8217;ve been following Michael Meier&#8217;s saga on Mastodon, where he&#8217;s trying to figure out how to properly monitor his Kubernetes certificates. In the end he came up with some clever ideas in a blog post. So I decided to look at what can I do for my situation, and then I learned that things in [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I&#8217;ve been following <a href="https://social.mei-home.net/@mmeier/115663677987260386">Michael Meier&#8217;s saga on Mastodon</a>, where he&#8217;s trying to figure out how to properly monitor his Kubernetes certificates. In the end he came up with some <a href="https://blog.mei-home.net/posts/k8s-certs/">clever ideas in a blog post</a>. So I decided to look at what can I do for my situation, and then I learned that things in Talos are much simpler than I expected.</p>



<ol><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/12/07/monitoring-the-kubernetes-certificates-on-a-talos-cluster/#how-talos-manages-the-certificates">How Talos manages the certificates</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/12/07/monitoring-the-kubernetes-certificates-on-a-talos-cluster/#how-to-spot-check-the-certificate-expiration-on-the-server-side">How to spot check the certificate expiration on the server side</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/12/07/monitoring-the-kubernetes-certificates-on-a-talos-cluster/#using-monitoring-tools-to-check-for-the-certificate-expiration">Using monitoring tools to check for the certificate expiration</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/12/07/monitoring-the-kubernetes-certificates-on-a-talos-cluster/#don-t-forget-to-check-the-client-cert-as-well">Don&#8217;t forget to check the client cert as well</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/12/07/monitoring-the-kubernetes-certificates-on-a-talos-cluster/#conclusion">Conclusion</a></li></ol>



<h2 class="wp-block-heading" id="how-talos-manages-the-certificates">How Talos manages the certificates</h2>



<p class="wp-block-paragraph">This is what they explained <a href="https://docs.siderolabs.com/talos/v1.12/security/cert-management">in their documentation</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><em>Talos Linux automatically manages and rotates all server side certificates for etcd, Kubernetes, and the Talos API. Note however that the kubelet needs to be restarted at least once a year in order for the certificates to be rotated. Any upgrade/reboot of the node will suffice for this effect.</em></p>
</blockquote>



<p class="wp-block-paragraph">And I can fully confirm that. A few days ago I had an issue that forced me to <a href="https://hachyderm.io/@badnetmask/115644675759593580">reboot my single-node cluster</a>. After it came back up the certificates were renewed. Keep reading and I will explain how I figured that out.</p>


<div class="wp-block-image">
<figure class="aligncenter size-thumbnail"><a href="https://mteixeira.wordpress.com/wp-content/uploads/2025/12/screenshot-from-2025-12-07-16-06-39.png"><img loading="lazy" width="129" height="149" data-attachment-id="1603" data-permalink="https://mteixeira.wordpress.com/2025/12/07/monitoring-the-kubernetes-certificates-on-a-talos-cluster/screenshot-from-2025-12-07-16-06-39/" data-orig-file="https://mteixeira.wordpress.com/wp-content/uploads/2025/12/screenshot-from-2025-12-07-16-06-39.png" data-orig-size="600,697" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot From 2025-12-07 16-06-39" data-image-description="" data-image-caption="" data-large-file="https://mteixeira.wordpress.com/wp-content/uploads/2025/12/screenshot-from-2025-12-07-16-06-39.png?w=600" src="https://mteixeira.wordpress.com/wp-content/uploads/2025/12/screenshot-from-2025-12-07-16-06-39.png?w=129" alt="The best way to test the resilience of your service is: reboot the server. I mean it. Don't just restart the service, reboot the server. A story in two parts:

Part 1: a while ago I configured ZFS under my Talos Kubernetes cluster, and everything was fine, until I decided to reboot. When it came back, nothing was working, because I forgot to properly configure a way for Talos to read the ZFS volume encryption key.

Part 2: at some point I configured Audiobookshelf to store data on top of said ZFS volume. Everything was working fine for a few weeks, until I had to reboot the server again (for reasons). When it came back, I lost all my downloaded podcasts, because I had a typo on my configuration that was pointing to a directory outside of the PVC, so it mounted as an emptyDir volume.

Honestly, I should have known better. I had issues in the past when some servers went down because of power failures (battery didn't last) and they did not come back properly.

You gotta do a reboot/power test every once in a while, just like you have to test your backups on a regular basis.

#HomeLab #TalosLinux #ZFS #SRE #DevOps @homelab" class="wp-image-1603" srcset="https://mteixeira.wordpress.com/wp-content/uploads/2025/12/screenshot-from-2025-12-07-16-06-39.png?w=129 129w, https://mteixeira.wordpress.com/wp-content/uploads/2025/12/screenshot-from-2025-12-07-16-06-39.png?w=258 258w" sizes="(max-width: 129px) 100vw, 129px" /></a></figure>
</div>


<h2 class="wp-block-heading" id="how-to-spot-check-the-certificate-expiration-on-the-server-side">How to spot check the certificate expiration on the server side</h2>



<p class="wp-block-paragraph">There are multiple ways of doing that, and the easiest/most portable way is to simply check for the API certificate dates.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
$ echo | openssl s_client -connect &lt;api_fqdn_or_ip&gt;:6443 2&gt;/dev/null \
  | openssl x509 -noout -enddate
notAfter=Dec 3 11:55:14 2026 GMT
</pre></div>


<p class="wp-block-paragraph">This should work for the majority of the cases, because all the certificates are renewed at the same time, and that check can be done from a machine that does not have authorization to access the Talos API.</p>



<p class="wp-block-paragraph">There are two other ways which are more specific to Talos:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
$ talosctl config info
Current context:     talos
Nodes:               &lt;node_ip&gt;
Endpoints:           &lt;node_ip&gt;
Roles:               os:admin
Certificate expires: 11 months from now (2026-11-23)

$ talosctl get KubernetesDynamicCerts -o yaml \
  | yq -r &#039;.spec.apiServerKubeletClient.crt&#039; \
  | base64 -d | openssl x509 -text -out - | grep &quot;Not After&quot;
            Not After : Dec  3 11:55:14 2026 GMT
</pre></div>


<p class="wp-block-paragraph">Both of them are pretty easy to use. The first one does basically the same thing as the second, but gives an output that is easier to read. The second one queries for the certificates on the API server, the kubelet service and the front-end proxy. I&#8217;m filtering for the kubelet certificate for simplicity.</p>



<p class="wp-block-paragraph">But, as the documentation says, you shouldn&#8217;t worry too much: all the certs are going to be rotated at the same time, so they all have the same expiration date.</p>



<p class="wp-block-paragraph">And yes, I did find out that my certificates were rotated <a href="https://hachyderm.io/@badnetmask/115644675759593580">after a reboot</a> when I was doing the research for this blog post. So I can confirm the documentation is accurate.</p>



<h2 class="wp-block-heading" id="using-monitoring-tools-to-check-for-the-certificate-expiration">Using monitoring tools to check for the certificate expiration</h2>



<p class="wp-block-paragraph">As I mentioned before, you can safely assume that the API certificate expiration will be the same as the internal kubelet, because Talos updates them all together, so you can add a remote check to whatever monitoring service you use.</p>



<p class="wp-block-paragraph">I personally use <a href="https://uptime.kuma.pet/">Uptime Kuma</a> to monitor various aspects of my home lab which are not integrated into the Kubernetes cluster. In the HTTP(s) monitor there is an option &#8220;Certificate Expiry Notification&#8221;. I do have a monitor to check if the Talos cluster is up, but since it uses it&#8217;s own internal CA, Uptime Kuma does not recognize it, so I had to check the &#8220;Ignore TLS/SSL errors for HTTPS websites&#8221; option. The problem is that this option also disables the certificate expiration. I even <a href="https://github.com/louislam/uptime-kuma/issues/6464">opened a request for enhancement</a>, to separate the CA check from other TLS/SSL errors, because I think there are valid situations (like this one) where the CA will not be recognized. (Edit: the request was rejected. I won&#8217;t argue, it&#8217;s their right).</p>



<p class="wp-block-paragraph">But, for completeness sake, I extracted the internal cluster CA that issues the API certificate like this:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
$ kubectl get configmap -n kube-system kube-root-ca.crt -o jsonpath=&#039;{.data.ca\.crt}&#039; &gt; talos-api-ca.crt
</pre></div>


<p class="wp-block-paragraph">For most people using Uptime Kuma, at this point you can simply copy <code>talos-api-ca.crt</code> inside the container data directory, then pass the <code>NODE_EXTRA_CA_CERTS</code> environment variable and restart the container.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: yaml; title: ; notranslate">
uptime-kuma:
  (...)
  environment:
    NODE_EXTRA_CA_CERTS: /app/data/talos-api-ca.crt
  (...)
</pre></div>


<p class="wp-block-paragraph">In my case, since <a href="https://mteixeira.wordpress.com/2024/03/03/running-your-private-certificate-authority-with-acme-support/">I run my own private CA</a>, I had to actually concatenate my existing <code>homelab-ca.crt</code> file with the new <code>talos-api-ca-crt</code> into a new file named combine.crt, and load that one instead. That&#8217;s because NodeJS can only load one file via the <code>NODE_EXTRA_CA_CERTS</code> env var.</p>



<p class="wp-block-paragraph">Now you can go into Uptime Kuma, add an HTTP(s) monitor, point at <code>https://&lt;api_fqdn_or_ip&gt;:6443</code> and check the box &#8220;Certificate Expiry Notification&#8221;. But note: since you are not using a client certificate, you need to add <code>401</code> as a valid &#8220;Accepted Status Codes&#8221;.</p>



<h2 class="wp-block-heading" id="don-t-forget-to-check-the-client-cert-as-well">Don&#8217;t forget to check the client cert as well</h2>



<p class="wp-block-paragraph">Just like Michael explained <a href="https://blog.mei-home.net/posts/k8s-certs/">in his blog post</a>, I am also the sole admin for this cluster, so I don&#8217;t care about configuring complicated authentication methods, and I simply use the standard <code>kubectl</code> client certificate. This one also has an expiration date, so you need to check for it as well. Michael&#8217;s solution checks out his <code>kubeconfig</code> from his password store, but if you, like me, just use plain <code>kubeconfig</code>, this is how you can check it:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
$ grep client-certificate-data ~/.kube/config | awk '{print $2}' \
  | base64 -d | openssl x509 -text -out - | grep "Not After"
            Not After : Nov 23 01:06:26 2026 GMT
$ grep client-certificate-data ~/.kube/config | awk '{print $2}' \
  | base64 -d | openssl x509 -checkend 2592000 -noout
Certificate will not expire
</pre></div>


<p class="wp-block-paragraph">With that, you can use <a href="https://blog.mei-home.net/posts/k8s-certs/#monitoring-the-certs">Michael&#8217;s code</a> to check your expiration when you open a shell session, if you want.</p>



<h2 class="wp-block-heading" id="conclusion">Conclusion</h2>



<p class="wp-block-paragraph">It&#8217;s really useful to keep an eye in the <code>#homelab</code> hashtag on Mastodon, as well as follow the <a href="https://fedigroups.social/@homelab">homelab bot</a>. There&#8217;s a lot of great content coming out of that community.</p>



<p class="wp-block-paragraph">I&#8217;m also happy that this situation forced me to understand how Talos handles certificates, before I end up in a bad spot with them.</p>



<p class="wp-block-paragraph">I hope this has been useful for you! And if you need any help with anything described here, feel free to ask your question in the comments below, or <a href="https://hachyderm.io/@badnetmask">reach out to me on Mastodon</a>!</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://mteixeira.wordpress.com/2025/12/07/monitoring-the-kubernetes-certificates-on-a-talos-cluster/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1576</post-id>
		<media:content url="https://2.gravatar.com/avatar/be97b85b50b810ababecb8b92f87be16dfd6bd605465ecf97707b3d363e8fef3?s=96&#38;d=https%3A%2F%2F2.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=PG" medium="image">
			<media:title type="html">netmask</media:title>
		</media:content>
	</item>
		<item>
		<title>Booting Talos Linux on a Framework Desktop</title>
		<link>https://mteixeira.wordpress.com/2025/09/28/booting-talos-linux-on-a-framework-desktop/</link>
					<comments>https://mteixeira.wordpress.com/2025/09/28/booting-talos-linux-on-a-framework-desktop/#comments</comments>
		
		<dc:creator><![CDATA[badnetmask]]></dc:creator>
		<pubDate>Mon, 29 Sep 2025 00:40:51 +0000</pubDate>
				<category><![CDATA[homelab]]></category>
		<category><![CDATA[tutorial]]></category>
		<category><![CDATA[framework]]></category>
		<category><![CDATA[kubernetes]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[talos]]></category>
		<guid isPermaLink="false">http://mteixeira.wordpress.com/?p=1499</guid>

					<description><![CDATA[I recently received my Framework Desktop, and I wanted to install Talos Linux on it. My first attempt at booting with the USB drive was a total failure. In this blog post I will show you how I managed to get it temporarily working, and what is the final/definitive solution to get Talos Linux installed [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I recently received my <a href="https://frame.work/desktop">Framework Desktop</a>, and I wanted to install <a href="https://www.talos.dev/">Talos Linux</a> on it. My first attempt at booting with the USB drive was a total failure. In this blog post I will show you how I managed to get it temporarily working, and what is the final/definitive solution to get Talos Linux installed on your Framework Desktop.</p>



<ol><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/09/28/booting-talos-linux-on-a-framework-desktop/#introduction">Introduction</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/09/28/booting-talos-linux-on-a-framework-desktop/#the-boot-problem">The boot problem</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/09/28/booting-talos-linux-on-a-framework-desktop/#first-the-wrong-solution">First, the wrong solution</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/09/28/booting-talos-linux-on-a-framework-desktop/#the-final-correct-solution-for-the-boot-problem">The final/correct solution for the boot problem</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/09/28/booting-talos-linux-on-a-framework-desktop/#conclusion">Conclusion</a></li></ol>



<h2 class="wp-block-heading" id="introduction">Introduction</h2>



<p class="wp-block-paragraph">I have been running a single-node Talos Linux Kubernetes cluster for a while. I still have my old and reliable Raspberry Pis, but I wanted to &#8220;up my game&#8221;, so I&#8217;m running this cluster in parallel, until I decide what I really want to do with it. Up until today, I have been running it on a single salvaged <a href="https://www.amazon.com/EliteDesk-Factor-Desktop-i5-8500-Windows/dp/B07F3RKHNH">HP EliteDesk 800 G4 SFF</a>, which was upgraded from i5 to i7, and 8GB to 64GB RAM. However, I was unable to find a decent GPU that would both fit on this case without modifications, and would be able to run some decent AI model using <a href="https://ollama.com/">ollama</a>.</p>



<p class="wp-block-paragraph">I have been eyeing the Framework laptops for years, but so far I haven&#8217;t had the need to buy one. However, it was a great coincidence that they released their <a href="https://frame.work/desktop">Desktop</a> version right around the time when I was trying to find a GPU solution. After beating around the bush a lot, looking at many different possible solutions (vendors, form factors, etc), I decided it was a good option for me, so I got the one with the AMD Ryzen AI Max+ 395 CPU and 128GB of RAM. The main reason is: if it doesn&#8217;t do what I need it to do (expose the GPU via Kubernetes, and run ollama [or any other AI engine] decently) then the worst that can happen is that I get a new super spec&#8217;d up desktop to replace my current aging laptop.</p>



<p class="wp-block-paragraph">DISCLAIMERS:</p>



<ul class="wp-block-list">
<li>No AI or LLM was used during the research of this problem, or for writing this blog post.</li>



<li>The content here is provided AS IS, without any warranties that it will work for you or will not damage your computer.</li>



<li>This is purely a demonstration of MY personal experience, with the expectations that it will help someone else.</li>



<li>Please, don&#8217;t blame me if this bricks your computer or cause any data corruption.</li>
</ul>



<h2 class="wp-block-heading" id="the-boot-problem">The boot problem</h2>



<p class="wp-block-paragraph">Right, you don&#8217;t want to hear my sob story. You&#8217;re here to get Talos booting on your Framework Desktop.</p>



<p class="wp-block-paragraph">First, let me tell you one thing: this post does not have any screenshots because I don&#8217;t have an IP KVM (yet), and any pictures I would take with my phone had a terrible reflection of me and my messy home office. So I wanted to spare you from those.</p>



<p class="wp-block-paragraph">But this is the problem: I downloaded the official <a href="https://github.com/siderolabs/talos/releases/tag/v1.11.2">ISO boot image</a>, burned it into a USB drive, plug it in, power on, and then I was presented with this lovely error:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
EFI USB Device (Generic Flash Disk) boot failed.
</pre></div>


<p class="wp-block-paragraph">I knew what that problem meant, so I didn&#8217;t panic. This is related to the fact that the secure boot trust certificate database does not know about Talos. I&#8217;ve been through this when I first attempted to install it on my HP, and I gladly found <a href="https://www.oct8l.com/posts/2025/46/enabling-talos-os-secureboot-on-hp-elitedesk-800-g5/">this blog post</a> explaining how to get it working. So I just needed to borrow the ideas, and figure out how to do that on the Framework.</p>



<h2 class="wp-block-heading" id="first-the-wrong-solution">First, the wrong solution</h2>



<p class="wp-block-paragraph">I call it &#8220;wrong&#8221; just because it&#8217;s a temporary solution, not permanent. And also because I initially did this way because I was not paying attention, and went to the wrong menu options.</p>



<p class="wp-block-paragraph">NOTE: this section is only for &#8220;extra knowledge&#8221;. The right/correct solution is in the next section. Keep reading only if you are extra curious about how I wasted my time.</p>



<p class="wp-block-paragraph">When you power on the Framework Desktop, keep spamming F2 until you see the BIOS menu. Go to the option <em>&#8220;Administer Secure Boot&#8221;</em>, and follow these steps:</p>



<ul class="wp-block-list">
<li>&#8220;Select a UEFI file as trusted for execution&#8221;</li>



<li>If you don&#8217;t already have anything installed on this machine, just select the first device. If you do, make sure you select the USB drive.</li>



<li>Select &#8220;&lt;EFI&gt;&#8221; -&gt; &#8220;&lt;BOOT&gt;&#8221; -&gt; &#8220;BOOTX64.EFI&#8221; (this tells the BIOS to start GRUB)</li>



<li>You will be asked &#8220;Add this hash image to allowed database (db)&#8221;, you tell it &#8220;Yes&#8221;
<ul class="wp-block-list">
<li>Do you already see where my mistake is?</li>
</ul>
</li>



<li>Now go to the same &#8220;Select a UEFI file&#8221; menu.</li>



<li>Select the USB device again.</li>



<li>Select &#8220;&lt;EFI&gt;&#8221; -&gt; &#8220;&lt;LINUX&gt;&#8221; -&gt; &#8220;Talos-&lt;version&gt;.efi&#8221; (this tells the BIOS to allow Talos to boot)</li>



<li>Same as before, say &#8220;Yes&#8221; to the &#8220;Accept&#8221; question.</li>



<li>Now hit F10 to save and reboot.</li>
</ul>



<p class="wp-block-paragraph">At this point you should be able to <a href="https://www.talos.dev/v1.11/talos-guides/install/bare-metal-platforms/secureboot/">install Talos as usual</a>, but there is one caveat: the installed system will NOT boot properly. After you complete the install, you have to remove the USB device before the system reboots, then spam F2 again. Then you need to follow the same steps as I described above, but this time you will select the files from the NVMe drive.</p>



<p class="wp-block-paragraph">Why is this the wrong solution? Because it only trusts <em>those specific files</em>. As soon as you upgrade your Talos version, your system will be unbootable, because the files will change, and you will be forced to follow this procedure again. So, yeah, not a good solution. Please don&#8217;t do that.</p>



<h2 class="wp-block-heading" id="the-final-correct-solution-for-the-boot-problem">The final/correct solution for the boot problem</h2>



<p class="wp-block-paragraph">As I learned with my journey with my first node, the proper way to fix this problem is to tell the BIOS to trust the Talos UEFI certificates.</p>



<p class="wp-block-paragraph">NOTE: if you already did the wrong thing, like I did before, it&#8217;s better if you reset your secure boot keys to the factory defaults. It&#8217;s an easy option to spot in the menus.</p>



<p class="wp-block-paragraph">Make sure you have the USB device burned with the Talos installer already plugged in (this does not work from a Talos already installed in the NVMe). Power on the Framework Desktop, and keep spamming F2 until you see the BIOS menu. Go to the option <em>&#8220;Administer Secure Boot&#8221;</em>, and follow these steps:</p>



<ul class="wp-block-list">
<li>Select &#8220;PK Options&#8221; -&gt; &#8220;Enroll PK&#8221; -&gt; then select &#8220;AUTH&#8221; as the signature format.</li>



<li>Select the USB device.</li>



<li>Select &#8220;&lt;loader&gt;&#8221; -&gt; &#8220;&lt;keys&gt;&#8221; -&gt; &#8220;&lt;auto&gt;&#8221; -&gt; &#8220;PK.auth&#8221; -&gt; Enroll? &#8220;Yes&#8221;.
<ul class="wp-block-list">
<li>In my case, I got the message &#8220;No data added!!&#8221;, but I am keeping this here just in case something changes in the future.</li>
</ul>
</li>



<li>You will get bumped back to the secure boot menu. If not, hit ESC.</li>



<li>Select &#8220;KEK Options&#8221; -&gt; &#8220;Enroll KEK&#8221; -&gt; then select &#8220;AUTH&#8221; as the signature format.</li>



<li>Select the USB device.</li>



<li>Select &#8220;&lt;loader&gt;&#8221; -&gt; &#8220;&lt;keys&gt;&#8221; -&gt; &#8220;&lt;auto&gt;&#8221; -&gt; &#8220;KEK.auth&#8221; -&gt; Enroll? &#8220;Yes&#8221;.</li>



<li>You will get bumped back to the &#8220;Enroll KEK&#8221; screen, and you should see &#8220;[PKCS7] talos.dev&#8221;.</li>



<li>Now hit ESC to go back to the secure boot main menu.</li>



<li>Follow the exact same steps for &#8220;db options&#8221;, using the &#8220;db.auth&#8221; file.</li>



<li>At the end you will scroll down the screen and also see &#8220;[PKCS7] talos.dev&#8221; at the bottom of the list.</li>



<li>IMPORTANT: hit F10 to save. If you just reboot from here, all is going to be lost.</li>



<li>I repeat: hit F10 to save.</li>
</ul>



<p class="wp-block-paragraph">At this point you should be able to <a href="https://www.talos.dev/v1.11/talos-guides/install/bare-metal-platforms/secureboot/">install Talos as usual</a>, and have a fully functional system.</p>



<h2 class="wp-block-heading" id="conclusion">Conclusion</h2>



<p class="wp-block-paragraph">And there you go! Hopefully your Talos install will complete without any issues at all. You will also be able to easily execute any future version upgrades without having to connect a KVM to your Framework.</p>



<p class="wp-block-paragraph">And for the sake of everyone else in the future, I will contact <a href="https://www.talos.dev/">Talos</a>, the company, and show them this blog post. Hopefully they will add these steps to their documentation, but if not, at least it will be here for anyone else that ever needs it.</p>



<p class="wp-block-paragraph">I hope this has been useful for you! And if you need any help with anything described here, feel free to ask your question in the comments below, or <a href="https://hachyderm.io/@badnetmask">reach out to me on Mastodon</a>!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://mteixeira.wordpress.com/2025/09/28/booting-talos-linux-on-a-framework-desktop/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1499</post-id>
		<media:content url="https://2.gravatar.com/avatar/be97b85b50b810ababecb8b92f87be16dfd6bd605465ecf97707b3d363e8fef3?s=96&#38;d=https%3A%2F%2F2.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=PG" medium="image">
			<media:title type="html">netmask</media:title>
		</media:content>
	</item>
		<item>
		<title>Running Renovate on self-hosted Forgejo</title>
		<link>https://mteixeira.wordpress.com/2025/02/16/running-renovate-on-self-hosted-forgejo/</link>
					<comments>https://mteixeira.wordpress.com/2025/02/16/running-renovate-on-self-hosted-forgejo/#comments</comments>
		
		<dc:creator><![CDATA[badnetmask]]></dc:creator>
		<pubDate>Mon, 17 Feb 2025 02:04:01 +0000</pubDate>
				<category><![CDATA[homelab]]></category>
		<category><![CDATA[tutorial]]></category>
		<category><![CDATA[forgejo]]></category>
		<category><![CDATA[linux]]></category>
		<guid isPermaLink="false">http://mteixeira.wordpress.com/?p=1421</guid>

					<description><![CDATA[In this post I will demonstrate how I configured Renovate to run on my self-hosted Forgejo runners, so I can automatically track updates on my home lab applications. A lot of the quirks with the runner configuration have been explained in a previous blog post, where I have gone through the full runner setup. A [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">In this post I will demonstrate how I configured <a href="https://docs.renovatebot.com/">Renovate</a> to run on my self-hosted Forgejo runners, so I can automatically track updates on my home lab applications. A lot of the quirks with the runner configuration have been explained in a previous blog post, where I have gone through the <a href="/2025/02/03/my-self-hosted-forgejo-runner-setup/">full runner setup</a>. A bonus configuration to this blog is telling Forgejo to <a href="/2025/02/16/sending-email-notifications-via-pushover/">send pull requests notifications via Pushover</a>.</p>



<p class="wp-block-paragraph"><strong>Updates:</strong></p>



<ul class="wp-block-list">
<li>2025-03-05 &#8211; Added a note about the RENOVATE_GITHUB_COM_TOKEN variable.
<ul class="wp-block-list">
<li>This is a &#8220;Day 2&#8221; type of problem. After using this solution for a few days, I realized that it was not pulling the release notes, and there was an observation on the merge request that I should fix it by following the<a href="https://github.com/renovatebot/renovate/blob/main/docs/usage/examples/self-hosting.md"> self-hosted documentation</a>.</li>



<li>IMPORTANT: if you are reading this <strong>after</strong> you have been using this for a while, like me, you <strong>need</strong> to pull a new version of the &#8220;<strong>renovate</strong>&#8221; Docker image, which contains the updated variable. If it&#8217;s not working for you, it probably means you are using the old image, so try with the GITHUB_COM_TOKEN variable instead.</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph"><strong>Table of Contents:</strong></p>



<ol><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/02/16/running-renovate-on-self-hosted-forgejo/#introduction">Introduction</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/02/16/running-renovate-on-self-hosted-forgejo/#configuring-forgejo">Configuring Forgejo</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/02/16/running-renovate-on-self-hosted-forgejo/#creating-the-forgejo-api-personal-access-token-pat">Creating the Forgejo API Personal Access Token (PAT)</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/02/16/running-renovate-on-self-hosted-forgejo/#creating-the-github-api-personal-access-token-pat">Creating the GitHub API Personal Access Token (PAT)</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/02/16/running-renovate-on-self-hosted-forgejo/#creating-the-renovate-workflow">Creating the Renovate workflow</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/02/16/running-renovate-on-self-hosted-forgejo/#finalizing">Finalizing</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/02/16/running-renovate-on-self-hosted-forgejo/#conclusion">Conclusion</a></li></ol>



<h2 class="wp-block-heading" id="introduction">Introduction</h2>



<p class="wp-block-paragraph">For a while I have been meaning to find ways to automate the way I update my self-hosted applications. The first step was to figure out <a href="/2025/02/03/my-self-hosted-forgejo-runner-setup/">how to run GitHub-style workflows</a> on my self-hosted Forgejo instance. The second step was to find some tool to automatically help me figure out when something needs to be updated. I have looked at many options, but I ended up deciding to use <a href="/2025/02/03/my-self-hosted-forgejo-runner-setup/">Renovate</a>, simply because I wanted something that was integrated into my infrastructure as code (IaC) workflow.</p>



<p class="wp-block-paragraph">Just so you know, I also looked at <a href="https://containrrr.dev/watchtower/">Watchtower</a>, but that tool is very specifically directed at Docker containers, and I wanted something more generic. Renovate, on the other hand, supports a very wide range of &#8220;data sources&#8221; (<a href="https://docs.renovatebot.com/modules/manager/">called &#8220;managers&#8221;</a>), so it can be used for various other purposes, like custom developed applications, or other non-docker things (like maintaining Ansible module versions, which I intend to use with <a href="https://github.com/badnetmask/miscelaneous/tree/main/ansible/homelab-ee">my custom Execution Environment</a>, once I figure out how).</p>



<p class="wp-block-paragraph">Also, in order to make this more clearly useful, most likely I will go back to all my Docker Compose files and make sure I use explicit container versions, rather than &#8220;latest&#8221;. This way Renovate can pick them up and to it&#8217;s magic.</p>



<h2 class="wp-block-heading" id="configuring-forgejo">Configuring Forgejo</h2>



<p class="wp-block-paragraph">Renovate works by creating pull requests in your repository, so here you have a choice of simply having to check your web UI for any newly created requests, or you can receive notifications via email. I chose the former, since I don&#8217;t want to keep looking at my UI all the time. But this is totally optional, and you can skip this step if you don&#8217;t want email.</p>



<p class="wp-block-paragraph">To get email, you need to have some way to deliver it. Forgejo needs an SMTP server, and how you achieve that is out of scope of this post. However I will leave you with this bonus knowledge nugget: I have configured an <a href="/2025/02/16/sending-email-notifications-via-pushover/">email to Pushover notification service</a>. If you need/want that, go on, read the other blog post, then come back here.</p>



<p class="wp-block-paragraph">So, in my case, I have modified the following configuration options on <code>&lt;forgejo-path&gt;/gitea/conf/app.ini</code> (you can find all the configuration options in <a href="https://forgejo.org/docs/latest/admin/email-setup/">the official documentation</a>):</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
&#91;service]
ENABLE_NOTIFY_EMAIL = true

&#91;mailer]
ENABLED = true
PROTOCOL = smtp
SMTP_ADDR = smtp-pushover
FROM = forgejo@&#91;REDACTED my domain]
</pre></div>


<p class="wp-block-paragraph">Notice that I can&#8217;t use &#8220;localhost&#8221; here, because that would be the Forgejo&#8217;s localhost. So I have to use &#8220;<code>smtp-pushover</code>&#8220;, which is the internal hostname of the SMTP service inside the Docker network.</p>



<h2 class="wp-block-heading" id="creating-the-forgejo-api-personal-access-token-pat">Creating the Forgejo API Personal Access Token (PAT)</h2>



<p class="wp-block-paragraph">There are two ways you can do this: either use your own API token, or create a new user for the Renovate service. The former is easier, but I prefer to have separate users for specific purposes, so I went with the latter.</p>



<p class="wp-block-paragraph">If you want to use your own user token:</p>



<ul class="wp-block-list">
<li>On the web UI, click on your user picture on the top right, then click &#8220;Settings&#8221;, then &#8220;Applications&#8221;.</li>



<li>On that screen, give the token a name, then select the permissions <a href="https://docs.renovatebot.com/modules/platform/gitea/">as described in this document</a>.</li>



<li>Once you click &#8220;Generate token&#8221;, it will show up <strong>only one time</strong> on your screen. So make sure to copy it into a safe place.</li>
</ul>



<p class="wp-block-paragraph">If you want a separate user:</p>



<ul class="wp-block-list">
<li>On the web UI, click on your user picture on the top right, then click &#8220;Site Administration&#8221;, then &#8220;Identity &amp; Access&#8221;, then &#8220;User accounts&#8221;.</li>



<li>Create a new user account, and make note of the password. Make sure to <strong>uncheck</strong> the box that says &#8220;Require user to change password (recommended)&#8221;. I gave mine the username &#8220;renovatebot&#8221;, and the email &#8220;renovatebot@[REDACTED my domain]&#8221;</li>



<li>On the following screen, edit the user and add a &#8220;Full name&#8221;. <strong>This is important</strong>. Without that, it won&#8217;t be able to commit to Git. In my case I called it &#8220;Renovate Bot&#8221;.</li>



<li>Now you have two choices: either you login in to the web UI using that account, and create a token just like the personal token as described before, or you can do it via CLI.</li>



<li>If you want to do it via CLI, here&#8217;s the one-liner:</li>
</ul>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
$ curl -H "Content-Type: application/json" -d '{"name":"renovatebot","scopes":&#91;"write:repository","read:user","write:issue","read:organization","read:misc","read:package"]}' -u renovatebot https://forgejo.&#91;REDACTED my domain]/api/v1/users/renovatebot/tokens
</pre></div>


<ul class="wp-block-list">
<li>This will ask you for the user password.</li>



<li>It will output the API key it will show up <strong>only one time</strong> on your screen. So make sure to copy it into a safe place.</li>



<li>Now go to the repository where you will run the workflow, click on &#8220;Settings&#8221;, then &#8220;Collaboration&#8221;, and add the &#8220;<code>renovatebot</code>&#8221; user.</li>
</ul>



<p class="wp-block-paragraph">No matter which way you created the token, you have to pass it to the Renovate application, which will be executed inside a runner, so it will need to be passed as an action secret.</p>



<ul class="wp-block-list">
<li>On the web UI, click on your user picture on the top right, then click &#8220;Settings&#8221;, then &#8220;Actions&#8221;, then &#8220;Secrets&#8221;.</li>



<li>Add a secret, calling it <code>RENOVATE_TOKEN</code>, and the value being the token you created in the previous steps.</li>
</ul>



<h2 class="wp-block-heading" id="creating-the-github-api-personal-access-token-pat">Creating the GitHub API Personal Access Token (PAT)</h2>



<p class="wp-block-paragraph">This is optional, and only applies if you do have a GitHub account. If you don&#8217;t, you can skip this section.</p>



<p class="wp-block-paragraph">In order to get the release notes for (most of) the updated images, you need a personal access token from GitHub. For that, go to the GitHub web UI, click on your user on the top-right, then click &#8220;Settings&#8221;, scroll all the way to the end and click &#8220;Developer Settings&#8221;. Now click on &#8220;Personal access tokens&#8221; then &#8220;Tokens (classic)&#8221; and generate a new token.</p>



<p class="wp-block-paragraph">Back in your Forgejo web UI, click on your user picture on the top right, then click &#8220;Settings&#8221;, then &#8220;Actions&#8221;, then &#8220;Secrets&#8221;. Create a new secret called <code>RENOVATE_GITHUB_COM_TOKEN</code>, and paste the content that you just copied from GitHub.</p>



<h2 class="wp-block-heading" id="creating-the-renovate-workflow">Creating the Renovate workflow</h2>



<p class="wp-block-paragraph">Now that you have all the foundations ready, you should be good to create your workflow <a href="https://gist.github.com/badnetmask/0f866e3213590a820916239c7aed1cab">by copying my example on GitHub Gist</a>. Just in case you don&#8217;t remember: inside your repository create a directory tree named <code>.forgejo/workflows</code>, and create a YAML file there (with any name) with the contents of the workflow.</p>



<p class="wp-block-paragraph">I need to call your attention to a few details:</p>



<ul class="wp-block-list">
<li>My example workflow will run at the 15 minute mark of every hour. You can tailor that to any time you want, using <a href="https://en.wikipedia.org/wiki/Cron">the crontab notation</a>.
<ul class="wp-block-list">
<li>Optionally you can shorten that time to make it run <a href="https://forgejo.org/docs/v1.20/user/actions/#on">every time you push to the repository</a>. This might be useful while you&#8217;re testing your configuration.</li>
</ul>
</li>



<li>There are two extra Docker volumes which have been added because <a href="/2024/03/03/running-your-private-certificate-authority-with-acme-support/">I use my own private CA</a>.
<ul class="wp-block-list">
<li>Also there is a line starting with &#8220;env-regex&#8221; which was also required because I need to pass my private CA to the underlying application (via the <code>NODE_EXTRA_CA_CERTS</code> variable).</li>
</ul>
</li>



<li>The line that starts with <code>RENOVATE_REPOSITORIES</code> is optional, with a catch. Basically it tells Renovate to run against <strong>this repository only</strong>.
<ul class="wp-block-list">
<li>Optionally you can tell it to run against <strong>all</strong> your repositories by <a href="https://docs.renovatebot.com/self-hosted-configuration/#autodiscover">setting the appropriate configuration option</a>. I didn&#8217;t want to do that right now. Maybe at a later time, when I create some &#8220;maintenance&#8221; repository. For the time being, this workflow is exclusive to this repository.</li>
</ul>
</li>



<li>The RENOVATE_GITHUB_COM_TOKEN is optional (read the section above).</li>
</ul>



<h2 class="wp-block-heading" id="finalizing">Finalizing</h2>



<p class="wp-block-paragraph">Once you push the workflow, if everything is running as expected, you should see a pull request in your repository with the title &#8220;Configure Renovate&#8221;. This is simply a way to tell Renovate that the configuration has been completed, and you agree to allow it to run it&#8217;s course. Go to that pull request and merge it, as any usual merge.</p>



<p class="wp-block-paragraph">Now, if everything is working right, and you have outdated configurations, you should see one or more new pull requests suggesting updates to your applications. I my case, I had two, right on the first run.</p>



<figure class="wp-block-image size-large"><a href="https://mteixeira.wordpress.com/wp-content/uploads/2025/02/screenshot-from-2025-02-16-19-48-51.png"><img loading="lazy" width="880" height="366" data-attachment-id="1461" data-permalink="https://mteixeira.wordpress.com/2025/02/16/running-renovate-on-self-hosted-forgejo/screenshot-from-2025-02-16-19-48-51/" data-orig-file="https://mteixeira.wordpress.com/wp-content/uploads/2025/02/screenshot-from-2025-02-16-19-48-51.png" data-orig-size="880,366" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot From 2025-02-16 19-48-51" data-image-description="" data-image-caption="" data-large-file="https://mteixeira.wordpress.com/wp-content/uploads/2025/02/screenshot-from-2025-02-16-19-48-51.png?w=700" src="https://mteixeira.wordpress.com/wp-content/uploads/2025/02/screenshot-from-2025-02-16-19-48-51.png?w=880" alt="Screenshot of the Forgejo screen, on the &quot;Pull requests&quot; tab, showing a list of three pull requests, where the one with the title &quot;Configure Renovate&quot; has been merged, and the others are pending." class="wp-image-1461" srcset="https://mteixeira.wordpress.com/wp-content/uploads/2025/02/screenshot-from-2025-02-16-19-48-51.png 880w, https://mteixeira.wordpress.com/wp-content/uploads/2025/02/screenshot-from-2025-02-16-19-48-51.png?w=150 150w, https://mteixeira.wordpress.com/wp-content/uploads/2025/02/screenshot-from-2025-02-16-19-48-51.png?w=300 300w, https://mteixeira.wordpress.com/wp-content/uploads/2025/02/screenshot-from-2025-02-16-19-48-51.png?w=768 768w" sizes="(max-width: 880px) 100vw, 880px" /></a></figure>



<h2 class="wp-block-heading" id="conclusion">Conclusion</h2>



<p class="wp-block-paragraph">That&#8217;s it! At this point you should be all set to start seeing automatic notifications to update your applications. And if you followed the bonus steps, you will even receive the notifications via email (or push message on your phone).</p>



<p class="wp-block-paragraph">I hope this has been useful for you. And if you need any help with anything described here, feel free to ask your question in the comments below, or&nbsp;<a href="https://hachyderm.io/@badnetmask">reach out to me on Mastodon</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://mteixeira.wordpress.com/2025/02/16/running-renovate-on-self-hosted-forgejo/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1421</post-id>
		<media:content url="https://2.gravatar.com/avatar/be97b85b50b810ababecb8b92f87be16dfd6bd605465ecf97707b3d363e8fef3?s=96&#38;d=https%3A%2F%2F2.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=PG" medium="image">
			<media:title type="html">netmask</media:title>
		</media:content>

		<media:content url="https://mteixeira.wordpress.com/wp-content/uploads/2025/02/screenshot-from-2025-02-16-19-48-51.png?w=880" medium="image">
			<media:title type="html">Screenshot of the Forgejo screen, on the &#034;Pull requests&#034; tab, showing a list of three pull requests, where the one with the title &#034;Configure Renovate&#034; has been merged, and the others are pending.</media:title>
		</media:content>
	</item>
		<item>
		<title>Sending email notifications via Pushover</title>
		<link>https://mteixeira.wordpress.com/2025/02/16/sending-email-notifications-via-pushover/</link>
					<comments>https://mteixeira.wordpress.com/2025/02/16/sending-email-notifications-via-pushover/#respond</comments>
		
		<dc:creator><![CDATA[badnetmask]]></dc:creator>
		<pubDate>Mon, 17 Feb 2025 02:03:52 +0000</pubDate>
				<category><![CDATA[homelab]]></category>
		<category><![CDATA[tutorial]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[linux]]></category>
		<guid isPermaLink="false">http://mteixeira.wordpress.com/?p=1427</guid>

					<description><![CDATA[In this post I will show you how to configure a very simple service called smtp-pushover, which does nothing more than simply convert an email message into a notification to be sent to your phone via Pushover. I am using this mostly to send pull requests notifications from my self-hosted Forgejo instance. Introduction For a [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">In this post I will show you how to configure a very simple service called <a href="https://github.com/mattbun/smtp-pushover">smtp-pushover</a>, which does nothing more than simply convert an email message into a notification to be sent to your phone via <a href="https://pushover.net/">Pushover</a>. I am using this mostly to <a href="/2025/02/16/running-renovate-on-self-hosted-forgejo/">send pull requests notifications from my self-hosted Forgejo instance</a>.</p>



<ol><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/02/16/sending-email-notifications-via-pushover/#introduction">Introduction</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/02/16/sending-email-notifications-via-pushover/#getting-an-api-key">Getting an API key</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/02/16/sending-email-notifications-via-pushover/#running-the-smtp-pushover-app">Running the smtp-pushover app</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/02/16/sending-email-notifications-via-pushover/#testing">Testing</a></li><li><a class="wp-block-table-of-contents__entry" href="https://mteixeira.wordpress.com/2025/02/16/sending-email-notifications-via-pushover/#conclusion">Conclusion</a></li></ol>



<h2 class="wp-block-heading" id="introduction">Introduction</h2>



<p class="wp-block-paragraph">For a while I have been using <a href="https://pushover.net/">Pushover</a> to sent notifications from <a href="https://github.com/louislam/uptime-kuma">Uptime Kuma</a> to my phone. This is very convenient for me in many ways. Initially I was doing that via <a href="https://mteixeira.wordpress.com/2024/02/19/sending-notifications-from-changedetection-io-to-home-assistant/">Home Assistant</a>, but this got very convoluted, because it started mixing up with automation-related notifications, and not giving me the visibility I wanted. I have used Pushover many many years ago for other abandoned projects, so I decided to go back into it.</p>



<p class="wp-block-paragraph">At some point I realized that some of my self-hosted applications can only send notifications via email, or their notifications being more useful that way (the biggest one being <a href="https://forgejo.org/">Forgejo</a>). This presents a challenge, since I do not host my own email, and I would like to avoid creating dummy accounts just for that. Initially I thought about going with <a href="https://www.mailgun.com/">Mailgun</a>, which was recommended by a lot of people in the home lab community on Mastodon, but I backed away from it because email is not encrypted, so I would be exposing a lot of my internal data to 3rd parties.</p>



<p class="wp-block-paragraph">So I found this nice application (<a href="https://github.com/mattbun/smtp-pushover">smtp-pushover</a>) that accepts email messages and sends them to me as Pushover notifications. While there is still unencrypted email involved, its only local to my servers, and they only exist in memory, until dispatched to the Pushover API. The biggest advantage of using Pushover for delivery is that <a href="https://blog.pushover.net/posts/2023/12/encryption">all the notifications are encrypted</a>, and on top of that, <a href="https://support.pushover.net/i39-notification-storage-and-delivery">they are very short-lived</a>.</p>



<h2 class="wp-block-heading" id="getting-an-api-key">Getting an API key</h2>



<p class="wp-block-paragraph">The first step is to get an API key. If you&#8217;re a Pushover user, you probably already know how to do that, but it doesn&#8217;t hurt to get a refresher (this is not something we do very often).</p>



<ul class="wp-block-list">
<li>Log into your Pushover account.</li>



<li>Scroll down to &#8220;Your Applications&#8221;.</li>



<li>Click on &#8220;Create an Application/API Token&#8221;.</li>



<li>All you need here is a name, and I simply called it &#8220;smtp-pushover&#8221;.</li>



<li>Click on the &#8220;Create Application&#8221; and you will be redirected to the page where you can copy the key.</li>
</ul>



<h2 class="wp-block-heading" id="running-the-smtp-pushover-app">Running the smtp-pushover app</h2>



<p class="wp-block-paragraph">Before running the app, you need to save the API key somewhere. Since I run the app in Docker Compose, with the definition file stored in Git, it&#8217;s better to put the secrets somewhere else. For that, I created a simple file named <code>.smtp-pushover.env</code> on the same directory as my compose file, then added my user key, and the application token.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
PORT=25 # optional, defaults to 25
PUSHOVER_USER=...
PUSHOVER_TOKEN=...
</pre></div>


<p class="wp-block-paragraph">Now you can add the following to your compose file:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: yaml; title: ; notranslate">
  smtp-pushover:
    restart: unless-stopped
    container_name: smtp-pushover
    image: ghcr.io/mattbun/smtp-pushover:0.5.31
    ports:
      - "25:25"
    env_file:
      - .smtp-pushover.env
</pre></div>


<p class="wp-block-paragraph">Note that the image version is optional. You can use &#8220;latest&#8221; if you want. But I started to use explicit version numbers because <a href="/2025/02/16/running-renovate-on-self-hosted-forgejo/">I will begin using Renovate</a> to update my containers.</p>



<p class="wp-block-paragraph">Now you can restart your compose.</p>



<h2 class="wp-block-heading" id="testing">Testing</h2>



<p class="wp-block-paragraph">The developer suggested a one-liner for testing, that goes like this:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
$ echo -e "Subject:Test\n\nHello" | msmtp blah@blah.com
</pre></div>


<p class="wp-block-paragraph">Run this, and you should have a notification on your phone. Great!</p>



<p class="wp-block-paragraph">But, hang on. I don&#8217;t have <code>msmtp</code> installed, and I don&#8217;t want to. So here&#8217;s a trick about how you can do that manually.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
$ telnet localhost 25
Trying ::1...
Connected to localhost.
Escape character is &#039;^]&#039;.
220 a9e93df6e1b5 ESMTP
helo localhost
250 a9e93df6e1b5 Nice to meet you, &#91;172.23.0.1]
mail from: &lt;test@localhost&gt;
250 Accepted
rcpt to: &lt;example@example.com&gt;
250 Accepted
data                  
354 End data with &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;
Subject: pushover test

this should work fine
.
250 {&quot;status&quot;:1,&quot;request&quot;:&quot;79793830-05db-7009-92e6-7f2ef74a92a5&quot;}
^]
telnet&gt; quit
</pre></div>


<p class="wp-block-paragraph">Oh man. I haven&#8217;t done that in ages!</p>



<p class="wp-block-paragraph">But anyway, I do have a fully working notification service!</p>



<h2 class="wp-block-heading" id="conclusion">Conclusion</h2>



<p class="wp-block-paragraph">Now for any application that you want to send an email, simply point them to use that host as their SMTP server. If the application is running on the same compose, make sure to point it to &#8220;smtp-pushover&#8221;. If it&#8217;s a stand-alone application running on the same host, you can point to localhost.</p>



<p class="wp-block-paragraph">Notice that this is still insecure, and unencrypted, so you need to make your own decisions about your level of exposure. In my case, I block port 25 inside my network, and have one instance of that service running in each of my servers, so I can simply use &#8220;localhost&#8221; as the SMTP endpoint.</p>



<p class="wp-block-paragraph">I hope this has been useful for you. And if you need any help with anything described here, feel free to ask your question in the comments below, or&nbsp;<a href="https://hachyderm.io/@badnetmask">reach out to me on Mastodon</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://mteixeira.wordpress.com/2025/02/16/sending-email-notifications-via-pushover/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1427</post-id>
		<media:content url="https://2.gravatar.com/avatar/be97b85b50b810ababecb8b92f87be16dfd6bd605465ecf97707b3d363e8fef3?s=96&#38;d=https%3A%2F%2F2.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=PG" medium="image">
			<media:title type="html">netmask</media:title>
		</media:content>
	</item>
	</channel>
</rss>
