<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Self-Hosting on paradigmatic.systems</title>
    <link>https://paradigmatic.systems/tags/self-hosting/</link>
    <description>Recent content in Self-Hosting on paradigmatic.systems</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Sun, 12 Apr 2026 04:30:00 +0000</lastBuildDate><atom:link href="https://paradigmatic.systems/tags/self-hosting/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Self-Hosting with NixOS</title>
      <link>https://paradigmatic.systems/posts/self-hosting-nixos/</link>
      <pubDate>Sun, 12 Apr 2026 04:30:00 +0000</pubDate>
      
      <guid>https://paradigmatic.systems/posts/self-hosting-nixos/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been meaning to upgrade my home network based on some of the lessons I&amp;rsquo;ve learned deploying NixOS on a VPS. I&amp;rsquo;ve got a few of these little ProDesk machines that I got on eBay a while back. They&amp;rsquo;re great if you want some lightweight processes running 24/7. But they were still running Ubuntu! Oof! Time for an overhaul.&lt;/p&gt;
&lt;h2 id=&#34;1-deploy-over-network-with-deploy-rs&#34;&gt;1: Deploy Over Network with deploy-rs&lt;/h2&gt;
&lt;p&gt;Based on my &lt;a href=&#34;https://paradigmatic.systems/posts/setting-up-deploy-rs&#34;&gt;experience&lt;/a&gt; with a DigitalOcean VPS I knew I wanted to use the &lt;code&gt;deploy-rs&lt;/code&gt; model on a local node. Here&amp;rsquo;s the basic idea:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I&rsquo;ve been meaning to upgrade my home network based on some of the lessons I&rsquo;ve learned deploying NixOS on a VPS. I&rsquo;ve got a few of these little ProDesk machines that I got on eBay a while back. They&rsquo;re great if you want some lightweight processes running 24/7. But they were still running Ubuntu! Oof! Time for an overhaul.</p>
<h2 id="1-deploy-over-network-with-deploy-rs">1: Deploy Over Network with deploy-rs</h2>
<p>Based on my <a href="/posts/setting-up-deploy-rs">experience</a> with a DigitalOcean VPS I knew I wanted to use the <code>deploy-rs</code> model on a local node. Here&rsquo;s the basic idea:</p>
<ul>
<li>The node configuration lives in version control.</li>
<li>You edit it and build it on your workstation.</li>
<li>You push the complete build over the network onto the node.</li>
<li>Errors are caught with atomic rollbacks.</li>
</ul>
<p>That means I don&rsquo;t need physical access to the machine. It can live in the basement with the router. I don&rsquo;t even need to SSH in. Just edit the config and <code>deploy</code>. The process is very similar to the instructions in my other post:</p>
<ol>
<li>Install NixOS on the node</li>
<li>Modify the configuration:
<ul>
<li>Add your workstation SSH key to <code>users.root.openssh.authorizedKeys.keys</code></li>
<li>Enable Flakes</li>
</ul>
</li>
<li>Copy the contents of <code>/etc/nixos/</code> to your workstation and wrap them in a flake that specifies its hostname.</li>
<li>Add the <code>deploy-rs</code> configuration and then <code>deploy</code>.</li>
<li>Your node is unchanged, but now the configuration lives on your workstation and in version control.</li>
</ol>
<p>Just make sure your SSH keys are loaded with <code>ssh-add</code> - it&rsquo;s basic but this tripped me up as I thought it was a user configuration issue.</p>
<h2 id="2-define-a-dns-overlay-server-with-dnsmasq">2: Define a DNS Overlay Server with dnsmasq</h2>
<p>If you&rsquo;ve served custom domains over LAN before, you may have gone into your router settings and added custom DNS entries. This is something I wanted to avoid: I wanted to be able to add new domains purely in my node configuration.</p>
<p>So the first step was to go to a different place in the router settings. This will be different for everyone. For me, it was under Broadband Connection settings -&gt; &ldquo;Obtain IPv4 DNS Addresses Automatically&rdquo;. I adjusted this to point to the node&rsquo;s IP.</p>
<p>Next, configuring the node to act as a DNS server was easy as pie with the <code>dnsmasq</code> module:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>services<span style="color:#f92672">.</span>dnsmasq <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>  settings <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    server <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#34;8.8.8.8&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#34;8.8.4.4&#34;</span>
</span></span><span style="display:flex;"><span>    ];
</span></span><span style="display:flex;"><span>    domain <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;home&#34;</span>;
</span></span><span style="display:flex;"><span>    local <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;/home/&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    address <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#34;/test.home/mynode&#34;</span>
</span></span><span style="display:flex;"><span>    ];
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>I tried using a cute custom domain extension but couldn&rsquo;t get that to work. No big deal and I don&rsquo;t care enough to pull that thread: <code>.home</code> and <code>.lan</code> both worked.</p>
<p>Now since that domain is resolving to the same node, we need to add a virtual host. The endgame is to reverse proxy these domains to various services, but we&rsquo;ll start with a static response:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>services<span style="color:#f92672">.</span>nginx <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  virtualHosts<span style="color:#f92672">.</span><span style="color:#e6db74">&#34;test.home&#34;</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    locations<span style="color:#f92672">.</span><span style="color:#e6db74">&#34;/&#34;</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>      return <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;200 &#39;hello&#39;&#34;</span>;
</span></span><span style="display:flex;"><span>      extraConfig <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        add_header Content-Type text/plain;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      &#39;&#39;</span>;
</span></span><span style="display:flex;"><span>    };
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>If everything worked, you should be able to hit <code>http://test.home</code> from any device on your network, and get a nice little <code>hello</code>!</p>
<h2 id="3-configure-frigate-net-video-recorder">3: Configure Frigate (Net Video Recorder)</h2>
<p>If you&rsquo;ve followed to this point, then your system is a blank slate for adding new services under custom domains. I&rsquo;ll walk through this <a href="https://frigate.video/">Frigate</a> example since that&rsquo;s the main thing I was migrating! It was straightforward configuring the rtsp feed of my IP cameras. But with naive settings the CPU usage was very high. It took some fiddling to get the Intel hardware acceleration enabled and reduce some of the overhead. I&rsquo;ll annotate this fragment:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>services<span style="color:#f92672">.</span>frigate <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>  hostname <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;localhost&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  settings <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    mqtt<span style="color:#f92672">.</span>enabled <span style="color:#f92672">=</span> <span style="color:#66d9ef">false</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#This enables the Intel hardware acceleration</span>
</span></span><span style="display:flex;"><span>    ffmpeg <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>      hwaccel_args <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;preset-vaapi&#34;</span>;
</span></span><span style="display:flex;"><span>    };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    cameras<span style="color:#f92672">.</span><span style="color:#e6db74">&#34;front_camera&#34;</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>      ffmpeg<span style="color:#f92672">.</span>inputs <span style="color:#f92672">=</span> [{
</span></span><span style="display:flex;"><span>        path <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;rtsp://admin:pwd@192.168.1.54:554/h264Preview_01_main&#34;</span>;
</span></span><span style="display:flex;"><span>        roles <span style="color:#f92672">=</span> [ <span style="color:#e6db74">&#34;record&#34;</span> ];
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#75715e"># This second input needs to be specified even though we&#39;re not</span>
</span></span><span style="display:flex;"><span>      <span style="color:#75715e"># doing detection, otherwise a high def feed is getting fed </span>
</span></span><span style="display:flex;"><span>      <span style="color:#75715e"># through to the dashboard! </span>
</span></span><span style="display:flex;"><span>      {
</span></span><span style="display:flex;"><span>        path <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;rtsp://admin:pwd@192.168.1.54:554/h264Preview_01_sub&#34;</span>;
</span></span><span style="display:flex;"><span>        roles <span style="color:#f92672">=</span> [ <span style="color:#e6db74">&#34;detect&#34;</span> ];
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      ];
</span></span><span style="display:flex;"><span>      detect<span style="color:#f92672">.</span>enabled <span style="color:#f92672">=</span> <span style="color:#66d9ef">false</span>;
</span></span><span style="display:flex;"><span>    };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    record <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>      enabled <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>      retain <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>        days <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>        mode <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;all&#34;</span>;
</span></span><span style="display:flex;"><span>      };
</span></span><span style="display:flex;"><span>    };
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>This keeps a trailing 24 hours of recording.</p>
<h3 id="intel-hardware-acceleration">Intel Hardware Acceleration</h3>
<p>This took a lot of trial and error, but got CPU usage down significantly (like, 75% to 2%)! Without knowing much about it, I will present without commentary.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>systemd<span style="color:#f92672">.</span>services<span style="color:#f92672">.</span>frigate <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  environment<span style="color:#f92672">.</span>LIBVA_DRIVER_NAME <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;iHD&#34;</span>;
</span></span><span style="display:flex;"><span>  serviceConfig <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    SupplementaryGroups <span style="color:#f92672">=</span> [ <span style="color:#e6db74">&#34;render&#34;</span> <span style="color:#e6db74">&#34;video&#34;</span> ];
</span></span><span style="display:flex;"><span>    DeviceAllow <span style="color:#f92672">=</span> [ <span style="color:#e6db74">&#34;/dev/dri/renderD128&#34;</span> ];
</span></span><span style="display:flex;"><span>    AmbientCapabilities <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;CAP_PERFMON&#34;</span>;
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>  
</span></span></code></pre></div><p>And in the <code>hardware-configuration.nix</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>hardware<span style="color:#f92672">.</span>opengl <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  enable <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>  extraPackages <span style="color:#f92672">=</span> <span style="color:#66d9ef">with</span> pkgs; [
</span></span><span style="display:flex;"><span>    intel-media-driver
</span></span><span style="display:flex;"><span>    intel-vaapi-driver
</span></span><span style="display:flex;"><span>  ];
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><h3 id="reverse-proxy-to-frigate-default-port">Reverse Proxy to Frigate Default Port</h3>
<p>Last but not least, we need to add the address in <code>dnsmasq.settings.address</code>, and the reverse proxy config:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nix" data-lang="nix"><span style="display:flex;"><span>virtualHosts<span style="color:#f92672">.</span><span style="color:#e6db74">&#34;frigate.home&#34;</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  locations<span style="color:#f92672">.</span><span style="color:#e6db74">&#34;/&#34;</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    proxyPass <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;http://127.0.0.1:5000&#34;</span>;
</span></span><span style="display:flex;"><span>    proxyWebsockets <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>; 
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>The websocket part is important as Frigate uses them for live views.</p>
<p>That&rsquo;s it! Works like a charm.</p>
<h2 id="recap">Recap</h2>
<p>We&rsquo;re in a pretty good spot. We&rsquo;ve got:</p>
<ul>
<li>Access to the entire corpus of <code>nixpkgs</code> with modules for many incredible open-source tools.</li>
<li>Version-controlled configuration.</li>
<li>Network deployments with atomic rollback.</li>
</ul>
<p>That means we can host new services (like Jellyfin, Immich, NextCloud, Home Assistant, etc) with a few steps:</p>
<ol>
<li>Enable it: <code>services.jellyfin.enable = true;</code></li>
<li>Reverse proxy with <code>nginx</code>.</li>
<li>Add a DNS entry with <code>dnsmasq</code>.</li>
<li><code>deploy</code>.</li>
</ol>
<p>You have basically zero risk of bricking the machine with a failed experiment, or interfering at all with your existing services. If something goes awry you simply roll back the node and revert the config changes.</p>
<h2 id="next-steps">Next Steps?</h2>
<p>I reserve the right to work (or not) in any of these directions:</p>
<ul>
<li>
<p>Being an affirmed <a href="/posts/phoenix-in-nix">Elixir</a> enjoyer, I&rsquo;m tempted to make a LiveView dashboard. That would make it really easy to wrap Frigate and augment with whatever custom live logic I want.</p>
</li>
<li>
<p>I&rsquo;ve been wanting an excuse to try out Slack&rsquo;s <a href="https://github.com/slackhq/nebula">Nebula</a>. The idea there would be to make a network overlay connecting my VPS to the local node. Then I could (very carefully) access my services from public internet.</p>
</li>
<li>
<p>Once we&rsquo;ve got that, why not introduce some BEAM clustering so our LiveView backend is distributed? I have as many reasons to do that as I have not to! (It&rsquo;s zero.)</p>
</li>
</ul>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
