<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Infrastructure on Artur Rios Lab</title><link>https://artur-rios.github.io/personal-lab/infrastructure/</link><description>Recent content in Infrastructure on Artur Rios Lab</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Wed, 21 Oct 2026 17:55:00 +0000</lastBuildDate><atom:link href="https://artur-rios.github.io/personal-lab/infrastructure/index.xml" rel="self" type="application/rss+xml"/><item><title>Deploying With Yggdrasil</title><link>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-deploys/</link><pubDate>Wed, 21 Oct 2026 17:55:00 +0000</pubDate><guid>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-deploys/</guid><description>&lt;p&gt;&lt;a href="https://github.com/artur-rios/yggdrasil"&gt;Yggdrasil&lt;/a&gt; is the deploy manager I&#10;built for my server. It does one job: take an application from a GitHub&#10;repository to a healthy container in the right environment, and back to the&#10;previous version if the new one does not come up. This part follows a change&#10;from a pull request to production.&lt;/p&gt;&#10;&lt;h2 id="one-catalog"&gt;One catalog&lt;a class="heading-anchor" href="#one-catalog" aria-label="Link to this section"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Everything yggdrasil knows comes from one file, &lt;code&gt;catalog.yaml&lt;/code&gt;, in three&#10;layers:&lt;/p&gt;</description></item><item><title>Documentation Sites</title><link>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-documentation-sites/</link><pubDate>Wed, 21 Oct 2026 17:55:00 +0000</pubDate><guid>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-documentation-sites/</guid><description>&lt;p&gt;Every project with users gets a documentation site: what it is, how to install&#10;and use it, and its reference. Heimdall&amp;rsquo;s documentation is one, and so is the&#10;documentation of my .NET libraries. They are all built the same way.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Concern&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Choice&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Generator&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Hugo, extended edition&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Theme&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Docsy, as a Hugo module&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Hosting&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;GitHub Pages&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Deploys&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;GitHub Actions&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;DNS&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Cloudflare, optional&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="why-hugo"&gt;Why Hugo&lt;a class="heading-anchor" href="#why-hugo" aria-label="Link to this section"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Hugo is a single binary that builds thousands of pages in about a second, and&#10;documentation is Markdown in a folder, next to the code it describes, reviewed&#10;in the same pull requests. Docsy is a documentation theme from Google with&#10;navigation, search and versioned sections built in.&lt;/p&gt;</description></item><item><title>How I Host My Projects</title><link>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-overview/</link><pubDate>Wed, 21 Oct 2026 17:55:00 +0000</pubDate><guid>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-overview/</guid><description>&lt;p&gt;Every project eventually has to run somewhere, and deciding where for each one&#10;separately is how a hobby turns into a pile of accounts, bills and half-&#10;remembered setups. So I decided once, by kind of project, and every new project&#10;inherits the answer. This series describes that setup, as it runs today.&lt;/p&gt;&#10;&lt;h2 id="the-decision-table"&gt;The decision table&lt;a class="heading-anchor" href="#the-decision-table" aria-label="Link to this section"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;I&amp;rsquo;m building&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Stack&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Hosting&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;A marketing or product page&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Next.js with shadcn/ui&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Vercel, behind Cloudflare&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;A static portfolio or brochure site&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Astro, or a Next.js static export&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Cloudflare Pages or GitHub Pages&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Technical documentation&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Hugo with the Docsy theme&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;GitHub Pages&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;An interactive web app&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Flutter, built for the web&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;My server: Docker, Traefik, yggdrasil&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;A backend API&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;ASP.NET Core&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;My server: Docker, Traefik, yggdrasil&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;A reusable package&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;NuGet, or the language&amp;rsquo;s own format&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;nuget.org and GitHub Packages&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="the-rule-behind-it"&gt;The rule behind it&lt;a class="heading-anchor" href="#the-rule-behind-it" aria-label="Link to this section"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;&lt;strong&gt;Anything with a running backend is self-hosted; everything else is not.&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Landing Pages</title><link>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-landing-pages/</link><pubDate>Wed, 21 Oct 2026 17:55:00 +0000</pubDate><guid>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-landing-pages/</guid><description>&lt;p&gt;A landing page sells something: a hero section, a grid of features, pricing,&#10;a contact or waiting-list form. It has to look polished, load fast everywhere,&#10;and occasionally do something dynamic, such as accept a form. That last part is&#10;why landing pages are not plain &lt;a href="https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-static-sites/"&gt;static sites&lt;/a&gt;.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Concern&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Choice&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Framework&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Next.js, with the App Router&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;UI&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;shadcn/ui and Tailwind CSS&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Hosting&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Vercel, on the free tier&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;DNS&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Cloudflare&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Deploys&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Vercel&amp;rsquo;s Git integration&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="building-one"&gt;Building one&lt;a class="heading-anchor" href="#building-one" aria-label="Link to this section"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;div class="code"&gt;&#10; &lt;div class="code__bar"&gt;&#10; &lt;span&gt;bash&lt;/span&gt;&#10; &lt;button class="code__copy" type="button" data-nuno-copy aria-label="Copy code to clipboard"&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npx create-next-app@latest my-landing --ts --tailwind --app --eslint&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; my-landing&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npx shadcn@latest init&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npx shadcn@latest add button card input navigation-menu&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;shadcn/ui is not a component library installed as a dependency: it copies each&#10;component&amp;rsquo;s source into the project, built on Radix and Tailwind, so every&#10;component can be changed like my own code.&lt;/p&gt;</description></item><item><title>Libraries</title><link>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-libraries/</link><pubDate>Wed, 21 Oct 2026 17:55:00 +0000</pubDate><guid>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-libraries/</guid><description>&lt;p&gt;Code that more than one project needs becomes a package. Mine are .NET&#10;libraries, published as NuGet packages to two places: &lt;strong&gt;nuget.org&lt;/strong&gt;, where&#10;anyone finds them, and &lt;strong&gt;GitHub Packages&lt;/strong&gt;, next to their source.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Concern&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Choice&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Package format&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;NuGet, or the language&amp;rsquo;s own format elsewhere&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Primary feed&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;nuget.org&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Secondary feed&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;GitHub Packages&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Publishing&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;GitHub Actions, on a version tag&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Versioning&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Semantic versioning&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="package-metadata"&gt;Package metadata&lt;a class="heading-anchor" href="#package-metadata" aria-label="Link to this section"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;The project file describes the package, so &lt;code&gt;dotnet pack&lt;/code&gt; produces a complete&#10;&lt;code&gt;.nupkg&lt;/code&gt; with its symbols alongside:&lt;/p&gt;</description></item><item><title>Sizing the Server</title><link>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-sizing/</link><pubDate>Wed, 21 Oct 2026 17:55:00 +0000</pubDate><guid>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-sizing/</guid><description>&lt;p&gt;Before an application goes on &lt;a href="https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-server/"&gt;the server&lt;/a&gt;,&#10;it should be clear that it fits. This is the model I size the server with: one&#10;figure per service for what it needs, a formula that adds headroom, and the&#10;signals that say the model has stopped being true.&lt;/p&gt;&#10;&lt;p&gt;The figures are &lt;strong&gt;starting estimates&lt;/strong&gt; for a small production load, a few&#10;requests a second on each API, not measurements. After a week of real traffic,&#10;Grafana&amp;rsquo;s numbers replace them.&lt;/p&gt;</description></item><item><title>Static Sites</title><link>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-static-sites/</link><pubDate>Wed, 21 Oct 2026 17:55:00 +0000</pubDate><guid>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-static-sites/</guid><description>&lt;p&gt;A static site is rendered to HTML, CSS and JavaScript when it is built, and&#10;served as files from then on: portfolios, brochure sites, event pages and small&#10;blogs. With no code running on a server, it can be hosted for free on a CDN and&#10;will never go down for want of a patch.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Concern&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Choice&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Generator&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Astro for content-first sites, or Next.js with a static export&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Styling&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Tailwind CSS&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Hosting&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Cloudflare Pages, or GitHub Pages&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Deploys&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;The host&amp;rsquo;s Git integration, or GitHub Actions&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Its neighbours in this series draw the line: a page that needs a form or a&#10;server function is a &lt;a href="https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-landing-pages/"&gt;landing page&lt;/a&gt;,&#10;on Vercel, and reference documentation is a&#10;&lt;a href="https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-documentation-sites/"&gt;documentation site&lt;/a&gt;,&#10;in Hugo.&lt;/p&gt;</description></item><item><title>The Platform: Traefik, Monitoring and the Console</title><link>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-platform/</link><pubDate>Wed, 21 Oct 2026 17:55:00 +0000</pubDate><guid>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-platform/</guid><description>&lt;p&gt;On top of &lt;a href="https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-server/"&gt;the server&lt;/a&gt; runs one&#10;Compose project, &lt;code&gt;yggdrasil&lt;/code&gt;, that every application shares: the reverse proxy,&#10;the monitoring, and a status API with its console. It is started once per host,&#10;and serves every environment on that host.&lt;/p&gt;&#10;&lt;div class="code"&gt;&#10; &lt;div class="code__bar"&gt;&#10; &lt;span&gt;bash&lt;/span&gt;&#10; &lt;button class="code__copy" type="button" data-nuno-copy aria-label="Copy code to clipboard"&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;scripts/platform.sh up&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Component&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Version&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Job&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Traefik&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;v3.6&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Routes every request, terminates TLS&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Docker socket proxies&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.13.1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Give Traefik, Alloy and the status API read-only access to Docker&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Status API&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;.NET 10&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Health, version and deploy time of every application&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Console&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Flutter&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Shows the status API, on the web, Windows and Android&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Prometheus&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;v3.5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Metrics, kept 30 days&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Loki&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Logs, kept 31 days&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Alloy&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;v1.10&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Ships every container&amp;rsquo;s logs to Loki&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Grafana&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;12.1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Dashboards over Prometheus and Loki&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Jenkins&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;LTS&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Deploys, covered in &lt;a href="https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-deploys/"&gt;Deploying With Yggdrasil&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="traefik-and-one-wildcard-certificate"&gt;Traefik and one wildcard certificate&lt;a class="heading-anchor" href="#traefik-and-one-wildcard-certificate" aria-label="Link to this section"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Traefik is the only container that publishes ports, 80 and 443, and port 80&#10;does nothing but redirect to 443. It watches Docker for containers that opt in&#10;with labels, so a new application is routed the moment it starts, without&#10;touching Traefik&amp;rsquo;s configuration.&lt;/p&gt;</description></item><item><title>The Server</title><link>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-server/</link><pubDate>Wed, 21 Oct 2026 17:55:00 +0000</pubDate><guid>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-server/</guid><description>&lt;p&gt;Everything self-hosted runs on one machine: the platform, the deploy server,&#10;and every application in three environments. This part covers the machine&#10;itself and what is installed on it directly, outside any container.&lt;/p&gt;&#10;&lt;h2 id="the-machine"&gt;The machine&lt;a class="heading-anchor" href="#the-machine" aria-label="Link to this section"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Resource&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Spec&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Provider&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Hostinger VPS&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;vCPU&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4 cores&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;RAM&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;16 GB&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Disk&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;200 GB NVMe&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Bandwidth&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;16 TB a month&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;OS&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Ubuntu LTS&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;One machine is a single point of failure, and I accept that knowingly: these&#10;are my own projects, an outage costs me an evening, and one well-understood&#10;server is easier to keep healthy than three. Whether everything fits on it is&#10;the subject of &lt;a href="https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-sizing/"&gt;Sizing the Server&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Web APIs</title><link>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-web-apis/</link><pubDate>Wed, 21 Oct 2026 17:55:00 +0000</pubDate><guid>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-web-apis/</guid><description>&lt;p&gt;My backends are ASP.NET Core APIs, and every one of them meets the platform the&#10;same way. Heimdall&amp;rsquo;s API is the example throughout; Fortuna&amp;rsquo;s follows the same&#10;shape.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Concern&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Choice&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Framework&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;ASP.NET Core on .NET 10&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Image&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Multi-stage: the SDK to build, the ASP.NET runtime to run&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Database&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;The host&amp;rsquo;s PostgreSQL, one database per environment&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Configuration&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Environment variables, from yggdrasil&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Health&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;/healthcheck&lt;/code&gt;, probed by Docker, yggdrasil and the status API&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Metrics&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;OpenTelemetry&amp;rsquo;s Prometheus exporter, on port 9464&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Logs&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Standard output, shipped to Loki&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Checks&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Tests and a Docker build in GitHub Actions&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="the-image"&gt;The image&lt;a class="heading-anchor" href="#the-image" aria-label="Link to this section"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;The Dockerfile has two stages. The first restores and publishes with the .NET&#10;10 SDK, copying the project files before the sources so a source-only change&#10;reuses the cached restore. The second copies the output onto the much smaller&#10;ASP.NET runtime image, and runs it as the image&amp;rsquo;s own non-root user:&lt;/p&gt;</description></item><item><title>Web Apps</title><link>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-web-apps/</link><pubDate>Wed, 21 Oct 2026 17:55:00 +0000</pubDate><guid>https://artur-rios.github.io/personal-lab/infrastructure/infrastructure-web-apps/</guid><description>&lt;p&gt;My interactive front ends are Flutter apps, and on the web they are a set of&#10;static files: HTML, JavaScript and WebAssembly. So the container that runs one&#10;is just a web server, small and stateless. Heimdall&amp;rsquo;s web app is the example.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Concern&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Choice&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Framework&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Flutter, built for the web&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Build stage&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Debian with a Flutter SDK pinned by version and checksum&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Runtime&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;nginx, unprivileged, with a read-only file system&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Settings&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Compiled in at build time, once per environment&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;API&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Its own origin&amp;rsquo;s &lt;code&gt;/api/&lt;/code&gt;, routed to the API by Traefik&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Health&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;/healthz&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Checks&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Analysis and tests, and generated code checked in GitHub Actions&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="the-image"&gt;The image&lt;a class="heading-anchor" href="#the-image" aria-label="Link to this section"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;The build stage installs Flutter itself, rather than starting from a community&#10;Flutter image, so the exact SDK version is pinned and its download is checked&#10;against a SHA-256 checksum. It builds as a non-root user, and fetches packages&#10;with &lt;code&gt;--enforce-lockfile&lt;/code&gt;, so the build uses exactly the versions the&#10;repository was tested with:&lt;/p&gt;</description></item></channel></rss>