<?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>Pocketid on Haseeb Majid</title>
    <link>https://haseebmajid.dev/tags/pocketid/</link>
    <description>Recent content in Pocketid on Haseeb Majid</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <lastBuildDate>Tue, 25 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://haseebmajid.dev/tags/pocketid/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Why I Migrated From Authentik to PocketID</title>
      <link>https://haseebmajid.dev/posts/2026-08-25-why-i-migrated-from-authentik-to-pocketid/</link>
      <pubDate>Tue, 25 Aug 2026 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2026-08-25-why-i-migrated-from-authentik-to-pocketid/</guid>
      <description>&lt;p&gt;A while back I wrote about &lt;a href=&#34;https://haseebmajid.dev/posts/2024-09-06-how-to-setup-authentik-forward-auth-with-traefik-on-nixos&#34;&gt;how I set up Authentik forward auth with Traefik on
NixOS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It worked fine but recently I decided to rebuild my homelab/vps from scratch and decided to try moving to Pocket ID
(and TinyAuth). We will also cover how I migrated over, though this was very basic I didn&amp;rsquo;t actually move any data over.&lt;/p&gt;
&lt;p&gt;This is all installed via NixOS, so I want things to be as declarative as possible, i.e. fewest manual steps I need
to do.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>A while back I wrote about <a href="/posts/2024-09-06-how-to-setup-authentik-forward-auth-with-traefik-on-nixos">how I set up Authentik forward auth with Traefik on
NixOS</a>.</p>
<p>It worked fine but recently I decided to rebuild my homelab/vps from scratch and decided to try moving to Pocket ID
(and TinyAuth). We will also cover how I migrated over, though this was very basic I didn&rsquo;t actually move any data over.</p>
<p>This is all installed via NixOS, so I want things to be as declarative as possible, i.e. fewest manual steps I need
to do.</p>
<h2 id="why-migrate">Why migrate?</h2>
<p>Authentik does a lot. Outposts, flows, property mappings, RBAC, the works. I was only using a sliver of it, log in
once and forward auth in front of the arr stack and a few apps without their own auth. The embedded outpost setup from
the last post worked but was always a bit fiddly, and Authentik was eating a fair chunk of RAM on my VPS for something
as simple as SSO/auth.</p>
<p>Pocket ID is just OIDC, nothing else. TinyAuth is just forward auth. Two small binaries instead of one big app, and a
much simpler tool. I had read online on say the selfhosted subreddit how simple Pocket ID was and a lot of people
liked it or had migrated. So after needing to rebuild my VPS, I thought it was a perfect chance to move to something simpler.
Only my family were using the homelab and that barely.</p>
<h2 id="pocket-id">Pocket ID</h2>
<p>Pocket ID is the OIDC provider, the thing my apps and TinyAuth log in against.
The config I have set up looks something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">sops</span><span class="o">.</span><span class="n">secrets</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">pocketid_encryption_key</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">owner</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">pocket-id</span><span class="o">.</span><span class="n">user</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="k">inherit</span> <span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">pocket-id</span><span class="p">)</span> <span class="n">group</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">mode</span> <span class="o">=</span> <span class="s2">&#34;0400&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">pocketid_static_api_key</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">owner</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">pocket-id</span><span class="o">.</span><span class="n">user</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="k">inherit</span> <span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">pocket-id</span><span class="p">)</span> <span class="n">group</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">mode</span> <span class="o">=</span> <span class="s2">&#34;0400&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">postgresql</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">ensureDatabases</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;pocketid&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">ensureUsers</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;pocketid&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">ensureDBOwnership</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">pocket-id</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">user</span> <span class="o">=</span> <span class="s2">&#34;pocketid&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">group</span> <span class="o">=</span> <span class="s2">&#34;pocketid&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">APP_URL</span> <span class="o">=</span> <span class="s2">&#34;https://id.haseebmajid.dev&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">TRUST_PROXY</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">HOST</span> <span class="o">=</span> <span class="s2">&#34;127.0.0.1&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">PORT</span> <span class="o">=</span> <span class="mi">1411</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">DB_CONNECTION_STRING</span> <span class="o">=</span> <span class="s2">&#34;postgres://pocketid@/pocketid?host=/run/postgresql&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">UI_CONFIG_DISABLED</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">ALLOW_USER_SIGNUPS</span> <span class="o">=</span> <span class="s2">&#34;withToken&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">ANALYTICS_DISABLED</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">VERSION_CHECK_DISABLED</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">credentials</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">ENCRYPTION_KEY</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">pocketid_encryption_key</span><span class="o">.</span><span class="n">path</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">STATIC_API_KEY</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">pocketid_static_api_key</span><span class="o">.</span><span class="n">path</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">traefik</span><span class="o">.</span><span class="n">dynamicConfigOptions</span><span class="o">.</span><span class="n">http</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">nixicle</span><span class="o">.</span><span class="n">mkTraefikService</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;pocket-id&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">port</span> <span class="o">=</span> <span class="mi">1411</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">subdomain</span> <span class="o">=</span> <span class="s2">&#34;id&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">domain</span> <span class="o">=</span> <span class="s2">&#34;haseebmajid.dev&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="sops">Sops</h3>
<p>To manage secrets I use SOPs, the secrets are encrypted and kept in git. It&rsquo;s just simpler in this case and everything
can be kept in a single repo. Of course in my case it&rsquo;s public, so if any of my age private keys get leaked anyone
could decrypt the files and view them. But again like most things it&rsquo;s about trade-offs.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">  <span class="n">sops</span><span class="o">.</span><span class="n">secrets</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">pocketid_encryption_key</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">owner</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">pocket-id</span><span class="o">.</span><span class="n">user</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="k">inherit</span> <span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">pocket-id</span><span class="p">)</span> <span class="n">group</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">mode</span> <span class="o">=</span> <span class="s2">&#34;0400&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">pocketid_static_api_key</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">owner</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">pocket-id</span><span class="o">.</span><span class="n">user</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="k">inherit</span> <span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">pocket-id</span><span class="p">)</span> <span class="n">group</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">mode</span> <span class="o">=</span> <span class="s2">&#34;0400&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span></code></pre></div><p>These secrets are then passed to the module like so, where with sops we pass in the path to the secrets file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">credentials</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">ENCRYPTION_KEY</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">pocketid_encryption_key</span><span class="o">.</span><span class="n">path</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">STATIC_API_KEY</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">pocketid_static_api_key</span><span class="o">.</span><span class="n">path</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><h3 id="nix">Nix</h3>
<p>The rest of the module should be fairly straightforward just configuring what ports to run on, various env vars.
The <code>ALLOW_USER_SIGNUPS = &quot;withToken&quot;</code> line means only new users can sign up when I (as admin) share a specific
code with them.</p>
<p><code>mkTraefikService</code> is a little helper I wrote. It spits out the router, service and tls block for a given subdomain,
so I don&rsquo;t repeat that boilerplate per app.</p>
<h3 id="manual-steps">Manual Steps</h3>
<p>You still need to manually create the first admin account, which is fine I think. Maybe in future there will be a way
to pre-create an account for temporary setup. But for now it&rsquo;s a minor niggle.</p>
<h2 id="tinyauth">TinyAuth</h2>
<p>TinyAuth is the forward auth, same job the Authentik embedded outpost did before. It sits in front of Traefik,
redirects you to Pocket ID to log in, then checks each request after that.</p>
<p>TinyAuth is for apps that don&rsquo;t have their own login page, or that can&rsquo;t talk to an OIDC provider themselves.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">authPort</span> <span class="o">=</span> <span class="mi">3000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">domain</span> <span class="o">=</span> <span class="s2">&#34;haseebmajid.dev&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">tinyauth</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">environmentFile</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">tinyauth_env</span><span class="o">.</span><span class="n">path</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">SERVER_ADDRESS</span> <span class="o">=</span> <span class="s2">&#34;127.0.0.1&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">SERVER_PORT</span> <span class="o">=</span> <span class="n">authPort</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">APPURL</span> <span class="o">=</span> <span class="s2">&#34;https://auth.</span><span class="si">${</span><span class="n">domain</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">OAUTH_PROVIDERS_pocketid_AUTHURL</span> <span class="o">=</span> <span class="s2">&#34;https://id.</span><span class="si">${</span><span class="n">domain</span><span class="si">}</span><span class="s2">/authorize&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">OAUTH_PROVIDERS_pocketid_TOKENURL</span> <span class="o">=</span> <span class="s2">&#34;https://id.</span><span class="si">${</span><span class="n">domain</span><span class="si">}</span><span class="s2">/api/oidc/token&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">OAUTH_PROVIDERS_pocketid_USERINFOURL</span> <span class="o">=</span> <span class="s2">&#34;https://id.</span><span class="si">${</span><span class="n">domain</span><span class="si">}</span><span class="s2">/api/oidc/userinfo&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">OAUTH_PROVIDERS_pocketid_REDIRECTURL</span> <span class="o">=</span> <span class="s2">&#34;https://auth.</span><span class="si">${</span><span class="n">domain</span><span class="si">}</span><span class="s2">/api/oauth/callback/pocketid&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">OAUTH_PROVIDERS_pocketid_SCOPES</span> <span class="o">=</span> <span class="s2">&#34;openid profile email groups&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">OAUTH_PROVIDERS_pocketid_NAME</span> <span class="o">=</span> <span class="s2">&#34;Pocket ID&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">OAUTH_AUTOREDIRECT</span> <span class="o">=</span> <span class="s2">&#34;pocketid&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The <code>tinyauth_env</code> sops secret holds the OAuth client id and secret for the TinyAuth client I made in <strong>Pocket ID</strong>.
The module loads it via <code>environmentFile</code>, and everything else (the OAuth URLs, scopes, etc.) goes in <code>settings</code>.
More on how the secret gets there in a bit.</p>
<h3 id="traefik-forwardauth">Traefik ForwardAuth</h3>
<p>Using the same <code>let</code> (<code>authPort</code>, <code>domain</code>) from above, the Traefik side is just a forwardAuth middleware plus
a router so the callback URL is reachable:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">traefik</span><span class="o">.</span><span class="n">dynamicConfigOptions</span><span class="o">.</span><span class="n">http</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">middlewares</span><span class="o">.</span><span class="n">tinyauth</span><span class="o">.</span><span class="n">forwardAuth</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">address</span> <span class="o">=</span> <span class="s2">&#34;http://127.0.0.1:</span><span class="si">${</span><span class="nb">toString</span> <span class="n">authPort</span><span class="si">}</span><span class="s2">/api/auth/traefik&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">authResponseHeaders</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Remote-User&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Remote-Name&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Remote-Email&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Remote-Groups&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">routers</span><span class="o">.</span><span class="n">tinyauth</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">entryPoints</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;websecure&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">      <span class="n">rule</span> <span class="o">=</span> <span class="s2">&#34;Host(`auth.</span><span class="si">${</span><span class="n">domain</span><span class="si">}</span><span class="s2">`)&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">service</span> <span class="o">=</span> <span class="s2">&#34;tinyauth&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">tls</span><span class="o">.</span><span class="n">certResolver</span> <span class="o">=</span> <span class="s2">&#34;letsencrypt&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">services</span><span class="o">.</span><span class="n">tinyauth</span><span class="o">.</span><span class="n">loadBalancer</span><span class="o">.</span><span class="n">servers</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span> <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;http://127.0.0.1:</span><span class="si">${</span><span class="nb">toString</span> <span class="n">authPort</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This is the bit that replaces the <code>authentik</code> middleware from the old post. The address is TinyAuth&rsquo;s
<code>/api/auth/traefik</code> endpoint instead of the Authentik outpost on <code>9443</code>, and the headers it sends back are
<code>Remote-User</code> and friends instead of the <code>X-authentik-*</code> ones. Same idea, less moving parts.</p>
<p>To protect an app I just add the <code>tinyauth</code> middleware to that app&rsquo;s Traefik router, same as I used to add <code>authentik</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">traefik</span><span class="o">.</span><span class="n">dynamicConfigOptions</span><span class="o">.</span><span class="n">http</span><span class="o">.</span><span class="n">routers</span><span class="o">.</span><span class="n">sonarr</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">entryPoints</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;websecure&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">rule</span> <span class="o">=</span> <span class="s2">&#34;Host(`sonarr.homelab.haseebmajid.dev`)&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">service</span> <span class="o">=</span> <span class="s2">&#34;sonarr&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">tls</span><span class="o">.</span><span class="n">certResolver</span> <span class="o">=</span> <span class="s2">&#34;letsencrypt&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">middlewares</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;tinyauth&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Hit the app unauthenticated and TinyAuth bounces you to Pocket ID to log in, then back. That&rsquo;s the whole flow.</p>
<h2 id="terranix">Terranix</h2>
<p>With Authentik I used to click around the admin UI to make a new client for each app. With Pocket ID I declare the
clients in nix and apply them with OpenTofu via Terranix (terraform defined in nix code), so they&rsquo;re version
controlled and reproducible.</p>
<p>Though you could easily just use Terraform/OpenTofu directly, I am just trying out terranix to see how well it fits
into my flow.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Authentik and TF</summary>
  
  There is also no reason you couldn&rsquo;t use TF (terraform/opentofu) to configure authentik declaratively as well.
