{
  "version": "https://jsonfeed.org/version/1",
  "title": "Ian's Digital Garden",
  "home_page_url": "https://ianwwagner.com/",
  "feed_url": "https://ianwwagner.com//tag-homelab.json",
  "description": "",
  "items": [
    {
      "id": "https://ianwwagner.com//overview-of-my-new-homelab-setup.html",
      "url": "https://ianwwagner.com//overview-of-my-new-homelab-setup.html",
      "title": "Overview of my New Homelab Setup",
      "content_html": "<p>For various reasons, I recently decided to start running a homelab again.\nIn this post I'll give a broad overview of the architecture I settled on,\nand why I made specific choices.\nI'll try to keep things relatively high level here,\nand we'll dive into the implementation details in future posts.</p>\n<h1><a href=\"#my-requirements\" aria-hidden=\"true\" class=\"anchor\" id=\"my-requirements\"></a>My requirements</h1>\n<p>Let's start from the beginning with what I'm trying to achieve.\nI want to host multiple services, some of them internet-accessible,\nfrom a box on my desk running FreeBSD.\nSpecifically FreeBSD, because I find it has an uncommon trio of properties:</p>\n<ul>\n<li>It's (really) easy to administer.</li>\n<li>It's rock solid stable.</li>\n<li>It has a huge collection of up-to-date packages.</li>\n</ul>\n<p>It's surprisingly rare to find all 3 in a system.\nThe last 2 in particular are often mutually exclusive.</p>\n<p>I already have the hardware (an old NUC, which I've named <code>bsdcube</code> even though it's not a cube),\nand my home internet is rock solid, but I don't have a static IP.\nEven if I did, I'm not sure I want to run some services on a residential IP,\nor to invite attacks on my home network.</p>\n<h1><a href=\"#broad-architecture-overview\" aria-hidden=\"true\" class=\"anchor\" id=\"broad-architecture-overview\"></a>Broad architecture overview</h1>\n<p>The architecture that I chose keeps <code>bsdcube</code> on my home network behind the NAT firewall.\nTo make the server accessible from the internet,\nI use a WireGuard tunnel to a remote host that is internet-accessible.\nPF rules on the accessible server route certain traffic over the tunnel and block the rest.</p>\n<pre><code class=\"language-txt\"> +----------+       +---------+                         +---------+\n | Internet |------&gt;| proxbox |&lt;---WireGuard-Tunnel----&gt;| bsdcube |\n +----------+       +---------+                         +---------+\n</code></pre>\n<h1><a href=\"#choosing-a-host-for-proxbox\" aria-hidden=\"true\" class=\"anchor\" id=\"choosing-a-host-for-proxbox\"></a>Choosing a host for <code>proxbox</code></h1>\n<p>For my internet-accessible box, which I dubbed <code>proxbox</code>,\nI wanted a reliable host that supported a BSD.\nI was initially planning on getting a FreeBSD box for the job.\nI even had a box set up, as described in <a href=\"setting-up-a-wireguard-tunnel-on-freebsd-15.html\">my post on the subject</a>.\n(I guess I'll need to write a follow-up now!)</p>\n<p>I was somewhat discouraged by the poor support of FreeBSD on most cloud providers.\nThere is ironically a split where a lot of the mega-hosts like AWS have tier 1 support,\nbut there's nothing in the middle.\nMany of the midrange hosts which had good FreeBSD VM support now require a tedious manual install process.\nVultr seemed to be the only host that had at least some level of support,\nbut they were not my first choice.</p>\n<p>Then my friend <a href=\"https://andrewzah.com/blog/\">Andrew</a> mentioned <a href=\"https://openbsd.amsterdam/\">OpenBSD Amsterdam</a>.\nIt was love at first sight.\nI had, somehow (despite being a FreeBSD using since around 2002) never actually used OpenBSD.\nBut I loved their pitch: a community-focused host that donates a large part of every purchase\nto the OpenBSD foundation.</p>\n<p>The onboarding was fast and easy.\nI filled out the form, and within 2 hours I had an email with connection instructions!\nI hadn't even been given an invoice yet!\nReally fantastic service.</p>\n<h1><a href=\"#proxbox-setup-using-the-openbsd-base-system\" aria-hidden=\"true\" class=\"anchor\" id=\"proxbox-setup-using-the-openbsd-base-system\"></a><code>proxbox</code> setup using the OpenBSD base system</h1>\n<p>Now let's talk about the guts of <code>proxbox</code>.\nSurprisingly, almost everything running on this box is part of the OpenBSD base system!</p>\n<pre><code class=\"language-txt\">                            +-------+                                                     \n  /--\\      +-----HTTP-----&gt;| httpd |                                                     \n /    \\ ----+               |       |                                                     \n \\ PF /                     +-------+               +---------+                      \n  \\__/  ----+               +--------+  +---blog---&gt;| Varnish |--+   +---------------+\n            |               | relayd |--+           |         |  +--&gt;| WG -&gt; bsdcube |\n            +-----HTTPS----&gt;|        |--+           +---------+  +--&gt;|               |\n                            +--------+  |                        |   +---------------+\n                                        +-------matrix-----------+                   \n</code></pre>\n<p>It all starts with PF, the OpenBSD Packet Filter.\nIn addition to rejecting invalid / unwanted traffic,\nit's responsible for routing the legit packets to the right service.</p>\n<h2><a href=\"#serving-https-with-httpd8-and-relayd8\" aria-hidden=\"true\" class=\"anchor\" id=\"serving-https-with-httpd8-and-relayd8\"></a>Serving HTTP(S) with <code>httpd(8)</code> and <code>relayd(8)</code></h2>\n<p>An HTTP server sits on the box for handling the ACME challenges\n(e.g. from <a href=\"https://letsencrypt.org/\">Let's Encrypt</a>) to get a TLS cert.\nOpenBSD has <a href=\"https://man.openbsd.org/httpd.8\"><code>httpd(8)</code></a>,\nnot to be confused with the Apache <code>httpd</code>.\nIt can serve static files and FastCGI, but it's not a reverse proxy.</p>\n<p>Normally I'd reach for something like nginx, HAProxy, or Caddy,\nbut OpenBSD also has an excellent (load balancing) reverse proxy in the base system already:\n<a href=\"https://man.openbsd.org/relayd.8\"><code>relayd(8)</code></a>.</p>\n<p><code>relayd</code> in my setup is responsible for 2 things:\nrouting to the correct backend (e.g. based on SNI),\nand TLS termination.\nI'll write a longer post about this soon,\nincluding how to configure <a href=\"https://man.openbsd.org/acme-client.1\"><code>acme-client(1)</code></a>to get certs automatically,\nand configuring <code>relayd</code> to terminate TLS for multiple domains.</p>\n<h2><a href=\"#varnish-vinyl-cache\" aria-hidden=\"true\" class=\"anchor\" id=\"varnish-vinyl-cache\"></a>Varnish (Vinyl) cache</h2>\n<p>Since my blog is just a static site,\nit would be quite reasonable to host it with <code>httpd</code> on <code>proxbox</code>.\nThis <em>would</em> result in faster response times,\nbut I decided to host it on <code>bsdcube</code> to make the setup simpler.\nThe clear separation of concerns, with <code>bsdcube</code> being the final source of truth\nfor all data and services makes it easier for me to maintain.\nI'll never have to touch <code>proxbox</code> outside of routine system maintenance or adding a new service port.</p>\n<p>That said, I'm not going to just blindly forward <em>every single request</em> over to my home network.\nI've used <a href=\"https://vinyl-cache.org/\">Varnish Cache</a> (now rebranded as Vinyl)\nfor over a decade in various professional contexts.\nIt does an excellent job at absorbing traffic spikes,\nand smoothing over issues when the backend goes flaky.\nSince I'm tunneling everything over the internet,\nhalfway around the world,\nthis seems like table stakes.</p>\n<p>(Fun fact: the <a href=\"https://phk.freebsd.dk/\">author</a> of Varnish is <em>also</em> the author of FreeBSD's jails;\nhe's a legend in the FreeBSD community, and should be more widely known.)</p>\n<h1><a href=\"#the-middle-wireguard\" aria-hidden=\"true\" class=\"anchor\" id=\"the-middle-wireguard\"></a>The Middle: WireGuard</h1>\n<p>Whatever route a legitimate packet takes,\nif it needs to talk to a backend service,\nthis happens over a WireGuard tunnel.</p>\n<p>I could have done this with something like Tailscale or Cloudflare Warp,\nbut I wanted something that wasn't reliant on a &quot;free&quot; external service.\nSuch services are rarely free forever.\nRolling your own tunnel with a proven technology is <a href=\"setting-up-a-wireguard-tunnel-on-freebsd-15.html\">surprisingly easy</a>,\nand my intentional choice of a somewhat offbeat rest of the stack made the decision a no-brainer.\nBoth FreeBSD and OpenBSD have WireGuard support built-in at the kernel level already.</p>\n<p>PF rules on <em>both</em> sides of the tunnel ensure that only certain traffic passes through.\nI will probably write a post on this later as well as there's quite a lot here.</p>\n<h1><a href=\"#bsdcube\" aria-hidden=\"true\" class=\"anchor\" id=\"bsdcube\"></a><code>bsdcube</code></h1>\n<p>Let's look at the other side of the tunnel now.\n<code>bsdcube</code> sits on my home network behind a NAT firewall.\nIt only accepts traffic over the tunnel for specific service ports.\nAnd those ports are mapped to jailed services with a limited view of the system.\nIn case you're not familiar with FreeBSD, jails are a FreeBSD-native containerization technology\nwhich has been around for over 25 years (that's a long time before Docker popularized the term).\nWhile they have many differences with Linux containers,\nthat's a reasonable mental model for understanding the rest of this post.</p>\n<h2><a href=\"#appjail\" aria-hidden=\"true\" class=\"anchor\" id=\"appjail\"></a>AppJail</h2>\n<p>The base system has <code>jail(8)</code> as a basic management facility.\nThis does technically have everything you need,\nbut it requires enough steps that,\njust as in the Linux container world,\nmost people rely on higher level tools for management and orchestration.</p>\n<p>There are over a dozen such tools for FreeBSD,\neach with a different take on things.\nThat's both cool (healthy ecosystem!) and bewildering for newcomers to the space ;)</p>\n<p>I ended up settling on <a href=\"https://appjail.readthedocs.io/en/latest/\">AppJail</a> for <code>bsdcube</code>,\nat least for now,\nsince it has extensive documentation,\nsupport for features I'm curious about later (like Linux jails),\nand a declarative, composable configuration format.</p>\n<h3><a href=\"#creating-jails-with-makejail-files\" aria-hidden=\"true\" class=\"anchor\" id=\"creating-jails-with-makejail-files\"></a>Creating jails with <code>Makejail</code> files</h3>\n<p>Here's a sample of a <code>Makejail</code> file.\nAs you might guess, it's a set of instructions for building a jail.\nIt looks a bit like a Dockerfile.</p>\n<pre><code># blog/Makejail\n\n# Include directives let you separate out sections of your config across files\n# and reuse common sections.\nINCLUDE options/options.makejail\nINCLUDE options/network.makejail\n\n# Install nginx from the FreeBSD package repository\nPKG nginx\n\n# A sysrc directive.\n# This is the equivalent of enabling a service on a Linux box with systemd\nSYSRC nginx_enable=YES\n\n# Copies nginx.conf from the current directory into the specified path in the jail.\nCOPY nginx.conf /usr/local/etc/nginx/nginx.conf\n\n# Install Marmite, the static site generator I use for my blog.\n# Despite being relatively obscure, there's also an up-to-date package for this!\nPKG marmite\n\nINCLUDE buildsite.makejail\n\nSERVICE nginx start\n</code></pre>\n<h2><a href=\"#jail-lifecycle-management\" aria-hidden=\"true\" class=\"anchor\" id=\"jail-lifecycle-management\"></a>Jail lifecycle management</h2>\n<p>&quot;Scripted&quot; setups like this can get pretty unwieldy if you're not careful.\nAnd for me, one of the worst parts of CLI tools is remembering the half dozen switches\nto get a particular task done.</p>\n<p>To solve this, I structured my jail deployments as a git repository\nwhere each subdirectory is a self-contained service.\nIn my setup, there are <strong>no dependencies between services</strong>.\nI'm deploying full applications/services in isolated jails,\neven when I could theoretically have shared services (e.g., a Postgres database).\nAs such, it's easy to add a <a href=\"https://just.systems/man/en/introduction.html\"><code>justfile</code></a> to each directory\nwith deployment recipes so I don't have to remember them.</p>\n<p>The usual invocation is something like <code>just freshjail</code> to (re)create a jail.\nAnd for jails like my blog, where the <em>application</em> has no need to restart\nto get an update, I have recipe(s) that perform the necessary updates against the running jail.</p>\n<h1><a href=\"#conclusion-or-whats-next\" aria-hidden=\"true\" class=\"anchor\" id=\"conclusion-or-whats-next\"></a>Conclusion (or, what's next)</h1>\n<p>That was a lot... and there's still a lot I didn't cover!\nLike tips on the AppJail host setup, PF, handling multiple hosts with SNI via <code>relayd</code>, and a lot more.\nSo stay tuned for the next post,\nand give a shout on Mastodon if you have any feedback.</p>\n<p>In the meantime, I've uploaded the AppJail part of my setup\nto a <a href=\"https://codeberg.org/ianthetechie/appjails/\">public repo on Codeberg</a>.\nHopefully they are useful for others!</p>\n",
      "summary": "",
      "date_published": "2026-04-11T00:00:00-00:00",
      "image": "",
      "authors": [
        {
          "name": "Ian Wagner",
          "url": "https://fosstodon.org/@ianthetechie",
          "avatar": "media/avi.jpeg"
        }
      ],
      "tags": [
        "homelab",
        "FreeBSD",
        "OpenBSD",
        "networking",
        "jails",
        "containerization"
      ],
      "language": "en"
    }
  ]
}