<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>云雀通 Blog</title>
        <link>https://docs.larktun.com/en/blog</link>
        <description>云雀通 Blog</description>
        <lastBuildDate>Wed, 22 Apr 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[Headscale Multi-Tenant Transformation: Full Isolation for ACL, Routes, DNS, and More]]></title>
            <link>https://docs.larktun.com/en/blog/headscale-multi-tenant-isolation</link>
            <guid>https://docs.larktun.com/en/blog/headscale-multi-tenant-isolation</guid>
            <pubDate>Wed, 22 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[This post shows practical outcomes of transforming single-instance headscale into a multi-tenant model with isolation across tailnet, ACL, routes, DNS, CLI operations, and relay strategy.]]></description>
            <content:encoded><![CDATA[<p>This post shows practical outcomes of transforming single-instance <code>headscale</code> into a multi-tenant model with isolation across tailnet, ACL, routes, DNS, CLI operations, and relay strategy.</p>
<p>Original post:
<a href="https://ownding.com/2026/04/22/headscale%E5%A4%9A%E7%A7%9F%E6%88%B7%E6%94%B9%E9%80%A0%EF%BC%8CACL%E3%80%81%E8%B7%AF%E7%94%B1%E3%80%81DNS%E7%AD%89%E5%85%A8%E9%9A%94%E7%A6%BB/" target="_blank" rel="noopener noreferrer" class="">headscale多租户改造，ACL、路由、DNS等全隔离</a></p>
<!-- -->
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="transformation-goals">Transformation Goals<a href="https://docs.larktun.com/en/blog/headscale-multi-tenant-isolation#transformation-goals" class="hash-link" aria-label="Direct link to Transformation Goals" title="Direct link to Transformation Goals" translate="no">​</a></h2>
<p>Headscale is originally single-instance and effectively single-tailnet by default. Multi-tenant support requires strict per-tenant isolation for:</p>
<ul>
<li class="">tailnet address spaces</li>
<li class="">ACL policies</li>
<li class="">route definitions</li>
<li class="">MagicDNS namespaces</li>
<li class="">CLI management flow</li>
<li class="">relay server strategy</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="key-points">Key Points<a href="https://docs.larktun.com/en/blog/headscale-multi-tenant-isolation#key-points" class="hash-link" aria-label="Direct link to Key Points" title="Direct link to Key Points" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="tailnet">Tailnet<a href="https://docs.larktun.com/en/blog/headscale-multi-tenant-isolation#tailnet" class="hash-link" aria-label="Direct link to Tailnet" title="Direct link to Tailnet" translate="no">​</a></h3>
<ul>
<li class="">Each tenant has an independent tailnet.</li>
<li class="">Default pool <code>10.64.0.0/10</code> can remain, or be customized (for example <code>192.168.6.0/24</code>).</li>
<li class="">A built-in <code>default</code> tenant keeps backward-compatible behavior.</li>
</ul>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="acl-and-routes">ACL and Routes<a href="https://docs.larktun.com/en/blog/headscale-multi-tenant-isolation#acl-and-routes" class="hash-link" aria-label="Direct link to ACL and Routes" title="Direct link to ACL and Routes" translate="no">​</a></h3>
<ul>
<li class="">ACL policies are fully tenant-scoped.</li>
<li class="">Route configs are tenant-scoped as well.</li>
</ul>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="magicdns">MagicDNS<a href="https://docs.larktun.com/en/blog/headscale-multi-tenant-isolation#magicdns" class="hash-link" aria-label="Direct link to MagicDNS" title="Direct link to MagicDNS" translate="no">​</a></h3>
<ul>
<li class="">FQDN pattern: <code>hostname.&lt;tenant_key&gt;.&lt;dns.base_domain&gt;</code>.</li>
<li class="">The <code>default</code> tenant uses <code>default</code> as subdomain.</li>
</ul>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="cli">CLI<a href="https://docs.larktun.com/en/blog/headscale-multi-tenant-isolation#cli" class="hash-link" aria-label="Direct link to CLI" title="Direct link to CLI" translate="no">​</a></h3>
<ul>
<li class="">CLI commands add tenant targeting (for example <code>-t</code>).</li>
</ul>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="relay-servers">Relay Servers<a href="https://docs.larktun.com/en/blog/headscale-multi-tenant-isolation#relay-servers" class="hash-link" aria-label="Direct link to Relay Servers" title="Direct link to Relay Servers" translate="no">​</a></h3>
<ul>
<li class="">Tenants can share relay infra.</li>
<li class="">Or each tenant can use dedicated relay nodes for stronger isolation.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="example-screenshots">Example Screenshots<a href="https://docs.larktun.com/en/blog/headscale-multi-tenant-isolation#example-screenshots" class="hash-link" aria-label="Direct link to Example Screenshots" title="Direct link to Example Screenshots" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" alt="users with tenant" src="https://docs.larktun.com/en/assets/images/user-t-ba83054720a967a68423cd6545f041a4.webp" width="815" height="301" class="img_ev3q">
<img decoding="async" loading="lazy" alt="nodes overview" src="https://docs.larktun.com/en/assets/images/node-c8cd4b51a500de75df667181d8b7aae0.webp" width="1752" height="367" class="img_ev3q">
<img decoding="async" loading="lazy" alt="nodes with tenant" src="https://docs.larktun.com/en/assets/images/node-t-04b69f31b904472c20b63553c268d661.webp" width="1741" height="153" class="img_ev3q"></p>
<hr>
<p>This article is mirrored on the Larktun blog. For source updates and original context, refer to:
<a href="https://ownding.com/2026/04/22/headscale%E5%A4%9A%E7%A7%9F%E6%88%B7%E6%94%B9%E9%80%A0%EF%BC%8CACL%E3%80%81%E8%B7%AF%E7%94%B1%E3%80%81DNS%E7%AD%89%E5%85%A8%E9%9A%94%E7%A6%BB/" target="_blank" rel="noopener noreferrer" class="">headscale多租户改造，ACL、路由、DNS等全隔离</a></p>]]></content:encoded>
            <category>headscale</category>
            <category>multi-tenant</category>
            <category>ACL</category>
            <category>SaaS</category>
        </item>
        <item>
            <title><![CDATA[Headscale Series: Seamless User Switching in a Headscale SaaS Environment]]></title>
            <link>https://docs.larktun.com/en/blog/headscale-saas-seamless-user-switch</link>
            <guid>https://docs.larktun.com/en/blog/headscale-saas-seamless-user-switch</guid>
            <pubDate>Sat, 25 Oct 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[When scaling or rebalancing a Headscale SaaS deployment, the key challenge is how to migrate users with minimal disruption. This post presents a practical pattern: dynamic routing + system-level targeted disconnect + automatic reconnect.]]></description>
            <content:encoded><![CDATA[<p>When scaling or rebalancing a Headscale SaaS deployment, the key challenge is how to migrate users with minimal disruption. This post presents a practical pattern: dynamic routing + system-level targeted disconnect + automatic reconnect.</p>
<p>Original post:
<a href="https://ownding.com/2025/10/25/headscale-%E7%B3%BB%E5%88%97%EF%BC%9AHeadscale-SaaS-%E7%8E%AF%E5%A2%83%E4%B8%8B%E7%9A%84%E7%94%A8%E6%88%B7%E6%97%A0%E7%BC%9D%E5%88%87%E6%8D%A2%E5%AE%9E%E6%88%98/" target="_blank" rel="noopener noreferrer" class="">headscale-系列：Headscale-SaaS-环境下的用户无缝切换实战</a></p>
<!-- -->
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="architecture-recap">Architecture Recap<a href="https://docs.larktun.com/en/blog/headscale-saas-seamless-user-switch#architecture-recap" class="hash-link" aria-label="Direct link to Architecture Recap" title="Direct link to Architecture Recap" translate="no">​</a></h2>
<ul>
<li class="">User subdomains (for example <code>hsa.demo.com</code>) point to Traefik.</li>
<li class="">Traefik routes by host to a target headscale node.</li>
<li class="">Backend data is isolated and migrated by <code>cluster_id</code>.</li>
</ul>
<p><img decoding="async" loading="lazy" alt="saas architecture" src="https://docs.larktun.com/en/assets/images/arch-8a93c922edde223ea2163cdee96de0f9.webp" width="949" height="390" class="img_ev3q"></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="migration-challenge">Migration Challenge<a href="https://docs.larktun.com/en/blog/headscale-saas-seamless-user-switch#migration-challenge" class="hash-link" aria-label="Direct link to Migration Challenge" title="Direct link to Migration Challenge" translate="no">​</a></h2>
<p>Existing TCP long-lived connections usually stay attached to the old backend even after proxy route updates.</p>
<p><img decoding="async" loading="lazy" alt="tcp long connection" src="https://docs.larktun.com/en/assets/images/tcp-fe21186a46d8bcc72b4cf360b18fdb6c.webp" width="858" height="698" class="img_ev3q"></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="practical-migration-steps">Practical Migration Steps<a href="https://docs.larktun.com/en/blog/headscale-saas-seamless-user-switch#practical-migration-steps" class="hash-link" aria-label="Direct link to Practical Migration Steps" title="Direct link to Practical Migration Steps" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1-move-user-data">1) Move user data<a href="https://docs.larktun.com/en/blog/headscale-saas-seamless-user-switch#1-move-user-data" class="hash-link" aria-label="Direct link to 1) Move user data" title="Direct link to 1) Move user data" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># Export user-specific data from old node</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">pg_dump -h node3-db -U headscale -t nodes -t ip_addresses \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --where="user_id = (SELECT id FROM users WHERE name = 'hsa')" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  &gt; hsa_data.sql</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Update cluster_id and import into target node</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">sed -i 's/cluster_id: 3/cluster_id: 4/g' hsa_data.sql</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">psql -h node4-db -U headscale -d headscale &lt; hsa_data.sql</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2-hot-update-traefik-routing">2) Hot-update Traefik routing<a href="https://docs.larktun.com/en/blog/headscale-saas-seamless-user-switch#2-hot-update-traefik-routing" class="hash-link" aria-label="Direct link to 2) Hot-update Traefik routing" title="Direct link to 2) Hot-update Traefik routing" translate="no">​</a></h3>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">http</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">routers</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">hsa-router</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">rule</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Host(`hsa.demo.com`)"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">service</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"headscale-cluster4"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">entryPoints</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"https"</span><span class="token punctuation" style="color:#393A34">]</span><br></div></code></pre></div></div>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">curl -X POST http://traefik/api/providers/file?dynamic=true</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3-precisely-cut-old-connection-on-source-node">3) Precisely cut old connection on source node<a href="https://docs.larktun.com/en/blog/headscale-saas-seamless-user-switch#3-precisely-cut-old-connection-on-source-node" class="hash-link" aria-label="Direct link to 3) Precisely cut old connection on source node" title="Direct link to 3) Precisely cut old connection on source node" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">ss -K "dport = 7890 and src 203.0.113.5"</span><br></div></code></pre></div></div>
<p>Clients usually reconnect to the new node within seconds using their existing node keys, without re-authentication.</p>
<p><img decoding="async" loading="lazy" alt="seamless switch flow" src="https://docs.larktun.com/en/assets/images/change-5fdae981c99f791275e97d7cc7cc0230.webp" width="889" height="658" class="img_ev3q"></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="observed-results-from-original-post">Observed Results (from original post)<a href="https://docs.larktun.com/en/blog/headscale-saas-seamless-user-switch#observed-results-from-original-post" class="hash-link" aria-label="Direct link to Observed Results (from original post)" title="Direct link to Observed Results (from original post)" translate="no">​</a></h2>
<ul>
<li class="">Migration completion: typically under 15 seconds</li>
<li class="">Client interruption: typically 1-5 seconds</li>
<li class="">Re-authentication rate: near 0%</li>
</ul>
<hr>
<p>This article is mirrored on the Larktun blog. For source updates and original context, refer to:
<a href="https://ownding.com/2025/10/25/headscale-%E7%B3%BB%E5%88%97%EF%BC%9AHeadscale-SaaS-%E7%8E%AF%E5%A2%83%E4%B8%8B%E7%9A%84%E7%94%A8%E6%88%B7%E6%97%A0%E7%BC%9D%E5%88%87%E6%8D%A2%E5%AE%9E%E6%88%98/" target="_blank" rel="noopener noreferrer" class="">headscale-系列：Headscale-SaaS-环境下的用户无缝切换实战</a></p>]]></content:encoded>
            <category>headscale</category>
            <category>SaaS</category>
            <category>migration</category>
            <category>traefik</category>
        </item>
        <item>
            <title><![CDATA[Headscale Series: Build a Headscale Cluster for Near-Unlimited Device Onboarding]]></title>
            <link>https://docs.larktun.com/en/blog/headscale-cluster-infinite-scale</link>
            <guid>https://docs.larktun.com/en/blog/headscale-cluster-infinite-scale</guid>
            <pubDate>Wed, 01 Oct 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[This post explains how to evolve headscale from a monolith into a near linearly scalable cluster using cluster_id sharding, shared PostgreSQL, an admission/control service (ACS), strict query isolation, and incremental netmap rebuilds.]]></description>
            <content:encoded><![CDATA[<p>This post explains how to evolve <code>headscale</code> from a monolith into a near linearly scalable cluster using <code>cluster_id</code> sharding, shared PostgreSQL, an admission/control service (ACS), strict query isolation, and incremental netmap rebuilds.</p>
<p>Original post:
<a href="https://ownding.com/2025/10/01/headscale-%E7%B3%BB%E5%88%97%EF%BC%9A%E7%BB%84%E5%BB%BA-headscale-%E9%9B%86%E7%BE%A4%EF%BC%8C%E6%8A%8A%E8%AE%BE%E5%A4%87%E6%8E%A5%E5%85%A5%E8%83%BD%E5%8A%9B%E5%81%9A%E6%88%90%E2%80%9C%E6%97%A0%E9%99%90%E6%8E%A5%E8%BF%91%E6%97%A0%E9%99%90%E2%80%9D/" target="_blank" rel="noopener noreferrer" class="">headscale 系列：组建 headscale 集群，把设备接入能力做成“无限接近无限”</a></p>
<!-- -->
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="core-takeaway">Core Takeaway<a href="https://docs.larktun.com/en/blog/headscale-cluster-infinite-scale#core-takeaway" class="hash-link" aria-label="Direct link to Core Takeaway" title="Direct link to Core Takeaway" translate="no">​</a></h2>
<p>These building blocks move headscale toward a SaaS-style control plane:</p>
<ul>
<li class=""><code>cluster_id</code> logical sharding</li>
<li class="">Shared PG/PG cluster</li>
<li class="">ACS for tenant assignment and sticky routing</li>
<li class="">Mandatory <code>cluster_id</code> filtering in DAO/ORM</li>
<li class="">Incremental netmap calculation instead of full rebuilds</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="architecture-highlights">Architecture Highlights<a href="https://docs.larktun.com/en/blog/headscale-cluster-infinite-scale#architecture-highlights" class="hash-link" aria-label="Direct link to Architecture Highlights" title="Direct link to Architecture Highlights" translate="no">​</a></h2>
<ol>
<li class="">Multiple <code>headscale</code> nodes run in parallel, each bound to a unique <code>cluster_id</code>.</li>
<li class="">All nodes share one database layer, but every query must include <code>cluster_id</code>.</li>
<li class="">ACS handles first-time assignment, policy constraints, and balancing.</li>
<li class="">LISTEN/NOTIFY + debounce limits netmap recomputation scope and CPU impact.</li>
</ol>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="startup-example">Startup Example<a href="https://docs.larktun.com/en/blog/headscale-cluster-infinite-scale#startup-example" class="hash-link" aria-label="Direct link to Startup Example" title="Direct link to Startup Example" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">CLUSTER_ID=A \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">PG_DSN="postgres://..." \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">DERP_MAP_URL="https://derp.example.com/map.json" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">./headscale --config ./config.yaml</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="isolation-snippets">Isolation Snippets<a href="https://docs.larktun.com/en/blog/headscale-cluster-infinite-scale#isolation-snippets" class="hash-link" aria-label="Direct link to Isolation Snippets" title="Direct link to Isolation Snippets" translate="no">​</a></h2>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">ALTER</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">TABLE</span><span class="token plain"> nodes </span><span class="token keyword" style="color:#00009f">ADD</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">COLUMN</span><span class="token plain"> cluster_id </span><span class="token keyword" style="color:#00009f">TEXT</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">NOT</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">NULL</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">DEFAULT</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'default'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">INDEX</span><span class="token plain"> idx_nodes_cluster_id </span><span class="token keyword" style="color:#00009f">ON</span><span class="token plain"> nodes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cluster_id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">c </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">ClusterDB</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">Scoped</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">gorm</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">DB </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> c</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">db</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Where</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"cluster_id = ?"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> c</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">clusterID</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="incremental-netmap-strategy">Incremental Netmap Strategy<a href="https://docs.larktun.com/en/blog/headscale-cluster-infinite-scale#incremental-netmap-strategy" class="hash-link" aria-label="Direct link to Incremental Netmap Strategy" title="Direct link to Incremental Netmap Strategy" translate="no">​</a></h2>
<ul>
<li class="">On data changes, emit <code>NOTIFY netmap_dirty(cluster_id, scope_key)</code>.</li>
<li class="">Each node consumes only matching <code>cluster_id</code> events.</li>
<li class="">Rebuild only affected scope (<code>user/tag/route domain</code>) rather than full sets.</li>
<li class="">Add caching + versioning to minimize redundant computation.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="demo-screenshots">Demo Screenshots<a href="https://docs.larktun.com/en/blog/headscale-cluster-infinite-scale#demo-screenshots" class="hash-link" aria-label="Direct link to Demo Screenshots" title="Direct link to Demo Screenshots" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" alt="cluster id 1 nodes" src="https://docs.larktun.com/en/assets/images/h1-35cf1040c6b5197e5b905e89ba6e8894.webp" width="1280" height="799" class="img_ev3q">
<img decoding="async" loading="lazy" alt="cluster id 2 nodes" src="https://docs.larktun.com/en/assets/images/h2-b8103230a656ad59e5f72941c7538267.webp" width="1240" height="812" class="img_ev3q">
<img decoding="async" loading="lazy" alt="users table" src="https://docs.larktun.com/en/assets/images/user-a801867fb05bed413d3a81541e1c574d.webp" width="939" height="603" class="img_ev3q">
<img decoding="async" loading="lazy" alt="nodes table" src="https://docs.larktun.com/en/assets/images/nodes-469ba0c950c2b2b749b9044a0efce100.webp" width="935" height="593" class="img_ev3q">
<img decoding="async" loading="lazy" alt="preauth keys table" src="https://docs.larktun.com/en/assets/images/keys-64326793ec4d87f26d1a1128214561a4.webp" width="936" height="299" class="img_ev3q"></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="related-link">Related Link<a href="https://docs.larktun.com/en/blog/headscale-cluster-infinite-scale#related-link" class="hash-link" aria-label="Direct link to Related Link" title="Direct link to Related Link" translate="no">​</a></h2>
<ul>
<li class="">Project:
<a href="https://github.com/OwnDing/headscale-saas" target="_blank" rel="noopener noreferrer" class="">OwnDing/headscale-saas</a></li>
</ul>
<hr>
<p>This article is mirrored on the Larktun blog. For source updates and original context, refer to:
<a href="https://ownding.com/2025/10/01/headscale-%E7%B3%BB%E5%88%97%EF%BC%9A%E7%BB%84%E5%BB%BA-headscale-%E9%9B%86%E7%BE%A4%EF%BC%8C%E6%8A%8A%E8%AE%BE%E5%A4%87%E6%8E%A5%E5%85%A5%E8%83%BD%E5%8A%9B%E5%81%9A%E6%88%90%E2%80%9C%E6%97%A0%E9%99%90%E6%8E%A5%E8%BF%91%E6%97%A0%E9%99%90%E2%80%9D/" target="_blank" rel="noopener noreferrer" class="">headscale 系列：组建 headscale 集群，把设备接入能力做成“无限接近无限”</a></p>]]></content:encoded>
            <category>headscale</category>
            <category>cluster</category>
            <category>SaaS</category>
            <category>scalability</category>
        </item>
        <item>
            <title><![CDATA[Headscale Series: Headscale Stress Testing]]></title>
            <link>https://docs.larktun.com/en/blog/headscale-stress-test</link>
            <guid>https://docs.larktun.com/en/blog/headscale-stress-test</guid>
            <pubDate>Sun, 28 Sep 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[This article documents a practical stress-test workflow for headscale at around 200 concurrent clients, including environment sizing, bootstrap scripts, churn tests, and traffic sampling.]]></description>
            <content:encoded><![CDATA[<p>This article documents a practical stress-test workflow for <code>headscale</code> at around 200 concurrent clients, including environment sizing, bootstrap scripts, churn tests, and traffic sampling.</p>
<p>Original post:
<a href="https://ownding.com/2025/09/28/headscale%E7%B3%BB%E5%88%97%EF%BC%9Aheadsale%E5%8E%8B%E5%8A%9B%E6%B5%8B%E8%AF%95/" target="_blank" rel="noopener noreferrer" class="">headscale系列：headsale压力测试</a></p>
<!-- -->
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="test-scripts">Test Scripts<a href="https://docs.larktun.com/en/blog/headscale-stress-test#test-scripts" class="hash-link" aria-label="Direct link to Test Scripts" title="Direct link to Test Scripts" translate="no">​</a></h2>
<ul>
<li class="">Repository:
<a href="https://github.com/OwnDing/headscale-test-scripts.git" target="_blank" rel="noopener noreferrer" class="">OwnDing/headscale-test-scripts</a></li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="quick-runbook">Quick Runbook<a href="https://docs.larktun.com/en/blog/headscale-stress-test#quick-runbook" class="hash-link" aria-label="Direct link to Quick Runbook" title="Direct link to Quick Runbook" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># 1) Prepare env</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cp .env.example .env</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 2) Validate host environment</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">bash scripts/00_check_env.sh</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 3) Bootstrap clients (default 200)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">bash scripts/01_bootstrap_up.sh</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 4) Export node list</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">bash scripts/02_list_nodes.sh</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 5) Churn test (terminal A)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">bash scripts/03_churn.sh</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 6) Traffic rounds (terminal B)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">watch -n 20 'bash scripts/04_traffic_round.sh'</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 7) Cleanup</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">bash scripts/99_cleanup.sh</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="key-environment-variables">Key Environment Variables<a href="https://docs.larktun.com/en/blog/headscale-stress-test#key-environment-variables" class="hash-link" aria-label="Direct link to Key Environment Variables" title="Direct link to Key Environment Variables" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">HEADSCALE_URL=https://headscale.example.com</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">TS_AUTHKEY=tskey-auth-xxxxxxxxxxxxxxxxxxxxxxxxxxxx</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">REPLICAS=200</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">CHURN_INTERVAL=30</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">CHURN_BATCH_PERCENT=10</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">TRAFFIC_PAIR_COUNT=30</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">TRAFFIC_TCP_DURATION=10</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">TRAFFIC_TCP_PARALLEL=4</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">TRAFFIC_UDP_DURATION=10</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">TRAFFIC_UDP_BW=50M</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="recommended-sizing-200-devices">Recommended Sizing (200 devices)<a href="https://docs.larktun.com/en/blog/headscale-stress-test#recommended-sizing-200-devices" class="hash-link" aria-label="Direct link to Recommended Sizing (200 devices)" title="Direct link to Recommended Sizing (200 devices)" translate="no">​</a></h2>
<ul>
<li class="">Load generator: 16 vCPU / 32 GB / 100 GB NVMe</li>
<li class="">Headscale control plane target: 4 vCPU / 8 GB / 100 GB</li>
<li class="">Optional DERP relay: 2-4 vCPU / 2-4 GB</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="script-roles">Script Roles<a href="https://docs.larktun.com/en/blog/headscale-stress-test#script-roles" class="hash-link" aria-label="Direct link to Script Roles" title="Direct link to Script Roles" translate="no">​</a></h2>
<ul>
<li class=""><code>00_check_env.sh</code>: validate docker/compose/tun and pre-pull images.</li>
<li class=""><code>01_bootstrap_up.sh</code>: scale client containers to <code>REPLICAS</code>.</li>
<li class=""><code>02_list_nodes.sh</code>: export <code>nodes.tsv</code> (container -&gt; Tailnet IPv4).</li>
<li class=""><code>03_churn.sh</code>: periodic random <code>up/down/restart</code>.</li>
<li class=""><code>04_traffic_round.sh</code>: random TCP/UDP pair traffic rounds.</li>
<li class=""><code>99_cleanup.sh</code>: stop and remove test containers and sidecars.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="result-screenshots">Result Screenshots<a href="https://docs.larktun.com/en/blog/headscale-stress-test#result-screenshots" class="hash-link" aria-label="Direct link to Result Screenshots" title="Direct link to Result Screenshots" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" alt="200 clients online" src="https://docs.larktun.com/en/assets/images/clients-a944115873ee7273eb207d1232c228af.webp" width="1772" height="826" class="img_ev3q">
<img decoding="async" loading="lazy" alt="headscale cpu usage" src="https://docs.larktun.com/en/assets/images/cpus-8bf737c41aabb0138290cf20cebd17a8.webp" width="925" height="128" class="img_ev3q">
<img decoding="async" loading="lazy" alt="client memory usage" src="https://docs.larktun.com/en/assets/images/mems-8a24e63100eb9ea3d8ec41c739f2b7a2.webp" width="1772" height="826" class="img_ev3q"></p>
<hr>
<p>This article is mirrored on the Larktun blog. For source updates and original context, refer to:
<a href="https://ownding.com/2025/09/28/headscale%E7%B3%BB%E5%88%97%EF%BC%9Aheadsale%E5%8E%8B%E5%8A%9B%E6%B5%8B%E8%AF%95/" target="_blank" rel="noopener noreferrer" class="">headscale系列：headsale压力测试</a></p>]]></content:encoded>
            <category>headscale</category>
            <category>stress-test</category>
            <category>docker</category>
            <category>tailscale</category>
        </item>
        <item>
            <title><![CDATA[Headscale Series: Using MagicDNS in Headscale]]></title>
            <link>https://docs.larktun.com/en/blog/headscale-magicdns</link>
            <guid>https://docs.larktun.com/en/blog/headscale-magicdns</guid>
            <pubDate>Thu, 14 Aug 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[This post walks through enabling MagicDNS in headscale, plus practical usage of extra-records.json so clients can reach devices by hostname instead of memorizing raw IP addresses.]]></description>
            <content:encoded><![CDATA[<p>This post walks through enabling <code>MagicDNS</code> in <code>headscale</code>, plus practical usage of <code>extra-records.json</code> so clients can reach devices by hostname instead of memorizing raw IP addresses.</p>
<p>Original post:
<a href="https://ownding.com/2025/08/14/headscale%E7%B3%BB%E5%88%97%EF%BC%9A%E5%A6%82%E4%BD%95%E5%9C%A8headscale%E4%B8%AD%E4%BD%BF%E7%94%A8MagicDNS/" target="_blank" rel="noopener noreferrer" class="">headscale系列：如何在headscale中使用MagicDNS</a></p>
<!-- -->
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="scenario">Scenario<a href="https://docs.larktun.com/en/blog/headscale-magicdns#scenario" class="hash-link" aria-label="Direct link to Scenario" title="Direct link to Scenario" translate="no">​</a></h2>
<ul>
<li class="">After login, clients can communicate with peers using hostnames in addition to IP.</li>
<li class="">On Windows, <code>tailscale</code> writes host mappings into the system <code>hosts</code> file.</li>
<li class="">This is especially useful when using features like <code>4via6</code>.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="enable-magicdns">Enable MagicDNS<a href="https://docs.larktun.com/en/blog/headscale-magicdns#enable-magicdns" class="hash-link" aria-label="Direct link to Enable MagicDNS" title="Direct link to Enable MagicDNS" translate="no">​</a></h2>
<p>Update your <code>config.yaml</code> (core options shown below):</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">dns</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">magic_dns</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean important" style="color:#36acaa">true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">base_domain</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> example.com</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">override_local_dns</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean important" style="color:#36acaa">false</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">nameservers</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">global</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> 1.1.1.1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> 1.0.0.1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">search_domains</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">extra_records_path</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> /var/lib/headscale/extra</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">records.json</span><br></div></code></pre></div></div>
<p>Using <code>extra_records_path</code> makes DNS record updates easier to maintain.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="extra-recordsjson-example">extra-records.json Example<a href="https://docs.larktun.com/en/blog/headscale-magicdns#extra-recordsjson-example" class="hash-link" aria-label="Direct link to extra-records.json Example" title="Direct link to extra-records.json Example" translate="no">​</a></h2>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"192-168-6-1-via-7"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"AAAA"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"value"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"fd7a:115c:a1e0:b1a:0:7:c0a8:601"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">]</span><br></div></code></pre></div></div>
<ul>
<li class=""><code>type</code> supports <code>A</code> (IPv4) and <code>AAAA</code> (IPv6).</li>
<li class="">On first migration from inline <code>extra_records</code> to file-based records, one restart is recommended.</li>
<li class="">After that, changing <code>extra-records.json</code> is usually enough.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="usage">Usage<a href="https://docs.larktun.com/en/blog/headscale-magicdns#usage" class="hash-link" aria-label="Direct link to Usage" title="Direct link to Usage" translate="no">​</a></h2>
<p>After clients come online, you can access target services by the custom DNS name.</p>
<p><img decoding="async" loading="lazy" alt="route table update" src="https://docs.larktun.com/en/assets/images/route-3f10725f44c1dda24468712ab521d7df.webp" width="855" height="724" class="img_ev3q">
<img decoding="async" loading="lazy" alt="magicdns query example" src="https://docs.larktun.com/en/assets/images/dns-5bc8042e0638778e3dcf3ba274f2ff5c.webp" width="1129" height="1100" class="img_ev3q"></p>
<hr>
<p>This article is mirrored on the Larktun blog. For source updates and original context, refer to:
<a href="https://ownding.com/2025/08/14/headscale%E7%B3%BB%E5%88%97%EF%BC%9A%E5%A6%82%E4%BD%95%E5%9C%A8headscale%E4%B8%AD%E4%BD%BF%E7%94%A8MagicDNS/" target="_blank" rel="noopener noreferrer" class="">headscale系列：如何在headscale中使用MagicDNS</a></p>]]></content:encoded>
            <category>headscale</category>
            <category>MagicDNS</category>
            <category>DNS</category>
        </item>
        <item>
            <title><![CDATA[Headscale Series: Packaging Source-Built Headscale as a Docker Image]]></title>
            <link>https://docs.larktun.com/en/blog/headscale-source-build-docker-image</link>
            <guid>https://docs.larktun.com/en/blog/headscale-source-build-docker-image</guid>
            <pubDate>Fri, 08 Aug 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[The official Headscale image is usually around 80MB in docker images. In contrast, custom images built from a handwritten Dockerfile are often much larger.]]></description>
            <content:encoded><![CDATA[<p>The official <code>Headscale</code> image is usually around <code>80MB</code> in <code>docker images</code>. In contrast, custom images built from a handwritten <code>Dockerfile</code> are often much larger.</p>
<p>This post documents a closer-to-official approach: use <code>ko</code> to package Headscale into a container image, instead of maintaining a Dockerfile.</p>
<p>Original post:
<a href="https://ownding.com/2025/08/08/headscale%E7%B3%BB%E5%88%97%EF%BC%9A%E5%A6%82%E4%BD%95%E5%B0%86%E6%BA%90%E7%A0%81%E7%BC%96%E8%AF%91%E7%9A%84headscale%E6%89%93%E5%8C%85%E6%88%90docker%E9%95%9C%E5%83%8F/" target="_blank" rel="noopener noreferrer" class="">Headscale series: package source-built headscale as Docker image</a></p>
<!-- -->
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="why-ko">Why ko<a href="https://docs.larktun.com/en/blog/headscale-source-build-docker-image#why-ko" class="hash-link" aria-label="Direct link to Why ko" title="Direct link to Why ko" translate="no">​</a></h2>
<p><code>ko</code> is a lightweight tool for building and packaging Go applications for container platforms (Docker / Kubernetes). It can build container images directly from Go source, without writing a Dockerfile.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="install-ko">Install ko<a href="https://docs.larktun.com/en/blog/headscale-source-build-docker-image#install-ko" class="hash-link" aria-label="Direct link to Install ko" title="Direct link to Install ko" translate="no">​</a></h2>
<p>Install <code>ko</code> as a regular user:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">go install github.com/google/ko@latest</span><br></div></code></pre></div></div>
<p>After installation, the binary is typically available at <code>~/go/bin/ko</code>.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="build-headscale-image-with-ko">Build Headscale Image with ko<a href="https://docs.larktun.com/en/blog/headscale-source-build-docker-image#build-headscale-image-with-ko" class="hash-link" aria-label="Direct link to Build Headscale Image with ko" title="Direct link to Build Headscale Image with ko" translate="no">​</a></h2>
<p>Run in the Headscale source directory:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># --local means build locally without pushing to a registry</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">/home/djc/go/bin/ko build --local ./cmd/headscale</span><br></div></code></pre></div></div>
<p>After build, local images named <code>ko.local/...</code> will be created. Example <code>docker images</code> output:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">REPOSITORY                                            TAG                                                                IMAGE ID       CREATED      SIZE</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">headscale                                             v0.26.1-r                                                          bf66da388ca1   6 days ago   87.5MB</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ko.local/headscale-f40b3d8640713cd381403459ebd67e78   38aefca56cab7d9b11692c61968915fb59fdf1dce134e52fed02ae2fa3a0e871   bf66da388ca1   6 days ago   87.5MB</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ko.local/headscale-f40b3d8640713cd381403459ebd67e78   latest                                                             bf66da388ca1   6 days ago   87.5MB</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ghcr.io/juanfont/headscale                            v0.26.1                                                            b9e7b75fd3b0   N/A          80.8MB</span><br></div></code></pre></div></div>
<p>You can rename the generated image with <code>docker tag</code>.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="runtime-notes">Runtime Notes<a href="https://docs.larktun.com/en/blog/headscale-source-build-docker-image#runtime-notes" class="hash-link" aria-label="Direct link to Runtime Notes" title="Direct link to Runtime Notes" translate="no">​</a></h2>
<p>At runtime, prepare:</p>
<ul>
<li class=""><code>config.yaml</code></li>
<li class="">Read/write permissions for <code>/var/run/headscale/</code> and <code>/var/lib/headscale/</code></li>
</ul>
<p>In <code>config.yaml</code>, you can change:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">unix_socket</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> /var/run/headscale/headscale.sock</span><br></div></code></pre></div></div>
<p>to:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">unix_socket</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> /var/lib/headscale/headscale.sock</span><br></div></code></pre></div></div>
<p>Then run the container with only <code>/var/lib/headscale/</code> mounted:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">docker run -d \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -v ./headscale/config.yaml:/etc/headscale/config.yaml \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -v ./doc:/var/lib/headscale \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --name headscale \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -p 8080:8080 \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -p 9090:9090 \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  headscale:v0.26.1-r serve</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="reference">Reference<a href="https://docs.larktun.com/en/blog/headscale-source-build-docker-image#reference" class="hash-link" aria-label="Direct link to Reference" title="Direct link to Reference" translate="no">​</a></h2>
<ul>
<li class="">Source build article:
<a href="https://ownding.com/2025/07/30/%E4%BD%BF%E7%94%A8Headscale%E6%BA%90%E7%A0%81%E8%87%AA%E8%A1%8C%E7%BC%96%E8%AF%91%E6%89%93%E5%8C%85/" target="_blank" rel="noopener noreferrer" class="">Build Headscale from Source</a></li>
</ul>
<hr>
<p>This article is mirrored on the Larktun blog. For source updates and original context, refer to:
<a href="https://ownding.com/2025/08/08/headscale%E7%B3%BB%E5%88%97%EF%BC%9A%E5%A6%82%E4%BD%95%E5%B0%86%E6%BA%90%E7%A0%81%E7%BC%96%E8%AF%91%E7%9A%84headscale%E6%89%93%E5%8C%85%E6%88%90docker%E9%95%9C%E5%83%8F/" target="_blank" rel="noopener noreferrer" class="">Headscale series: package source-built headscale as Docker image</a></p>]]></content:encoded>
            <category>headscale</category>
            <category>Docker</category>
            <category>ko</category>
            <category>image-build</category>
        </item>
        <item>
            <title><![CDATA[Building Headscale from Source]]></title>
            <link>https://docs.larktun.com/en/blog/headscale-build-from-source</link>
            <guid>https://docs.larktun.com/en/blog/headscale-build-from-source</guid>
            <pubDate>Wed, 30 Jul 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[This post is adapted from my personal blog and documents a practical workflow to build Headscale from source on Windows + WSL2 + Ubuntu, including common build errors and fixes.]]></description>
            <content:encoded><![CDATA[<p>This post is adapted from my personal blog and documents a practical workflow to build Headscale from source on Windows + WSL2 + Ubuntu, including common build errors and fixes.</p>
<p>Original post:
<a href="https://ownding.com/2025/07/30/%E4%BD%BF%E7%94%A8Headscale%E6%BA%90%E7%A0%81%E8%87%AA%E8%A1%8C%E7%BC%96%E8%AF%91%E6%89%93%E5%8C%85/" target="_blank" rel="noopener noreferrer" class="">Using Headscale Source Code to Build and Package</a></p>
<!-- -->
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="context">Context<a href="https://docs.larktun.com/en/blog/headscale-build-from-source#context" class="hash-link" aria-label="Direct link to Context" title="Direct link to Context" translate="no">​</a></h2>
<ul>
<li class="">OS: Windows 11 + WSL2 Ubuntu 24.04 LTS</li>
<li class="">Example version: Headscale v0.26.1</li>
<li class="">Repository: <a href="https://github.com/juanfont/headscale" target="_blank" rel="noopener noreferrer" class="">github.com/juanfont/headscale</a></li>
<li class="">Recommendation: clone with Git instead of downloading a source archive</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="environment-setup">Environment Setup<a href="https://docs.larktun.com/en/blog/headscale-build-from-source#environment-setup" class="hash-link" aria-label="Direct link to Environment Setup" title="Direct link to Environment Setup" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1-clone-the-source">1) Clone the source<a href="https://docs.larktun.com/en/blog/headscale-build-from-source#1-clone-the-source" class="hash-link" aria-label="Direct link to 1) Clone the source" title="Direct link to 1) Clone the source" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git clone https://github.com/juanfont/headscale.git</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Checkout v0.26.1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git checkout -b release-v0.26.1 v0.26.1</span><br></div></code></pre></div></div>
<p>Then copy <code>config-example.yaml</code> to <code>config.yaml</code> in the project root.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2-configure-wsl">2) Configure WSL<a href="https://docs.larktun.com/en/blog/headscale-build-from-source#2-configure-wsl" class="hash-link" aria-label="Direct link to 2) Configure WSL" title="Direct link to 2) Configure WSL" translate="no">​</a></h3>
<p>Create <code>.wslconfig</code> in your Windows user directory (for example, <code>C:\Users\XXX</code>):</p>
<div class="language-ini codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ini codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">[wsl2]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">nestedVirtualization=true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ipv6=true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">[experimental]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">autoMemoryReclaim=gradual</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">networkingMode=mirrored</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">dnsTunneling=true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">firewall=true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">autoProxy=true</span><br></div></code></pre></div></div>
<p>Restart WSL before continuing.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3-install-nix-multi-user">3) Install Nix (Multi-user)<a href="https://docs.larktun.com/en/blog/headscale-build-from-source#3-install-nix-multi-user" class="hash-link" aria-label="Direct link to 3) Install Nix (Multi-user)" title="Direct link to 3) Install Nix (Multi-user)" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">sh &lt;(curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemon</span><br></div></code></pre></div></div>
<p>Reference: <a href="https://nixos.org/download/" target="_blank" rel="noopener noreferrer" class="">nixos.org/download</a></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="build-flow">Build Flow<a href="https://docs.larktun.com/en/blog/headscale-build-from-source#build-flow" class="hash-link" aria-label="Direct link to Build Flow" title="Direct link to Build Flow" translate="no">​</a></h2>
<p>Run build steps as a regular user (do not run <code>make build</code> as <code>root</code>):</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># Enter source directory</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cd /mnt/c/Users/XXX/Documents/develop/0me/headscale</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Enter dev shell</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">nix develop</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">make generate</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">make test</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">make build</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Check build output</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ls -la result/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cd result/bin</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">./headscale version</span><br></div></code></pre></div></div>
<p>After a successful build, a <code>result</code> directory is generated.</p>
<p><img decoding="async" loading="lazy" alt="headscale build result" src="https://docs.larktun.com/en/assets/images/result-890ed849e723f795423053931a08a192.webp" width="859" height="123" class="img_ev3q">
<img decoding="async" loading="lazy" alt="headscale build result on windows" src="https://docs.larktun.com/en/assets/images/w-result-5be58e4c356927dcbf31a3a25d406e7f.webp" width="718" height="540" class="img_ev3q"></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="running-the-binary">Running the Binary<a href="https://docs.larktun.com/en/blog/headscale-build-from-source#running-the-binary" class="hash-link" aria-label="Direct link to Running the Binary" title="Direct link to Running the Binary" translate="no">​</a></h2>
<p>After copying the built binary to a target server, check dependencies first:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">ldd headscale</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">file headscale</span><br></div></code></pre></div></div>
<p>If your binary points to a Nix interpreter path, prepare the required path and linker file based on <code>ldd</code> output. Also verify that <code>config.yaml</code>, <code>/root/.headscale/</code>, and <code>/var/lib/headscale/</code> exist with correct permissions.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="packaging-as-docker-image">Packaging as Docker Image<a href="https://docs.larktun.com/en/blog/headscale-build-from-source#packaging-as-docker-image" class="hash-link" aria-label="Direct link to Packaging as Docker Image" title="Direct link to Packaging as Docker Image" translate="no">​</a></h2>
<p>If you want to package the source-built binary into a Docker image, see:
<a href="https://ownding.com/2025/08/08/headscale%E7%B3%BB%E5%88%97%EF%BC%9A%E5%A6%82%E4%BD%95%E5%B0%86%E6%BA%90%E7%A0%81%E7%BC%96%E8%AF%91%E7%9A%84headscale%E6%89%93%E5%8C%85%E6%88%90docker%E9%95%9C%E5%83%8F/" target="_blank" rel="noopener noreferrer" class="">Headscale series: package source-built headscale as Docker image</a></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="common-build-errors">Common Build Errors<a href="https://docs.larktun.com/en/blog/headscale-build-from-source#common-build-errors" class="hash-link" aria-label="Direct link to Common Build Errors" title="Direct link to Common Build Errors" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="issue-1-dirtyshortrev-missing">Issue 1: <code>dirtyShortRev</code> missing<a href="https://docs.larktun.com/en/blog/headscale-build-from-source#issue-1-dirtyshortrev-missing" class="hash-link" aria-label="Direct link to issue-1-dirtyshortrev-missing" title="Direct link to issue-1-dirtyshortrev-missing" translate="no">​</a></h3>
<p>When <code>make build</code> fails with <code>attribute 'dirtyShortRev' missing</code>, add a fallback in <code>flake.nix</code>:</p>
<div class="language-nix codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-nix codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">headscaleVersion = if self ? shortRev</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                  then self.shortRev</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                  else if self ? dirtyShortRev</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                  then self.dirtyShortRev</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                  else "v0.26.1";</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="issue-2-missing-configyaml-during-test-phase">Issue 2: missing <code>config.yaml</code> during test phase<a href="https://docs.larktun.com/en/blog/headscale-build-from-source#issue-2-missing-configyaml-during-test-phase" class="hash-link" aria-label="Direct link to issue-2-missing-configyaml-during-test-phase" title="Direct link to issue-2-missing-configyaml-during-test-phase" translate="no">​</a></h3>
<p>If build fails in checks, prepare config and build binary directly:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">cp config-example.yaml config.yaml</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">go mod tidy</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">go build -o headscale ./cmd/headscale</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="issue-3-nix-command-disabled">Issue 3: <code>nix-command</code> disabled<a href="https://docs.larktun.com/en/blog/headscale-build-from-source#issue-3-nix-command-disabled" class="hash-link" aria-label="Direct link to issue-3-nix-command-disabled" title="Direct link to issue-3-nix-command-disabled" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">echo 'experimental-features = nix-command flakes' &gt;&gt; /etc/nix/nix.conf</span><br></div></code></pre></div></div>
<p>Run the config command with <code>root</code>.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="issue-4-flakenix-not-found">Issue 4: <code>flake.nix</code> not found<a href="https://docs.larktun.com/en/blog/headscale-build-from-source#issue-4-flakenix-not-found" class="hash-link" aria-label="Direct link to issue-4-flakenix-not-found" title="Direct link to issue-4-flakenix-not-found" translate="no">​</a></h3>
<p>If <code>nix develop</code> reports no <code>flake.nix</code>, switch to the source directory first, then retry.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="issue-5-initdb-cannot-run-as-root">Issue 5: <code>initdb</code> cannot run as root<a href="https://docs.larktun.com/en/blog/headscale-build-from-source#issue-5-initdb-cannot-run-as-root" class="hash-link" aria-label="Direct link to issue-5-initdb-cannot-run-as-root" title="Direct link to issue-5-initdb-cannot-run-as-root" translate="no">​</a></h3>
<p>PostgreSQL init in tests cannot run as <code>root</code>. Use a regular user for the build and test flow.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="manual-dependencies">Manual Dependencies<a href="https://docs.larktun.com/en/blog/headscale-build-from-source#manual-dependencies" class="hash-link" aria-label="Direct link to Manual Dependencies" title="Direct link to Manual Dependencies" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># Install Buf</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">go install github.com/bufbuild/buf/cmd/buf@v1.55.1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Install Protobuf</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">sudo apt update</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">sudo apt install protobuf-compiler</span><br></div></code></pre></div></div>
<hr>
<p>This article is mirrored on the Larktun blog. For source updates and original context, refer to:
<a href="https://ownding.com/2025/07/30/%E4%BD%BF%E7%94%A8Headscale%E6%BA%90%E7%A0%81%E8%87%AA%E8%A1%8C%E7%BC%96%E8%AF%91%E6%89%93%E5%8C%85/" target="_blank" rel="noopener noreferrer" class="">Using Headscale Source Code to Build and Package</a></p>]]></content:encoded>
            <category>headscale</category>
            <category>build</category>
            <category>WSL</category>
            <category>Nix</category>
        </item>
    </channel>
</rss>