50 lines
21 KiB
HTML
50 lines
21 KiB
HTML
<!doctype html><html lang=en><head><title>Deployment Lessons and My Take on Self-Hosting OpenClaw · Eric X. Liu's Personal Page</title><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><meta name=color-scheme content="light dark"><meta http-equiv=Content-Security-Policy content="upgrade-insecure-requests; block-all-mixed-content; default-src 'self'; child-src 'self'; font-src 'self' https://fonts.gstatic.com https://cdn.jsdelivr.net/; form-action 'self'; frame-src 'self' https://www.youtube.com https://disqus.com; img-src 'self' https://referrer.disqus.com https://c.disquscdn.com https://*.disqus.com; object-src 'none'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com/ https://cdn.jsdelivr.net/; script-src 'self' 'unsafe-inline' https://www.google-analytics.com https://cdn.jsdelivr.net/ https://pagead2.googlesyndication.com https://static.cloudflareinsights.com https://unpkg.com https://ericxliu-me.disqus.com https://disqus.com https://*.disqus.com https://*.disquscdn.com https://unpkg.com; connect-src 'self' https://www.google-analytics.com https://pagead2.googlesyndication.com https://cloudflareinsights.com ws://localhost:1313 ws://localhost:* wss://localhost:* https://links.services.disqus.com https://*.disqus.com;"><meta name=author content="Eric X. Liu"><meta name=description content="Deploying autonomous agents like OpenClaw on a self-hosted Kubernetes cluster offers significantly more control and integration potential than cloud-hosted alternatives. However, moving from a standard SaaS model to running your own intelligence infrastructure introduces several deployment challenges.
|
||
Here are the practical lessons learned, organized by the layers of the agentic stack: Environment, Runtime, and Capabilities.
|
||
|
||
Layer 1: The Environment – Breaking the Sandbox
|
||
|
||
|
||
Link to heading
|
||
|
||
|
||
To move beyond being a chatbot, an agent needs to be able to affect its world. Deep integration starts with networking."><meta name=keywords content="software engineer,performance engineering,Google engineer,tech blog,software development,performance optimization,Eric Liu,engineering blog,mountain biking,Jeep enthusiast,overlanding,camping,outdoor adventures"><meta name=twitter:card content="summary"><meta name=twitter:title content="Deployment Lessons and My Take on Self-Hosting OpenClaw"><meta name=twitter:description content="Deploying autonomous agents like OpenClaw on a self-hosted Kubernetes cluster offers significantly more control and integration potential than cloud-hosted alternatives. However, moving from a standard SaaS model to running your own intelligence infrastructure introduces several deployment challenges.
|
||
Here are the practical lessons learned, organized by the layers of the agentic stack: Environment, Runtime, and Capabilities.
|
||
Layer 1: The Environment – Breaking the Sandbox Link to heading To move beyond being a chatbot, an agent needs to be able to affect its world. Deep integration starts with networking."><meta property="og:url" content="https://ericxliu.me/posts/blog-draft/"><meta property="og:site_name" content="Eric X. Liu's Personal Page"><meta property="og:title" content="Deployment Lessons and My Take on Self-Hosting OpenClaw"><meta property="og:description" content="Deploying autonomous agents like OpenClaw on a self-hosted Kubernetes cluster offers significantly more control and integration potential than cloud-hosted alternatives. However, moving from a standard SaaS model to running your own intelligence infrastructure introduces several deployment challenges.
|
||
Here are the practical lessons learned, organized by the layers of the agentic stack: Environment, Runtime, and Capabilities.
|
||
Layer 1: The Environment – Breaking the Sandbox Link to heading To move beyond being a chatbot, an agent needs to be able to affect its world. Deep integration starts with networking."><meta property="og:locale" content="en"><meta property="og:type" content="article"><meta property="article:section" content="posts"><meta property="article:published_time" content="2026-02-03T00:00:00+00:00"><meta property="article:modified_time" content="2026-02-04T06:18:45+00:00"><link rel=preload href=/fonts/fa-solid-900.woff2 as=font type=font/woff2 crossorigin><link rel=preload href=/fonts/fa-brands-400.woff2 as=font type=font/woff2 crossorigin><link rel=canonical href=https://ericxliu.me/posts/blog-draft/><link rel=preload href=/fonts/fa-brands-400.woff2 as=font type=font/woff2 crossorigin><link rel=preload href=/fonts/fa-regular-400.woff2 as=font type=font/woff2 crossorigin><link rel=preload href=/fonts/fa-solid-900.woff2 as=font type=font/woff2 crossorigin><link rel=stylesheet href=/css/coder.min.4b392a85107b91dbdabc528edf014a6ab1a30cd44cafcd5325c8efe796794fca.css integrity="sha256-SzkqhRB7kdvavFKO3wFKarGjDNRMr81TJcjv55Z5T8o=" crossorigin=anonymous media=screen><link rel=stylesheet href=/css/coder-dark.min.a00e6364bacbc8266ad1cc81230774a1397198f8cfb7bcba29b7d6fcb54ce57f.css integrity="sha256-oA5jZLrLyCZq0cyBIwd0oTlxmPjPt7y6KbfW/LVM5X8=" crossorigin=anonymous media=screen><link rel=icon type=image/svg+xml href=/images/favicon.svg sizes=any><link rel=icon type=image/png href=/images/favicon-32x32.png sizes=32x32><link rel=icon type=image/png href=/images/favicon-16x16.png sizes=16x16><link rel=apple-touch-icon href=/images/apple-touch-icon.png><link rel=apple-touch-icon sizes=180x180 href=/images/apple-touch-icon.png><link rel=manifest href=/site.webmanifest><link rel=mask-icon href=/images/safari-pinned-tab.svg color=#5bbad5><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3972604619956476" crossorigin=anonymous></script><script type=application/ld+json>{"@context":"http://schema.org","@type":"Person","name":"Eric X. Liu","url":"https:\/\/ericxliu.me\/","description":"Software \u0026 Performance Engineer at Google","sameAs":["https:\/\/www.linkedin.com\/in\/eric-x-liu-46648b93\/","https:\/\/git.ericxliu.me\/eric"]}</script><script type=application/ld+json>{"@context":"http://schema.org","@type":"BlogPosting","headline":"Deployment Lessons and My Take on Self-Hosting OpenClaw","genre":"Blog","wordcount":"844","url":"https:\/\/ericxliu.me\/posts\/blog-draft\/","datePublished":"2026-02-03T00:00:00\u002b00:00","dateModified":"2026-02-04T06:18:45\u002b00:00","description":"\u003cp\u003eDeploying autonomous agents like OpenClaw on a self-hosted Kubernetes cluster offers significantly more control and integration potential than cloud-hosted alternatives. However, moving from a standard SaaS model to running your own intelligence infrastructure introduces several deployment challenges.\u003c\/p\u003e\n\u003cp\u003eHere are the practical lessons learned, organized by the layers of the agentic stack: Environment, Runtime, and Capabilities.\u003c\/p\u003e\n\u003ch2 id=\u0022layer-1-the-environment--breaking-the-sandbox\u0022\u003e\n Layer 1: The Environment – Breaking the Sandbox\n \u003ca class=\u0022heading-link\u0022 href=\u0022#layer-1-the-environment--breaking-the-sandbox\u0022\u003e\n \u003ci class=\u0022fa-solid fa-link\u0022 aria-hidden=\u0022true\u0022 title=\u0022Link to heading\u0022\u003e\u003c\/i\u003e\n \u003cspan class=\u0022sr-only\u0022\u003eLink to heading\u003c\/span\u003e\n \u003c\/a\u003e\n\u003c\/h2\u003e\n\u003cp\u003eTo move beyond being a chatbot, an agent needs to be able to affect its world. Deep integration starts with networking.\u003c\/p\u003e","author":{"@type":"Person","name":"Eric X. Liu"}}</script></head><body class="preload-transitions colorscheme-auto"><div class=float-container><a id=dark-mode-toggle class=colorscheme-toggle><i class="fa-solid fa-adjust fa-fw" aria-hidden=true></i></a></div><main class=wrapper><nav class=navigation><section class=container><a class=navigation-title href=https://ericxliu.me/>Eric X. Liu's Personal Page
|
||
</a><input type=checkbox id=menu-toggle>
|
||
<label class="menu-button float-right" for=menu-toggle><i class="fa-solid fa-bars fa-fw" aria-hidden=true></i></label><ul class=navigation-list><li class=navigation-item><a class=navigation-link href=/posts/>Posts</a></li><li class=navigation-item><a class=navigation-link href=https://chat.ericxliu.me>Chat</a></li><li class=navigation-item><a class=navigation-link href=https://git.ericxliu.me/user/oauth2/Authenitk>Git</a></li><li class=navigation-item><a class=navigation-link href=https://coder.ericxliu.me/api/v2/users/oidc/callback>Coder</a></li><li class=navigation-item><a class=navigation-link href=/about/>About</a></li><li class=navigation-item><a class=navigation-link href=/>|</a></li><li class=navigation-item><a class=navigation-link href=https://sso.ericxliu.me>Sign in</a></li></ul></section></nav><div class=content><section class="container post"><article><header><div class=post-title><h1 class=title><a class=title-link href=https://ericxliu.me/posts/blog-draft/>Deployment Lessons and My Take on Self-Hosting OpenClaw</a></h1></div><div class=post-meta><div class=date><span class=posted-on><i class="fa-solid fa-calendar" aria-hidden=true></i>
|
||
<time datetime=2026-02-03T00:00:00Z>February 3, 2026
|
||
</time></span><span class=reading-time><i class="fa-solid fa-clock" aria-hidden=true></i>
|
||
4-minute read</span></div></div></header><div class=post-content><p>Deploying autonomous agents like OpenClaw on a self-hosted Kubernetes cluster offers significantly more control and integration potential than cloud-hosted alternatives. However, moving from a standard SaaS model to running your own intelligence infrastructure introduces several deployment challenges.</p><p>Here are the practical lessons learned, organized by the layers of the agentic stack: Environment, Runtime, and Capabilities.</p><h2 id=layer-1-the-environment--breaking-the-sandbox>Layer 1: The Environment – Breaking the Sandbox
|
||
<a class=heading-link href=#layer-1-the-environment--breaking-the-sandbox><i class="fa-solid fa-link" aria-hidden=true title="Link to heading"></i>
|
||
<span class=sr-only>Link to heading</span></a></h2><p>To move beyond being a chatbot, an agent needs to be able to affect its world. Deep integration starts with networking.</p><p>Code execution agents often need to spin up temporary servers—for previews, React apps, or documentation sites. In a standard Kubernetes Pod, these dynamic ports (like 3000, 8080, etc.) are isolated inside the container network namespace.</p><p>To securely expose these arbitrary ports, I deployed a lightweight <strong>Nginx sidecar</strong> alongside the main OpenClaw agent. This avoids the complexity and latency of dynamically updating Ingress resources.</p><p>The Nginx configuration handling the routing logic:</p><div class=highlight><pre tabindex=0 style=color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:#a5d6ff>server {</span><span style=color:#6e7681>
|
||
</span></span></span><span style=display:flex><span><span style=color:#6e7681> </span><span style=color:#a5d6ff>listen 80;</span><span style=color:#6e7681>
|
||
</span></span></span><span style=display:flex><span><span style=color:#6e7681> </span><span style=color:#a5d6ff>server_name ~^(?<port>\d+)\.agent\.mydomain\.com$;</span><span style=color:#6e7681>
|
||
</span></span></span><span style=display:flex><span><span style=color:#6e7681>
|
||
</span></span></span><span style=display:flex><span><span style=color:#6e7681> </span><span style=color:#a5d6ff>location / {</span><span style=color:#6e7681>
|
||
</span></span></span><span style=display:flex><span><span style=color:#6e7681> </span><span style=color:#a5d6ff>proxy_pass http://localhost:$port;</span><span style=color:#6e7681>
|
||
</span></span></span><span style=display:flex><span><span style=color:#6e7681> </span><span style=color:#a5d6ff>proxy_set_header Host $host;</span><span style=color:#6e7681>
|
||
</span></span></span><span style=display:flex><span><span style=color:#6e7681> </span>}<span style=color:#6e7681>
|
||
</span></span></span><span style=display:flex><span>}<span style=color:#6e7681>
|
||
</span></span></span></code></pre></div><p>This configuration uses a regex-based server block to capture the port from the subdomain (e.g., <code>3000.agent.mydomain.com</code>) and proxies traffic to that port on <code>localhost</code>. Since containers in the same Pod share a network namespace, <code>localhost</code> connectivity is seamless.</p><p>For this to work effectively, the agent must be aware of its environment. I updated OpenClaw’s system prompts to understand this pattern: <em>“If you start a server on port X, the external URL is <a href=https://X.agent.mydomain.com class=external-link target=_blank rel=noopener>https://X.agent.mydomain.com</a>”</em>. This allows the agent to provide valid, clickable links for its generated applications.</p><h2 id=layer-2-the-runtime--agility-and-persistence>Layer 2: The Runtime – Agility and Persistence
|
||
<a class=heading-link href=#layer-2-the-runtime--agility-and-persistence><i class="fa-solid fa-link" aria-hidden=true title="Link to heading"></i>
|
||
<span class=sr-only>Link to heading</span></a></h2><p>Once the agent allows for external connectivity, the next challenge is agility. Self-hosting often requires customizations that haven’t yet been merged upstream.</p><p>Self-hosting often requires customizations that haven’t yet been merged upstream. For example, I needed a custom OAuth flow for Google’s internal APIs.</p><p>Instead of maintaining a forked Docker image, I used a Kubernetes <code>ConfigMap</code> to inject the necessary TypeScript plugin at runtime. The file is mounted directly into the container at <code>/app/extensions/google-antigravity-auth/index.ts</code>.</p><div class=highlight><pre tabindex=0 style=color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:#7ee787>kind</span>:<span style=color:#6e7681> </span><span style=color:#a5d6ff>ConfigMap</span><span style=color:#6e7681>
|
||
</span></span></span><span style=display:flex><span><span style=color:#7ee787>metadata</span>:<span style=color:#6e7681>
|
||
</span></span></span><span style=display:flex><span><span style=color:#6e7681> </span><span style=color:#7ee787>name</span>:<span style=color:#6e7681> </span><span style=color:#a5d6ff>openclaw-patch-antigravity</span><span style=color:#6e7681>
|
||
</span></span></span><span style=display:flex><span><span style=color:#7ee787>data</span>:<span style=color:#6e7681>
|
||
</span></span></span><span style=display:flex><span><span style=color:#6e7681> </span><span style=color:#7ee787>index.ts</span>:<span style=color:#6e7681> </span>|<span style=color:#a5d6ff>
|
||
</span></span></span><span style=display:flex><span><span style=color:#a5d6ff> import { createHash, randomBytes } from "node:crypto";
|
||
</span></span></span><span style=display:flex><span><span style=color:#a5d6ff> // ... custom OAuth implementation ...
|
||
</span></span></span><span style=display:flex><span><span style=color:#a5d6ff> export default antigravityPlugin;</span><span style=color:#6e7681>
|
||
</span></span></span></code></pre></div><p>This approach allows for rapid iteration on patches without rebuilding container images for every change.</p><p>However, two operational realities became clear during this process:</p><ol><li><strong>Debugging is Standard</strong>: When the agent fails (e.g., your custom patch throws an error), it behaves like any other application. Standard debugging tools like <code>kubectl logs</code> and <code>strace</code> remain the most effective way to diagnose issues.</li><li><strong>Persistent Storage Matches Tooling</strong>: Just as code needs injection, tools need persistence. I had to explicitly mount a volume for Homebrew (<code>.linuxbrew</code>) so that tools installed by me or the agent didn’t vanish on pod restart. Agents need long-term memory on their filesystem as much as in their context window.</li></ol><h2 id=layer-3-the-capabilities--skills-over-abstractions>Layer 3: The Capabilities – Skills over Abstractions
|
||
<a class=heading-link href=#layer-3-the-capabilities--skills-over-abstractions><i class="fa-solid fa-link" aria-hidden=true title="Link to heading"></i>
|
||
<span class=sr-only>Link to heading</span></a></h2><p>With the infrastructure (Layer 1) and runtime (Layer 2) established, we move to the application logic: how the agent actually <em>does</em> work.</p><p>While the industry chases complex abstractions like the Model Context Protocol (MCP), I found that simple, text-based “Skills” offer a superior workflow. I recently created a Gitea skill simply by exposing the <code>tea</code> CLI documentation to the agent.</p><p>This approach aligns with the UNIX philosophy: small, simple tools that do one thing well. MCP servers often clutter the context window and impose significant development overhead. A well-structured “Skill”—essentially a localized knowledge base for a CLI—is cleaner and faster to implement. I predict that these lightweight Skills will eventually replace heavy MCP integrations for the majority of use cases.</p><p>There is one current limitation: Gemini models lack specific post-training for these custom skills. The agent doesn’t always intuitively know when to reach for a specific tool. Also, remember that granting the agent access to CLI tools like <code>kubectl</code> or <code>tea</code> (Gitea CLI) enables it to perform operations directly, transforming it from a text generator to a system operator. My agent can now open Pull Requests on my self-hosted Gitea instance, effectively becoming a contributor to its own config repo.</p><h2 id=the-payoff-why-this-complexity-matters>The Payoff: Why This Complexity Matters
|
||
<a class=heading-link href=#the-payoff-why-this-complexity-matters><i class="fa-solid fa-link" aria-hidden=true title="Link to heading"></i>
|
||
<span class=sr-only>Link to heading</span></a></h2><p>Why go through this trouble of sidecars, config patches, and custom skills?</p><p>My previous AI workflows relied on standard chatbots via interfaces like Open-WebUI. The friction in that model is the “all-or-nothing” generation. LLMs are stochastic; regenerating an entire file to change three lines is inefficient and risky.</p><p>OpenClaw’s or agentic tools (such as Cursor or Antigravity) killer feature is <strong>partial editing</strong>. The ability to iteratively improve a stable codebase or document without regenerating the entire file is the missing link for AI-assisted development. We need to treat code as a living document, not a chat response.</p><p>When combined with tools like Obsidian that I already use as my second brain for persistent knowledge management, this model provides both the long-term memory and the granular control necessary for complex projects.</p><h2 id=references>References
|
||
<a class=heading-link href=#references><i class="fa-solid fa-link" aria-hidden=true title="Link to heading"></i>
|
||
<span class=sr-only>Link to heading</span></a></h2><ol><li><strong>OpenClaw Documentation</strong>: <a href=https://docs.openclaw.org class=external-link target=_blank rel=noopener>https://docs.openclaw.org</a></li><li><strong>Kubernetes Flux CD</strong>: <a href=https://fluxcd.io/ class=external-link target=_blank rel=noopener>https://fluxcd.io/</a></li><li><strong>Nginx Regex Server Names</strong>: <a href=https://nginx.org/en/docs/http/server_names.html class=external-link target=_blank rel=noopener>https://nginx.org/en/docs/http/server_names.html</a></li></ol></div><footer><div id=disqus_thread></div><script>window.disqus_config=function(){},function(){if(["localhost","127.0.0.1"].indexOf(window.location.hostname)!=-1){document.getElementById("disqus_thread").innerHTML="Disqus comments not available by default when the website is previewed locally.";return}var t=document,e=t.createElement("script");e.async=!0,e.src="//ericxliu-me.disqus.com/embed.js",e.setAttribute("data-timestamp",+new Date),(t.head||t.body).appendChild(e)}(),document.addEventListener("themeChanged",function(){document.readyState=="complete"&&DISQUS.reset({reload:!0,config:disqus_config})})</script></footer></article><link rel=stylesheet href=https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css integrity=sha384-vKruj+a13U8yHIkAyGgK1J3ArTLzrFGBbBc0tDp4ad/EyewESeXE/Iv67Aj8gKZ0 crossorigin=anonymous><script defer src=https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.js integrity=sha384-PwRUT/YqbnEjkZO0zZxNqcxACrXe+j766U2amXcgMg5457rve2Y7I6ZJSm2A0mS4 crossorigin=anonymous></script><script defer src=https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/contrib/auto-render.min.js integrity=sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05 crossorigin=anonymous onload='renderMathInElement(document.body,{delimiters:[{left:"$$",right:"$$",display:!0},{left:"$",right:"$",display:!1},{left:"\\(",right:"\\)",display:!1},{left:"\\[",right:"\\]",display:!0}]})'></script></section></div><footer class=footer><section class=container>©
|
||
2016 -
|
||
2026
|
||
Eric X. Liu
|
||
<a href="https://git.ericxliu.me/eric/ericxliu-me/commit/8aa06e9">[8aa06e9]</a></section></footer></main><script src=/js/coder.min.6ae284be93d2d19dad1f02b0039508d9aab3180a12a06dcc71b0b0ef7825a317.js integrity="sha256-auKEvpPS0Z2tHwKwA5UI2aqzGAoSoG3McbCw73gloxc="></script><script defer src=https://static.cloudflareinsights.com/beacon.min.js data-cf-beacon='{"token": "987638e636ce4dbb932d038af74c17d1"}'></script></body></html> |