Files
ericxliu-me/posts/blog-draft/index.html
2026-02-22 20:31:06 +00:00

50 lines
21 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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 ~^(?&lt;port&gt;\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&rsquo;s system prompts to understand this pattern: <em>&ldquo;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>&rdquo;</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&rsquo;t yet been merged upstream.</p><p>Self-hosting often requires customizations that haven&rsquo;t yet been merged upstream. For example, I needed a custom OAuth flow for Google&rsquo;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 &#34;node:crypto&#34;;
</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&rsquo;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 &ldquo;Skills&rdquo; 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 &ldquo;Skill&rdquo;—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&rsquo;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 &ldquo;all-or-nothing&rdquo; generation. LLMs are stochastic; regenerating an entire file to change three lines is inefficient and risky.</p><p>OpenClaw&rsquo;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>