It&rsquo;s just I didn&rsquo;t bother initially then got lazy. But when rebuilding my VPS, I decided to do it properly. As I had
the motivation at the time.
</details>

<p>Here&rsquo;s some example code:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">apps</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">tinyauth</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;TinyAuth&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">client_id</span> <span class="o">=</span> <span class="s2">&#34;tinyauth&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">launch_url</span> <span class="o">=</span> <span class="s2">&#34;https://auth.haseebmajid.dev&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">callback_urls</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;https://auth.haseebmajid.dev/api/oauth/callback/pocketid&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">];</span>
</span></span><span class="line"><span class="cl">      <span class="n">is_public</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">pkce_enabled</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">lettucego</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;LettuceGo&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">client_id</span> <span class="o">=</span> <span class="s2">&#34;lettucego&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">launch_url</span> <span class="o">=</span> <span class="s2">&#34;https://lettucego.haseebmajid.dev&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">callback_urls</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;https://lettucego.haseebmajid.dev/callback&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">];</span>
</span></span><span class="line"><span class="cl">      <span class="n">is_public</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">pkce_enabled</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Other Apps ...</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">mkClient</span> <span class="o">=</span> <span class="n">key</span><span class="p">:</span> <span class="n">cfg</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">resource</span><span class="o">.</span><span class="n">pocketid_client</span><span class="o">.</span><span class="si">${</span><span class="n">key</span><span class="si">}</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">inherit</span> <span class="p">(</span><span class="n">cfg</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">name</span>
</span></span><span class="line"><span class="cl">        <span class="n">client_id</span>
</span></span><span class="line"><span class="cl">        <span class="n">callback_urls</span>
</span></span><span class="line"><span class="cl">        <span class="n">is_public</span>
</span></span><span class="line"><span class="cl">        <span class="n">pkce_enabled</span>
</span></span><span class="line"><span class="cl">        <span class="n">launch_url</span>
</span></span><span class="line"><span class="cl">        <span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">output</span><span class="o">.</span><span class="s2">&#34;</span><span class="si">${</span><span class="n">key</span><span class="si">}</span><span class="s2">_client_id&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">value</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\$</span><span class="s2">{pocketid_client.</span><span class="si">${</span><span class="n">key</span><span class="si">}</span><span class="s2">.id}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">output</span><span class="o">.</span><span class="s2">&#34;</span><span class="si">${</span><span class="n">key</span><span class="si">}</span><span class="s2">_client_secret&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">value</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\$</span><span class="s2">{pocketid_client.</span><span class="si">${</span><span class="n">key</span><span class="si">}</span><span class="s2">.client_secret}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">sensitive</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span> <span class="nb">builtins</span><span class="o">.</span><span class="n">attrValues</span> <span class="p">(</span><span class="nb">builtins</span><span class="o">.</span><span class="n">mapAttrs</span> <span class="n">mkClient</span> <span class="n">apps</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Each entry becomes a <code>pocketid_client</code> Terraform resource, so <code>tofu apply</code> creates the client in Pocket ID and prints
the id and secret as outputs.</p>
<h3 id="openbao">OpenBao</h3>
<p>Not strictly related, but we also push the data (client id, client secret) into OpenBao (Vault alternative).
The values returned from our TF apply.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">apps</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;goroutinely&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;gothreads&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;lettucego&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;sure&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;tinyauth&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;grafana&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">mkSecret</span> <span class="o">=</span> <span class="n">app</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">resource</span><span class="o">.</span><span class="n">vault_kv_secret_v2</span><span class="o">.</span><span class="s2">&#34;</span><span class="si">${</span><span class="n">app</span><span class="si">}</span><span class="s2">_oidc&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">mount</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\$</span><span class="s2">{vault_mount.kv.path}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;apps/</span><span class="si">${</span><span class="n">app</span><span class="si">}</span><span class="s2">/oidc&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">data_json</span> <span class="o">=</span> <span class="nb">builtins</span><span class="o">.</span><span class="n">toJSON</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">client_id</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\$</span><span class="s2">{pocketid_client.</span><span class="si">${</span><span class="n">app</span><span class="si">}</span><span class="s2">.id}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">client_secret</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\$</span><span class="s2">{pocketid_client.</span><span class="si">${</span><span class="n">app</span><span class="si">}</span><span class="s2">.client_secret}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">resource</span><span class="o">.</span><span class="n">vault_mount</span><span class="o">.</span><span class="n">kv</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">path</span> <span class="o">=</span> <span class="s2">&#34;kv&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">type</span> <span class="o">=</span> <span class="s2">&#34;kv&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">options</span><span class="o">.</span><span class="n">version</span> <span class="o">=</span> <span class="s2">&#34;2&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span> <span class="nb">map</span> <span class="n">mkSecret</span> <span class="n">apps</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="nix-vs-opentofu">Nix vs OpenTofu</h2>
<p>So there are two declarative layers doing different jobs. Pocket ID and TinyAuth are NixOS services, declared in nix
and applied with <code>nixos-rebuild</code>, same as the rest of the box. The OIDC clients and secrets can&rsquo;t really be a NixOS
module, they live behind Pocket ID&rsquo;s and OpenBao&rsquo;s APIs. So I declare those in nix too but as Terranix, which compiles
to terraform JSON, and OpenTofu applies them.</p>
<p>The whole Terranix config is just one file that imports three modules:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="c1"># infra/terranix/prod.nix</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="sr">./modules/terraform.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">./modules/pocketid-apps.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">./modules/openbao-secrets.nix</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><code>pocketid-apps.nix</code> and <code>openbao-secrets.nix</code> are the ones from the last section. The third, <code>terraform.nix</code>, is the
boring bit that sets up the providers and variables:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">terraform</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">required_version</span> <span class="o">=</span> <span class="s2">&#34;&gt;= 1.0&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">backend</span><span class="o">.</span><span class="n">local</span> <span class="o">=</span> <span class="p">{</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">required_providers</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">pocketid</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">source</span> <span class="o">=</span> <span class="s2">&#34;trozz/pocketid&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">version</span> <span class="o">=</span> <span class="s2">&#34;~&gt; 0.1&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="n">vault</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">source</span> <span class="o">=</span> <span class="s2">&#34;hashicorp/vault&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">version</span> <span class="o">=</span> <span class="s2">&#34;~&gt; 4.0&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">provider</span><span class="o">.</span><span class="n">pocketid</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">base_url</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\$</span><span class="s2">{var.pocketid_base_url}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">api_token</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\$</span><span class="s2">{var.pocketid_api_token}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">provider</span><span class="o">.</span><span class="n">vault</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">address</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\$</span><span class="s2">{var.openbao_address}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">token</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\$</span><span class="s2">{var.openbao_token}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">skip_child_token</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">variable</span><span class="o">.</span><span class="n">pocketid_base_url</span><span class="o">.</span><span class="n">type</span> <span class="o">=</span> <span class="s2">&#34;string&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">variable</span><span class="o">.</span><span class="n">pocketid_api_token</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">type</span> <span class="o">=</span> <span class="s2">&#34;string&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">sensitive</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="n">variable</span><span class="o">.</span><span class="n">openbao_address</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">type</span> <span class="o">=</span> <span class="s2">&#34;string&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">default</span> <span class="o">=</span> <span class="s2">&#34;https://openbao.homelab.haseebmajid.dev&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="n">variable</span><span class="o">.</span><span class="n">openbao_token</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">type</span> <span class="o">=</span> <span class="s2">&#34;string&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">sensitive</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The <code>pocketid</code> provider (<code>trozz/pocketid</code>) talks to Pocket ID&rsquo;s admin API, the <code>vault</code> provider talks to OpenBao.
Both tokens come in as terraform variables, i.e. env vars at apply time, so they&rsquo;re not in the nix config.</p>
<p>To run it I have a <code>go-task</code> task that turns the nix into terraform JSON and hands it to OpenTofu:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># tasks/tofu.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">tasks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">generate</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cmds</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">mkdir -p infra/terranix/generated/prod</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">terranix infra/terranix/prod.nix &gt; infra/terranix/generated/prod/config.tf.json</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">apply</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">deps</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">generate]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">dir</span><span class="p">:</span><span class="w"> </span><span class="l">infra/terranix/generated/prod</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cmds</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">tofu init</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">tofu apply</span><span class="w">
</span></span></span></code></pre></div><p>So <code>task tofu:apply</code> runs <code>terranix prod.nix</code> to spit out a <code>config.tf.json</code>, then <code>tofu init &amp;&amp; tofu apply</code> creates
the clients in Pocket ID and pushes the secrets into OpenBao. No hand-written HCL, it&rsquo;s generated from nix, so the
clients and the NixOS services stay in sync in the same repo.</p>
<p>Both <code>opentofu</code> and <code>terranix</code> are just in the flake&rsquo;s devShell, so anyone cloning the repo gets them for free, no
separate terraform install to manage:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="c1"># flake devShell</span>
</span></span><span class="line"><span class="cl"><span class="n">nativeBuildInputs</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># ...sops, age, git, etc.</span>
</span></span><span class="line"><span class="cl">  <span class="n">opentofu</span>
</span></span><span class="line"><span class="cl">  <span class="n">terranix</span>
</span></span><span class="line"><span class="cl">  <span class="n">go-task</span>
</span></span><span class="line"><span class="cl"><span class="p">];</span>
</span></span></code></pre></div><h2 id="data-migration">Data Migration</h2>
<p>Usually migrating the data over is actually the hard part of any app migration. In my case, I was happy to throw
away the data from Authentik and start again. As it was just family with accounts, easy enough to convince them to take
2 mins to set up on Pocket ID. Then set up only the apps I used/deployed on the new VPS vs lots of old out of date
data on Authentik. Apps I no longer use or have deployed but never deleted the app on Authentik.</p>
<p>Of course in your case, this might be a lot harder to migrate over depending on how much data you already have in
Authentik and how important it is for your current users. But for a simple homelab with very few users, it was
just easier to start from scratch. A bit like putting on clean bed sheets hahaha.</p>
<h2 id="tldr">tldr</h2>
<p>Authentik was really heavy for my own personal use case, I swapped to pocketid (and tinyauth) as they are simpler and
more lightweight (take fewer resources).</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li>My home lab Nix config
<ul>
<li><a href="https://gitlab.com/hmajid2301/nixicle/-/blob/f1378817/modules/aspects/services/pocket-id.nix">Pocket ID config</a></li>
<li><a href="https://gitlab.com/hmajid2301/nixicle/-/blob/f1378817/modules/aspects/services/tinyauth.nix">TinyAuth config</a></li>
<li><a href="https://gitlab.com/hmajid2301/nixicle/-/blob/f1378817/infra/terranix/modules/pocketid-apps.nix">Pocket ID clients (Terranix)</a></li>
<li><a href="https://gitlab.com/hmajid2301/nixicle/-/blob/f1378817/infra/terranix/modules/openbao-secrets.nix">OpenBao OIDC secrets (Terranix)</a></li>
<li><a href="https://gitlab.com/hmajid2301/nixicle/-/blob/f1378817/infra/terranix/modules/terraform.nix">Terranix providers + vars</a></li>
<li><a href="https://gitlab.com/hmajid2301/nixicle/-/blob/f1378817/infra/terranix/prod.nix">Terranix entry point</a></li>
<li><a href="https://gitlab.com/hmajid2301/nixicle/-/blob/f1378817/tasks/tofu.yml">OpenTofu go-task tasks</a></li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
