<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Dockerfile on Alfero Chingono</title><link>https://www.chingono.com/tags/dockerfile/</link><description>Recent content in Dockerfile on Alfero Chingono</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Sat, 04 Apr 2026 23:18:06 -0400</lastBuildDate><atom:link href="https://www.chingono.com/tags/dockerfile/index.xml" rel="self" type="application/rss+xml"/><item><title>Inside the Dockerfile Behind My OpenClaw Gateway</title><link>https://www.chingono.com/blog/2026/03/15/inside-the-dockerfile-behind-my-openclaw-gateway/</link><pubDate>Sun, 15 Mar 2026 17:00:00 +0000</pubDate><guid>https://www.chingono.com/blog/2026/03/15/inside-the-dockerfile-behind-my-openclaw-gateway/</guid><description>&lt;img src="https://www.chingono.com/blog/2026/03/15/inside-the-dockerfile-behind-my-openclaw-gateway/cover.png" alt="Featured image of post Inside the Dockerfile Behind My OpenClaw Gateway" /&gt;&lt;p&gt;The first two posts in this series covered the Docker decision and the Signal/Teams channel integration. This one gets into the Dockerfile itself.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not very long, but it&amp;rsquo;s doing more architectural work than its size suggests.&lt;/p&gt;
&lt;h2 id="i-started-from-the-openclaw-base-image-on-purpose"&gt;I started from the OpenClaw base image on purpose
&lt;/h2&gt;&lt;p&gt;The first line matters:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ghcr.io/openclaw/openclaw:latest&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;I didn&amp;rsquo;t want to rebuild OpenClaw from scratch if I didn&amp;rsquo;t have to.&lt;/p&gt;
&lt;p&gt;The base image already gets me the core runtime. What I needed was an opinionated extension of that runtime for my own environment. So this Dockerfile is less &amp;ldquo;build a platform from zero&amp;rdquo; and more &amp;ldquo;declare the exact operational additions my setup needs.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;That distinction keeps the file focused.&lt;/p&gt;
&lt;h2 id="the-version-arguments-make-the-customization-explicit"&gt;The version arguments make the customization explicit
&lt;/h2&gt;&lt;p&gt;Very early in the file I keep a couple of version arguments:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ARG&lt;/span&gt; &lt;span class="nv"&gt;PNPM_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;.15.6&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ARG&lt;/span&gt; &lt;span class="nv"&gt;SIGNALCLI_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.14.1&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;I do this even when not every argument is fully threaded through every install step yet.&lt;/p&gt;
&lt;p&gt;It makes the additions feel intentional instead of accidental, and it gives me a clean place to pin or update them over time without pretending the whole image is fully static.&lt;/p&gt;
&lt;h2 id="i-keep-extra-node-runtime-pieces-out-of-the-main-install-path"&gt;I keep extra Node runtime pieces out of the main install path
&lt;/h2&gt;&lt;p&gt;This block is one of the more important small decisions in the file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;EXTRA_NODE_MODULES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/node/.openclaw-extra/node_modules &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;NODE_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/node/.openclaw-extra/node_modules&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;That extra module path is how I keep the runtime additions separated from the base image&amp;rsquo;s own install layout.&lt;/p&gt;
&lt;p&gt;I like this because it keeps things layered:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;base image provides OpenClaw&lt;/li&gt;
&lt;li&gt;extra module path provides my environment-specific additions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;rsquo;s cleaner than pretending I own the whole upstream install tree.&lt;/p&gt;
&lt;h2 id="the-root-phase-is-for-system-level-capability"&gt;The root phase is for system-level capability
&lt;/h2&gt;&lt;p&gt;After switching to &lt;code&gt;root&lt;/code&gt;, the file installs the system packages the stack actually needs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;apt-get install -y --no-install-recommends &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; curl &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; jq &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ca-certificates &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; gpg &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; openssh-client &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; libstdc++6 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; zlib1g &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; libgcc-s1&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This section isn&amp;rsquo;t glamorous, but it matters.&lt;/p&gt;
&lt;p&gt;These are the packages that make the runtime actually usable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;curl&lt;/code&gt; and &lt;code&gt;jq&lt;/code&gt; for scripting and diagnostics&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gpg&lt;/code&gt; and certificate tooling for package installs&lt;/li&gt;
&lt;li&gt;&lt;code&gt;openssh-client&lt;/code&gt; because the agent works with repositories and tunnels&lt;/li&gt;
&lt;li&gt;the runtime libraries needed by installed binaries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I try hard not to let this layer become a junk drawer. If a package is there, I want to be able to explain why it exists.&lt;/p&gt;
&lt;h2 id="github-cli-belongs-in-the-image-because-the-agent-actually-uses-it"&gt;GitHub CLI belongs in the image because the agent actually uses it
&lt;/h2&gt;&lt;p&gt;I also install &lt;code&gt;gh&lt;/code&gt; in the image.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s one of those choices that looks unnecessary until you remember what OpenClaw is actually doing here. It&amp;rsquo;s not just chatting. It&amp;rsquo;s working with repositories, issues, and pull requests. I wanted the GitHub CLI available in the environment the agent actually runs in, not as some optional extra on the host.&lt;/p&gt;
&lt;p&gt;That keeps the runtime consistent between the gateway and the CLI container, which matters.&lt;/p&gt;
&lt;h2 id="signal-cli-is-not-an-afterthought"&gt;Signal CLI is not an afterthought
&lt;/h2&gt;&lt;p&gt;The Signal section is the clearest example of why this had to be a custom image:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt; -eux&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mkdir -p /etc/apt/keyrings&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; curl -fsSL https://packaging.gitlab.io/signal-cli/gpg.key &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; gpg --dearmor -o /etc/apt/keyrings/signal-cli.gpg&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;deb [signed-by=/etc/apt/keyrings/signal-cli.gpg] https://packaging.gitlab.io/signal-cli signalcli main&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;gt; /etc/apt/sources.list.d/signal-cli.list&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; apt-get update&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; apt-get install -y --no-install-recommends signal-cli-native&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; rm -rf /var/lib/apt/lists/*&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; signal-cli --version&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;I wanted Signal to be a first-class runtime capability, not a hand-installed special case.&lt;/p&gt;
&lt;p&gt;Once it&amp;rsquo;s in the image, I know exactly where it comes from and which container owns it. Then I can mount the Signal state directory separately and let rebuilds stay rebuilds instead of becoming identity-loss events.&lt;/p&gt;
&lt;h2 id="permissions-matter-more-than-people-think"&gt;Permissions matter more than people think
&lt;/h2&gt;&lt;p&gt;Before dropping privileges again, the Dockerfile prepares the runtime directories and fixes ownership for the non-root &lt;code&gt;node&lt;/code&gt; user.&lt;/p&gt;
&lt;p&gt;That kind of step is easy to skip when you&amp;rsquo;re just trying to get it running. It&amp;rsquo;s also the kind of step that bites you later when mounted state or runtime-generated files start colliding with user permissions.&lt;/p&gt;
&lt;p&gt;I would rather be explicit here.&lt;/p&gt;
&lt;h2 id="the-pnpmcorepack-step-has-to-happen-before-switching-users"&gt;The pnpm/corepack step has to happen before switching users
&lt;/h2&gt;&lt;p&gt;This is one of the details that looks small but is operationally important:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; corepack &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; corepack prepare pnpm@&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PNPM_VERSION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; --activate&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;I do that while still running as &lt;code&gt;root&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s deliberate. Those paths need write access, and I don&amp;rsquo;t want to debug avoidable permission problems later. This is exactly the kind of detail that turns a Dockerfile from &amp;ldquo;technically valid&amp;rdquo; into &amp;ldquo;actually maintainable.&amp;rdquo;&lt;/p&gt;
&lt;h2 id="i-switch-back-to-node-for-runtime-behavior"&gt;I switch back to &lt;code&gt;node&lt;/code&gt; for runtime behavior
&lt;/h2&gt;&lt;p&gt;Once the system-level setup is done, the file drops back to the non-root user and stays there for the runtime-facing steps.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the right default here. The container needs enough privilege to install what it needs at build time, but it doesn&amp;rsquo;t need to run the application as root.&lt;/p&gt;
&lt;h2 id="the-teams-specific-node-addition-lives-in-the-extra-module-path"&gt;The Teams-specific Node addition lives in the extra module path
&lt;/h2&gt;&lt;p&gt;The final runtime customization is this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; pnpm add &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --dir /home/node/.openclaw-extra &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --save-exact &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; @microsoft/agents-hosting@1.3.1&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;That one line pretty much summarizes the whole Dockerfile philosophy.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not forking OpenClaw. I&amp;rsquo;m not replacing the base image. I&amp;rsquo;m adding the exact extra runtime capability my environment needs, in a separate path, with a pinned version, after the base runtime is already in place.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the kind of extension model I trust more.&lt;/p&gt;
&lt;h2 id="the-file-is-short-because-i-wanted-it-to-stay-inspectable"&gt;The file is short because I wanted it to stay inspectable
&lt;/h2&gt;&lt;p&gt;I could have packed more into this image.&lt;/p&gt;
&lt;p&gt;I could have added more helpers, more debugging tools, more convenience packages, more &amp;ldquo;while I&amp;rsquo;m here&amp;rdquo; installations.&lt;/p&gt;
&lt;p&gt;I chose not to.&lt;/p&gt;
&lt;p&gt;For this kind of system, I think a Dockerfile should be easy to read top to bottom and answer one question: &lt;strong&gt;what does this runtime need that upstream doesn&amp;rsquo;t already provide?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In my case, the answer was:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a few system tools&lt;/li&gt;
&lt;li&gt;GitHub CLI&lt;/li&gt;
&lt;li&gt;Signal CLI&lt;/li&gt;
&lt;li&gt;an extra Node path&lt;/li&gt;
&lt;li&gt;pnpm-managed Teams hosting support&lt;/li&gt;
&lt;li&gt;correct ownership and runtime defaults&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is enough.&lt;/p&gt;
&lt;p&gt;Honestly, &amp;ldquo;enough&amp;rdquo; is one of the healthiest instincts you can have when building infrastructure for yourself.&lt;/p&gt;
&lt;p&gt;Next in the series: &lt;a class="link" href="https://www.chingono.com/blog/2026/04/03/how-i-split-openclaw-into-main-and-personal-agents/" &gt;How I Split OpenClaw into Main and Personal Agents&lt;/a&gt;.&lt;/p&gt;</description></item></channel></rss>