<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Zach Cutler's Tech Blog</title>
        <link>https://zachcutler.me/blog</link>
        <description>Discoveries, projects, and knowledge sharing from a software developer.</description>
        <lastBuildDate>Wed, 18 Mar 2026 07:49:31 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>Copyright © 2026 Zach Cutler</copyright>
        <item>
            <title><![CDATA[Hosting Multiple OpenClaw Agents with Docker]]></title>
            <link>https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker</link>
            <guid>https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker</guid>
            <pubDate>Wed, 18 Mar 2026 07:49:31 GMT</pubDate>
            <description><![CDATA[How I set up multiple isolated OpenClaw instances on a single Ubuntu VPS using Docker, nginx, and certbot.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="OpenClaw hero" src="https://zachcutler.me/assets/images/openclaw-hero-b317c3daa0d98641c4c019d404fbbede.png" width="645" height="360" class="img_ev3q"></p>
<p><a href="https://openclaw.ai/" target="_blank" rel="noopener noreferrer" class="">OpenClaw</a> is the hot new thing in the world of AI and tech. It's a fairly open ended tool that allows you to run a personalized AI agent on your own hardware. It can run command line tools, perform web searches, write code, run desktop apps, and perform scheduled tasks, making it an excellent personal aid. All of that is paired with its extensive communication integrations, which allow you to chat with and control it through almost any messaging app.</p>
<p>OpenClaw is designed to be more than another AI agent. The fact that it can run your desktop empowers both you and it to do more than a standard cloud-based AI tool. As you'd expect, however, that comes with huge security implications.</p>
<p>I've opted not to run OpenClaw on any of my personal hardware. Instead, I'm trying to use it by giving it segmented virtual workspaces that it can own and operate within. This persistent workspace, along with the flexibility in tools I'll give it, makes it more useful for asynchronous tasks than current cloud offerings.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="before-we-begin">Before We Begin<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#before-we-begin" class="hash-link" aria-label="Direct link to Before We Begin" title="Direct link to Before We Begin" translate="no">​</a></h2>
<p>There's a lot of ways to setup OpenClaw and how I'm doing it is not considered the normal pattern. It focuses on segmentation, not strict isolation. This is also not a guide in how to configure OpenClaw, just how to get it running. I'd recommend anyone reading this to have familiarized themselves with what OpenClaw is capable of and the various ways it can be created and configured. They have <a href="https://docs.openclaw.ai/start/getting-started" target="_blank" rel="noopener noreferrer" class="">easy to follow setup guides</a> on their website.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="infrastructure">Infrastructure<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#infrastructure" class="hash-link" aria-label="Direct link to Infrastructure" title="Direct link to Infrastructure" translate="no">​</a></h3>
<p>I'm configuring this on a <a href="https://www.digitalocean.com/pricing/droplets" target="_blank" rel="noopener noreferrer" class="">DigitalOcean 2GB/2vCPU droplet</a> running <a href="https://ubuntu.com/" target="_blank" rel="noopener noreferrer" class="">Ubuntu</a> 24 LTS; which I plan on hosting multiple isolated OpenClaw instances on via <a href="https://www.docker.com/" target="_blank" rel="noopener noreferrer" class="">Docker</a>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="terms-to-know">Terms to Know<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#terms-to-know" class="hash-link" aria-label="Direct link to Terms to Know" title="Direct link to Terms to Know" translate="no">​</a></h3>
<p>I've tried to keep this guide abstract but at times I may reference my own file paths, usernames, etc. as I document this. Know that <code>zach</code>, <code>lod</code>, <code>lodsoftworks</code>, <code>lod-softworks</code>, and <code>zachcutler.me</code> reflect my person and organization and should be replaced with your own information.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ubuntu-setup">Ubuntu Setup<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#ubuntu-setup" class="hash-link" aria-label="Direct link to Ubuntu Setup" title="Direct link to Ubuntu Setup" translate="no">​</a></h2>
<p>I'm starting with a fresh droplet so we'll need to do some basic OS management. If you've already got a host setup you can skip to the next section.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="local-user-setup">Local User Setup<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#local-user-setup" class="hash-link" aria-label="Direct link to Local User Setup" title="Direct link to Local User Setup" translate="no">​</a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="create-new-admin-user">Create New Admin User<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#create-new-admin-user" class="hash-link" aria-label="Direct link to Create New Admin User" title="Direct link to Create New Admin User" translate="no">​</a></h4>
<p>Run the following commands to create a new user and copy the existing SSH keys (if preconfigured for root, otherwise create them) to the new user's SSH directory.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Create login user (admin/sudo)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">adduser myusername</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">usermod</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-aG</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> myusername</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Copy SSH keys - If these were not added by a hosting provider (i.e. DigitalOcean) you may just need to create them in the proper directory.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">rsync</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--archive</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--chown</span><span class="token operator">=</span><span class="token plain">myusername:myusername ~/.ssh /home/myusername</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">chmod</span><span class="token plain"> </span><span class="token number">700</span><span class="token plain"> /home/myusername/.ssh</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">chmod</span><span class="token plain"> </span><span class="token number">600</span><span class="token plain"> /home/myusername/.ssh/authorized_keys</span><br></span></code></pre></div></div>
<p>⚠️ Verify the new user can login using a 2nd shell session before continuing further and that they have <code>sudo</code> permissions.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Verify user = myusername</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">whoami</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Verify user = root</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">whoami</span><br></span></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="disable-root-user">Disable Root User<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#disable-root-user" class="hash-link" aria-label="Direct link to Disable Root User" title="Direct link to Disable Root User" translate="no">​</a></h4>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Lock the root user using the -l (L for lock) flag</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">passwd</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-l</span><span class="token plain"> root</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="network-security">Network Security<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#network-security" class="hash-link" aria-label="Direct link to Network Security" title="Direct link to Network Security" translate="no">​</a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="secure-ssh">Secure SSH<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#secure-ssh" class="hash-link" aria-label="Direct link to Secure SSH" title="Direct link to Secure SSH" translate="no">​</a></h4>
<p>Modify the SSH config to disable root login and require both public key + password authention. The file is large but look for these values, ensure they are uncommented and set to the correct values.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">vim</span><span class="token plain"> /etc/ssh/sshd_config</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Disables root login</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">PermitRootLogin no</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Enables Password &amp; Public Key auth</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">PasswordAuthentication </span><span class="token function" style="color:rgb(80, 250, 123)">yes</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">PubkeyAuthentication </span><span class="token function" style="color:rgb(80, 250, 123)">yes</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">UsePAM </span><span class="token function" style="color:rgb(80, 250, 123)">yes</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Sets the authentication mode to require both password &amp; publickey</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">AuthenticationMethods publickey,password</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">⚠️ There may be override files that explicitly change these settings </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">i.e. </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">`</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">/etc/ssh/sshd_config.d/50-cloud-init.conf</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">`</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> that may need to be changed as well.</span><br></span></code></pre></div></div>
<p>Then verify the configuration is valid</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Will generate a message if there is an invalid config, no response is a good response.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> sshd </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-t</span><br></span></code></pre></div></div>
<p>Reboot the system (or restart the ssh service) and login as the new user 😀</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="enable-firewall">Enable firewall<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#enable-firewall" class="hash-link" aria-label="Direct link to Enable firewall" title="Direct link to Enable firewall" translate="no">​</a></h4>
<p>Configure the default firewall to only allow SSH &amp; HTTP connections, then enable it.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> ufw default deny incoming</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> ufw default allow outgoing</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> ufw allow </span><span class="token number">22</span><span class="token plain">/tcp</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> ufw allow </span><span class="token number">80</span><span class="token plain">/tcp</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> ufw allow </span><span class="token number">443</span><span class="token plain">/tcp</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> ufw </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">enable</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> ufw status verbose</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> ufw status numbered</span><br></span></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="enable-fail2ban">Enable fail2ban<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#enable-fail2ban" class="hash-link" aria-label="Direct link to Enable fail2ban" title="Direct link to Enable fail2ban" translate="no">​</a></h4>
<p>Configure fail2ban, a service which monitors network activity for bad actors (via logs, journals, etc) and can perform network bans on IPs and what not.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Install fail2ban</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">apt</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> fail2ban</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Ensure the install started the service</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> systemctl status fail2ban</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> fail2ban-client </span><span class="token function" style="color:rgb(80, 250, 123)">ping</span><br></span></code></pre></div></div>
<p>Create a new config override file to start adding our rules to.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Default config is /etc/fail2ban/jail.conf and should not be modified</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">vim</span><span class="token plain"> /etc/fail2ban/jail.local</span><br></span></code></pre></div></div>
<div class="language-ini codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ini codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">[DEFAULT]</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">bantime = 1h</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">findtime = 10m</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">maxretry = 5</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">backend = systemd</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">banaction = ufw</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">[sshd]</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">enabled = true</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">port = 22</span><br></span></code></pre></div></div>
<p>Restart and enable the service and confirm the configuration has been loaded.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Restart the service to load config overrides</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> systemctl restart fail2ban</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Start banning based on rules</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> systemctl </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">enable</span><span class="token plain"> fail2ban</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Ensure things look good</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> fail2ban-client </span><span class="token function" style="color:rgb(80, 250, 123)">ping</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> fail2ban-client status</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> fail2ban-client status sshd</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Ensure override values were loaded</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> fail2ban-client get sshd bantime</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> fail2ban-client get sshd findtime</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> fail2ban-client get sshd maxretry</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="system-updates">System Updates<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#system-updates" class="hash-link" aria-label="Direct link to System Updates" title="Direct link to System Updates" translate="no">​</a></h3>
<p>Setup automatic system/security updates so the OS doesn't become outdated or vulnerable.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">apt</span><span class="token plain"> update</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">apt</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-y</span><span class="token plain"> unattended-upgrades</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> dpkg-reconfigure </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-plow</span><span class="token plain"> unattended-upgrades</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="docker-setup">Docker Setup<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#docker-setup" class="hash-link" aria-label="Direct link to Docker Setup" title="Direct link to Docker Setup" translate="no">​</a></h3>
<p>Ensure necessary dependencies and packages are installed per <a href="https://docs.docker.com/engine/install/ubuntu/" target="_blank" rel="noopener noreferrer" class="">Docker's official documentation</a>.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Refresh package lists</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">apt</span><span class="token plain"> update</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Remove any existing docker packages (from legacy sources)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">apt</span><span class="token plain"> remove </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-y</span><span class="token plain"> docker.io docker-doc </span><span class="token function" style="color:rgb(80, 250, 123)">docker-compose</span><span class="token plain"> docker-compose-v2 podman-docker containerd runc</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Install packages required to configure docker's apt source</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">apt</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-y</span><span class="token plain"> ca-certificates </span><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Ensure the keyrings directory exists and download docker's apt signing key</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-m</span><span class="token plain"> 0755 </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> /etc/apt/keyrings</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-fsSL</span><span class="token plain"> https://download.docker.com/linux/ubuntu/gpg </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-o</span><span class="token plain"> /etc/apt/keyrings/docker.asc</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">chmod</span><span class="token plain"> a+r /etc/apt/keyrings/docker.asc</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Add the docker source to apt</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"deb [arch=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">dpkg --print-architecture</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)"> signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable builtin class-name" style="color:rgb(189, 147, 249);font-style:italic">.</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> /etc/os-release </span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">&amp;&amp;</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token string variable builtin class-name" style="color:rgb(189, 147, 249);font-style:italic">echo</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token string variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">\</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">"$VERSION_CODENAME</span><span class="token string variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">\</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)"> stable"</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">tee</span><span class="token plain"> /etc/apt/sources.list.d/docker.list </span><span class="token operator">&gt;</span><span class="token plain"> /dev/null</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Refresh package lists (with new source)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">apt</span><span class="token plain"> update</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Install docker packages</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">apt</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-y</span><span class="token plain"> docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Verify installation &amp; service startup</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> version</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> compose version</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> systemctl status </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><br></span></code></pre></div></div>
<p>Update dockers logging configuration to support small file sizes with roll overs.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">vim</span><span class="token plain"> /etc/docker/daemon.json</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Choose their default driver</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"log-driver"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"local"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Or use a custom driver config</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"log-driver"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"json-file"</span><span class="token plain">,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"log-opts"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"max-size"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"10m"</span><span class="token plain">,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"max-file"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"3"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>Restart docker to pick up on the config changes</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> systemctl restart </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> systemctl status </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="prerequisite-setup">Prerequisite Setup<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#prerequisite-setup" class="hash-link" aria-label="Direct link to Prerequisite Setup" title="Direct link to Prerequisite Setup" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="prepare-nginx">Prepare nginx<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#prepare-nginx" class="hash-link" aria-label="Direct link to Prepare nginx" title="Direct link to Prepare nginx" translate="no">​</a></h3>
<p>Nginx will be our only publicly networked service; it'll reverse proxy into our OpenClaw containers and other services. We'll use certbot to create and renew TLS/SSL certificates for our agent domains.</p>
<p>First we'll create all of the non-agent specific directories we're going to need.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Create nginx directories</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">mkdir</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-p</span><span class="token plain"> /opt/claws/nginx/</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">conf,logs,sites,certbot/www,certbot/conf</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>Next we'll create our initial nginx config with a default fall-through/catch-all server that returns 404's for requests that don't match an agent.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">vim</span><span class="token plain"> /opt/claws/nginx/conf/default.conf</span><br></span></code></pre></div></div>
<div class="language-nginx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-nginx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain"># This should be above your server entries</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">map $http_upgrade $connection_upgrade {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    default upgrade;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    ''      close;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">}</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># This should always be the last server entry</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">server {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    listen 80 default_server;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    server_name _;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    return 404;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">}</span><br></span></code></pre></div></div>
<p>Validate configs before reloads:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exec</span><span class="token plain"> nginx-edge nginx </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-t</span><br></span></code></pre></div></div>
<p>Finally we'll create our Docker compose file which will eventually contain all of our nginx and OpenClaw containers.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">vim</span><span class="token plain"> /opt/claws/nginx/compose.yml</span><br></span></code></pre></div></div>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">services</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">nginx</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">image</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> nginx</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">stable</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">container_name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> nginx</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">edge</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">restart</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> unless</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">stopped</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">ports</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"80:80"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"443:443"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">volumes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> /opt/claws/nginx/conf/default.conf</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">/etc/nginx/conf.d/default.conf</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">ro</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> /opt/claws/nginx/logs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">/var/log/nginx</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> /opt/claws/nginx/certbot/www</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">/var/www/certbot</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">ro</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> /opt/claws/nginx/certbot/conf</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">/etc/letsencrypt</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">ro</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="automate-certificate-renewal">Automate certificate renewal<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#automate-certificate-renewal" class="hash-link" aria-label="Direct link to Automate certificate renewal" title="Direct link to Automate certificate renewal" translate="no">​</a></h3>
<p>We'll be using certbot for TLS/SSL certificates, the free certificates have a 90 day expiration so we need to configure certificates to auto-renew. We'll do this by creating a bash script and a cron job.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Create the bash script</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">vim</span><span class="token plain"> /opt/claws/renew-certs.sh</span><br></span></code></pre></div></div>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token shebang important">#!/usr/bin/env bash</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">set</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-euo</span><span class="token plain"> pipefail</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> run </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--rm</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-v</span><span class="token plain"> /opt/claws/nginx/certbot/www:/var/www/certbot </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-v</span><span class="token plain"> /opt/claws/nginx/certbot/conf:/etc/letsencrypt </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  certbot/certbot renew </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--webroot</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  --webroot-path</span><span class="token operator">=</span><span class="token plain">/var/www/certbot</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exec</span><span class="token plain"> nginx-edge nginx </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-s</span><span class="token plain"> reload</span><br></span></code></pre></div></div>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Make the script executable (permissions)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">chmod</span><span class="token plain"> +x /opt/claws/renew-certs.sh</span><br></span></code></pre></div></div>
<p>Now we'll add the scheduled cron job which will run our script daily. Certbot will not renew the certificate daily but will check each certificate to see if it's eligible for renewal and renew those that are needed.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># This will open the cron config in your text editor finish setup after you save and close the editor.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">crontab</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-e</span><br></span></code></pre></div></div>
<div class="language-ini codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ini codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Add this line anywhere in the file. You can change the time or frequency if desired.</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">17 3 * * * /opt/claws/renew-certs.sh &gt;&gt; /var/log/claws-cert-renew.log 2&gt;&amp;1</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="creating-an-openclaw-agent">Creating an OpenClaw Agent<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#creating-an-openclaw-agent" class="hash-link" aria-label="Direct link to Creating an OpenClaw Agent" title="Direct link to Creating an OpenClaw Agent" translate="no">​</a></h2>
<p>We're now ready to start setting up OpenClaw. We'll do the steps in this section for each OpenClaw instance we want to create. Each instance will be its own Docker container on its own network for isolation.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="create-directories">Create directories<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#create-directories" class="hash-link" aria-label="Direct link to Create directories" title="Direct link to Create directories" translate="no">​</a></h3>
<p>Create the directories specific to this OpenClaw instance.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Replace "claw-name" with the name you're giving this OpenClaw instance.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">mkdir</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-p</span><span class="token plain"> /opt/claws/openclaw/claw-name/</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">config,workspace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Generate a unique security token for this instance's gateway (copy this).</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">openssl rand </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-hex</span><span class="token plain"> </span><span class="token number">32</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="create-docker-environment">Create Docker Environment<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#create-docker-environment" class="hash-link" aria-label="Direct link to Create Docker Environment" title="Direct link to Create Docker Environment" translate="no">​</a></h2>
<p>Now we'll create an environment file which Docker will use when building the container. The values in this file will be available as environment variables within the container. We'll add a few things here but know this is a place where you can add data being pushed into the OpenClaw environment.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Create an environment file for the instance</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">vim</span><span class="token plain"> /opt/claws/openclaw/claw-name/.env</span><br></span></code></pre></div></div>
<div class="language-ini codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ini codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">OPENCLAW_IMAGE=openclaw:latest</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">OPENCLAW_GATEWAY_TOKEN=REPLACE_WITH_GENERATED_TOKEN</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">OPENCLAW_GATEWAY_BIND=lan</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">OPENCLAW_GATEWAY_PORT=18789</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">OPENCLAW_CONFIG_DIR=/home/node/.openclaw</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">OPENCLAW_WORKSPACE_DIR=/home/node/.openclaw/workspace</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># If you start seeing JS meomory faults when running OpenClaw you can adjust NodeJS options by injecting NODE_OPTIONS into the environment.</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">#NODE_OPTIONS=--max-old-space-size=1024</span><br></span></code></pre></div></div>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Restrict permissions on the environment file</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">chmod</span><span class="token plain"> </span><span class="token number">600</span><span class="token plain"> /opt/claws/openclaw/claw-name/.env</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="dns">DNS<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#dns" class="hash-link" aria-label="Direct link to DNS" title="Direct link to DNS" translate="no">​</a></h3>
<p>Add DNS records for the agent. This will be used to generate the TLS/SSL certificate and to access the gateway UI later. The values below are examples only, replace them with your own public DNS/IP values.</p>
<table><thead><tr><th>Type</th><th>Name</th><th>Value / Target</th><th>TTL</th><th>Priority</th></tr></thead><tbody><tr><td>A</td><td>claw1.zachcutler.me</td><td>185.199.111.153</td><td>3600</td><td>-</td></tr><tr><td>AAAA</td><td>claw1.zachcutler.me</td><td>2606:50c0:8003::153</td><td>3600</td><td>-</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="nginx-pre-certificate">nginx (pre-certificate)<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#nginx-pre-certificate" class="hash-link" aria-label="Direct link to nginx (pre-certificate)" title="Direct link to nginx (pre-certificate)" translate="no">​</a></h3>
<p>We need nginx to serve a challenge token to have certbot generate our TLS certificate. We'll add a <code>well-known</code> route to our nginx config for the domain. Each new agent will receive its own <code>server</code> block/section in the config which should go above the <code>default_server</code> entry.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">vim</span><span class="token plain"> /opt/claws/nginx/conf/default.conf</span><br></span></code></pre></div></div>
<div class="language-nginx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-nginx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">map $http_upgrade $connection_upgrade {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    default upgrade;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    ''      close;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">}</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">server {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    listen 80;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    # This will be the (sub)domain you just added DNS records for</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    server_name claw1.zachcutler.me;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    location /.well-known/acme-challenge/ {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        root /var/www/certbot;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">}</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">server {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    listen 80 default_server;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    server_name _;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    return 404;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="generate-certificate">Generate certificate<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#generate-certificate" class="hash-link" aria-label="Direct link to Generate certificate" title="Direct link to Generate certificate" translate="no">​</a></h3>
<p>We'll use certbot (via docker run) to generate the TLS certificate for our agent's domain.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Replace the domain (-d) &amp; email address (--email) with the correct values</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> run </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--rm</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-v</span><span class="token plain"> /opt/claws/nginx/certbot/www:/var/www/certbot </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-v</span><span class="token plain"> /opt/claws/nginx/certbot/conf:/etc/letsencrypt </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  certbot/certbot certonly </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--webroot</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  --webroot-path</span><span class="token operator">=</span><span class="token plain">/var/www/certbot </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> claw1.zachcutler.me </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--email</span><span class="token plain"> admin@zachcutler.me </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  --agree-tos </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  --no-eff-email</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="create-docker-network">Create Docker network<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#create-docker-network" class="hash-link" aria-label="Direct link to Create Docker network" title="Direct link to Create Docker network" translate="no">​</a></h3>
<p>Each OpenClaw instance will be given its own Docker network for isolation. Create the network replacing "claw-name" with the Docker friendly name of your OpenClaw instance (i.e. claw1, zachs-claw).</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Create a private internal docker network.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> network create claw-name-net</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> network inspect claw-name-net</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="add-to-docker-compose">Add to Docker compose<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#add-to-docker-compose" class="hash-link" aria-label="Direct link to Add to Docker compose" title="Direct link to Add to Docker compose" translate="no">​</a></h3>
<p>We can finally add OpenClaw to our Docker compose file. We'll be making the following edits:</p>
<ol>
<li class="">Append the new network to our nginx container's networks list</li>
<li class="">Add the OpenClaw container to our nginx dependencies list</li>
<li class="">Configure the OpenClaw container</li>
<li class="">Configure the OpenClaw network</li>
</ol>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">vim</span><span class="token plain"> /opt/claws/nginx/compose.yml</span><br></span></code></pre></div></div>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">services</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">nginx</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">image</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> nginx</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">stable</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">container_name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> nginx</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">edge</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">restart</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> unless</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">stopped</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">ports</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"80:80"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"443:443"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">volumes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> /opt/claws/nginx/conf/default.conf</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">/etc/nginx/conf.d/default.conf</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">ro</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> /opt/claws/nginx/logs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">/var/log/nginx</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> /opt/claws/nginx/certbot/www</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">/var/www/certbot</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">ro</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> /opt/claws/nginx/certbot/conf</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">/etc/letsencrypt</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">ro</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">networks</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> claw</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">net</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">depends_on</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> claw</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">name</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">claw-name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">image</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ghcr.io/openclaw/openclaw</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">main</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">container_name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> claw</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">name</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">restart</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> unless</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">stopped</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">env_file</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> /opt/claws/openclaw/claw</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">name/.env</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">volumes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> /opt/claws/openclaw/claw</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">name/config</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">/home/node/.openclaw</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> /opt/claws/openclaw/claw</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">name/workspace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">/home/node/.openclaw/workspace</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">networks</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> claw</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">net</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">security_opt</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> no</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">new</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">privileges</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token boolean important">true</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">mem_limit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> 1g</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">cpus</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">1.0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)"># Optional: add extra protections like cap_drop/read_only/pids_limit and seccomp/AppArmor profiles as needed.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">networks</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">claw-name-net</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">external</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token boolean important">true</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="add-to-nginx">Add to nginx<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#add-to-nginx" class="hash-link" aria-label="Direct link to Add to nginx" title="Direct link to Add to nginx" translate="no">​</a></h3>
<p>Now that the Docker container is configured we can reverse proxy to it via nginx. We'll be modifying the <code>server</code> block we created earlier in our nginx config and add another block for the TLS traffic. Server names and certificate paths should have the agent domain in them while the <code>proxy_pass</code> should match your Docker container name.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">vim</span><span class="token plain"> /opt/claws/nginx/conf/default.conf</span><br></span></code></pre></div></div>
<div class="language-nginx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-nginx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">map $http_upgrade $connection_upgrade {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    default upgrade;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    ''      close;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">}</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">server {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    listen 80;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    server_name claw1.zachcutler.me;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    location /.well-known/acme-challenge/ {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        root /var/www/certbot;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    location / {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        return 301 https://$host$request_uri;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">}</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">server {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    listen 443 ssl http2;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    server_name claw1.zachcutler.me;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    ssl_certificate /etc/letsencrypt/live/claw1.zachcutler.me/fullchain.pem;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    ssl_certificate_key /etc/letsencrypt/live/claw1.zachcutler.me/privkey.pem;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    location / {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        proxy_pass http://claw-name:18789;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        proxy_http_version 1.1;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        proxy_set_header Host $host;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        proxy_set_header X-Real-IP $remote_addr;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        proxy_set_header X-Forwarded-Proto $scheme;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        proxy_set_header Upgrade $http_upgrade;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        proxy_set_header Connection $connection_upgrade;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        # Optional hardening for long-lived websocket sessions</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        proxy_read_timeout 3600s;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        proxy_send_timeout 3600s;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    # Optional hardening (production)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    ssl_protocols TLSv1.2 TLSv1.3;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    ssl_prefer_server_ciphers on;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">}</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">server {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    listen 80 default_server;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    server_name _;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    return 404;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="restart-docker">Restart Docker<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#restart-docker" class="hash-link" aria-label="Direct link to Restart Docker" title="Direct link to Restart Docker" translate="no">​</a></h3>
<p>Now that everything is configured we'll tear down and rebuild our Docker service.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> compose </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-f</span><span class="token plain"> /opt/claws/nginx/compose.yml down</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> compose </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-f</span><span class="token plain"> /opt/claws/nginx/compose.yml up </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="configure-openclaw">Configure OpenClaw<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#configure-openclaw" class="hash-link" aria-label="Direct link to Configure OpenClaw" title="Direct link to Configure OpenClaw" translate="no">​</a></h3>
<p>OpenClaw should now be running within our container but it'll need to be configured. OpenClaw provides multiple ways to do this, you can create/edit the master config file at <code>/opt/claws/openclaw/claw-name/config/openclaw.json</code> or run the <code>setup</code> or <code>onboard</code> commands from the container.</p>
<p>The easiest path is to use the onboarding wizard. This will provide you with an onboarding wizard which guides you through configuring your LLM, communication channels, etc.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exec</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-it</span><span class="token plain"> claw-name openclaw onboard</span><br></span></code></pre></div></div>
<p>At the time of writing the onboarding wizard had some known issues and I was unable to get setup with it. Alternatively I was able to run <code>setup</code> and then manage the created config file.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exec</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-it</span><span class="token plain"> claw-name openclaw setup</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">vim</span><span class="token plain"> /opt/claws/openclaw/claw-name/config/openclaw.json</span><br></span></code></pre></div></div>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"meta"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"lastTouchedVersion"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2026.3.9"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"lastTouchedAt"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2026-03-12T01:06:50.050Z"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"agents"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"defaults"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"workspace"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"${OPENCLAW_WORKSPACE_DIR}"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"compaction"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"mode"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"safeguard"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"commands"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"native"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"auto"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"nativeSkills"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"auto"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"restart"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"ownerDisplay"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"raw"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"gateway"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"mode"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"local"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"bind"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"${OPENCLAW_GATEWAY_BIND}"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"auth"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"mode"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"token"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"token"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"${OPENCLAW_GATEWAY_TOKEN}"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"controlUi"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"allowedOrigins"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"http://localhost:18789"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"http://127.0.0.1:18789"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"https://claw1.zachcutler.me"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>We're really wanting to focus on the <code>gateway</code> node right now, getting this configured should allow nginx to serve the OpenClaw gateway dashboard which will give us a user interface to further configure and even chat with OpenClaw.</p>
<p>Restart the OpenClaw instance and give it 30-60 seconds to spin back up.
⚠️ Don't panic if you see a 502 Bad Gateway error fron nginx when you first restart the container. I noticed that it frequently took close to a minute for OpenClaw to fully initialize and start serving the dashboard.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> restart claw-name</span><br></span></code></pre></div></div>
<p>You should now be able to access the dashboard via a web browser using your configured (sub)domain.</p>
<p><img decoding="async" loading="lazy" alt="OpenClaw dashboard" src="https://zachcutler.me/assets/images/openclaw-gateway-connect-56e08e3c9efe34177840e271f0bebe5b.png" width="1137" height="755" class="img_ev3q"></p>
<p>Open the "Overview" page and enter the gateway token <code>OPENCLAW_GATEWAY_TOKEN</code> you configured in your <code>/opt/claws/claw-name/.env</code> file and press "Connect".</p>
<p>This should give you a "pairing required" warning. You'll approve the pairing request from the Docker container.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Approve the most recent pairing request</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exec</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-it</span><span class="token plain"> claw-name openclaw devices approve</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="closing-thoughts">Closing Thoughts<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#closing-thoughts" class="hash-link" aria-label="Direct link to Closing Thoughts" title="Direct link to Closing Thoughts" translate="no">​</a></h2>
<p>You should have a functioning OpenClaw container with a functional chat and configuration interface hosted at your custom domain name. There's still more work to do to get OpenClaw working for you but that really depends on how you want to use and interface with it so I'll end the guide here.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="memory">Memory<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#memory" class="hash-link" aria-label="Direct link to Memory" title="Direct link to Memory" translate="no">​</a></h3>
<p>I found myself running into memory issues prety frequently during the build. I'm starting with a 2GB VPS which isn't much for something designed to host multiple containers. OpenClaw would crash due to memory limits from the NodeJS runtime during certain operations (like upgrades, configuring Discord, etc) and I had to increase both the container and NodeJS memory limits. I'll likely upgrade the VPS as I move this into more of a production state.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="helpful-commands">Helpful Commands<a href="https://zachcutler.me/blog/2026-hosting-multiple-openclaw-agents-with-docker#helpful-commands" class="hash-link" aria-label="Direct link to Helpful Commands" title="Direct link to Helpful Commands" translate="no">​</a></h3>
<p>Here's a few of the most helpful commands I found while doing this setup.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Check docker container status - Look at the "Status" column and make sure your services have been running, if you see &lt;10 seconds your container is likely crashing and restarting.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">ps</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Tear down and rebuild your containers - While initially setting this up sometimes it's just easier to tear it all down and bring it back up as you make config changes, troubleshoot networks, add environment variables, or upgrade image versions.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> compse </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-f</span><span class="token plain"> /opt/claws/nginx/compose.yml down</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> compse </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-f</span><span class="token plain"> /opt/claws/nginx/compose.yml up </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Check the container logs, this is where you'll find crash logs or faults from OpenClaw</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> logs </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--tail</span><span class="token plain"> </span><span class="token number">200</span><span class="token plain"> claw-name</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Sometimes you just need to give yourself a pep talk</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"You’re making real progress — keep going."</span><br></span></code></pre></div></div>
<p>Once the pair request is accepted you should be able to refresh the dashboard and be able to do configuration, setup communication channels, and even chat directly with the LLM.</p>]]></content:encoded>
            <category>OpenClaw</category>
            <category>Docker</category>
            <category>AI</category>
            <category>Agentic AI</category>
            <category>nginx</category>
            <category>Docker Compose</category>
            <category>Ubuntu</category>
            <category>VPS</category>
            <category>DigitalOcean</category>
        </item>
        <item>
            <title><![CDATA[5 Things I Wish I Knew Sooner About ASP.NET Core Minimal APIs]]></title>
            <link>https://zachcutler.me/blog/aspnet-core-minimal-apis-tips</link>
            <guid>https://zachcutler.me/blog/aspnet-core-minimal-apis-tips</guid>
            <pubDate>Sun, 22 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Practical tips for working with ASP.NET Core Minimal APIs — from endpoint filters to validation patterns.]]></description>
            <content:encoded><![CDATA[<p>Minimal APIs in ASP.NET Core have come a long way since their introduction in .NET 6. After using them in several projects, here are five things I wish I'd known from the start.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-endpoint-filters-are-your-friend">1. Endpoint Filters Are Your Friend<a href="https://zachcutler.me/blog/aspnet-core-minimal-apis-tips#1-endpoint-filters-are-your-friend" class="hash-link" aria-label="Direct link to 1. Endpoint Filters Are Your Friend" title="Direct link to 1. Endpoint Filters Are Your Friend" translate="no">​</a></h2>
<p>If you're coming from controllers, you probably miss action filters. Endpoint filters are the Minimal API equivalent and they're incredibly useful for cross-cutting concerns like validation and logging.</p>
<div class="language-csharp codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csharp codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">MapPost</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/api/items"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">CreateItemRequest</span><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">IItemService</span><span class="token plain"> service</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name keyword" style="color:rgb(189, 147, 249);font-style:italic">var</span><span class="token plain"> item </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> service</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">CreateAsync</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> Results</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">Created</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token interpolation-string string" style="color:rgb(255, 121, 198)">$"/api/items/</span><span class="token interpolation-string interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token interpolation-string interpolation expression language-csharp">item</span><span class="token interpolation-string interpolation expression language-csharp punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token interpolation-string interpolation expression language-csharp">Id</span><span class="token interpolation-string interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token interpolation-string string" style="color:rgb(255, 121, 198)">"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">AddEndpointFilter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">context</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> next</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name keyword" style="color:rgb(189, 147, 249);font-style:italic">var</span><span class="token plain"> request </span><span class="token operator">=</span><span class="token plain"> context</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token generic-method function" style="color:rgb(80, 250, 123)">GetArgument</span><span class="token generic-method generic class-name punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generic-method generic class-name">CreateItemRequest</span><span class="token generic-method generic class-name punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">IsNullOrWhiteSpace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">Name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> Results</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">ValidationProblem</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token constructor-invocation class-name">Dictionary</span><span class="token constructor-invocation class-name punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token constructor-invocation class-name keyword" style="color:rgb(189, 147, 249);font-style:italic">string</span><span class="token constructor-invocation class-name punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token constructor-invocation class-name"> </span><span class="token constructor-invocation class-name keyword" style="color:rgb(189, 147, 249);font-style:italic">string</span><span class="token constructor-invocation class-name punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token constructor-invocation class-name punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token constructor-invocation class-name punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"Name"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"Name is required."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">next</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">context</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-route-groups-keep-things-organized">2. Route Groups Keep Things Organized<a href="https://zachcutler.me/blog/aspnet-core-minimal-apis-tips#2-route-groups-keep-things-organized" class="hash-link" aria-label="Direct link to 2. Route Groups Keep Things Organized" title="Direct link to 2. Route Groups Keep Things Organized" translate="no">​</a></h2>
<p>Once you have more than a handful of endpoints, <code>MapGroup</code> is essential for keeping your <code>Program.cs</code> clean:</p>
<div class="language-csharp codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csharp codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token class-name keyword" style="color:rgb(189, 147, 249);font-style:italic">var</span><span class="token plain"> items </span><span class="token operator">=</span><span class="token plain"> app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">MapGroup</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/api/items"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">RequireAuthorization</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">items</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">MapGet</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> GetAllItems</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">items</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">MapGet</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/{id}"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> GetItemById</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">items</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">MapPost</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> CreateItem</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">items</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">MapPut</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/{id}"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> UpdateItem</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">items</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">MapDelete</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/{id}"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> DeleteItem</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-typedresults-improve-openapi-generation">3. TypedResults Improve OpenAPI Generation<a href="https://zachcutler.me/blog/aspnet-core-minimal-apis-tips#3-typedresults-improve-openapi-generation" class="hash-link" aria-label="Direct link to 3. TypedResults Improve OpenAPI Generation" title="Direct link to 3. TypedResults Improve OpenAPI Generation" translate="no">​</a></h2>
<p>Using <code>TypedResults</code> instead of <code>Results</code> gives you better OpenAPI/Swagger documentation out of the box:</p>
<div class="language-csharp codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csharp codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">static</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token return-type class-name">Task</span><span class="token return-type class-name punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token return-type class-name">Results</span><span class="token return-type class-name punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token return-type class-name">Ok</span><span class="token return-type class-name punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token return-type class-name">Item</span><span class="token return-type class-name punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token return-type class-name punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token return-type class-name"> NotFound</span><span class="token return-type class-name punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token return-type class-name punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">GetItemById</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name keyword" style="color:rgb(189, 147, 249);font-style:italic">int</span><span class="token plain"> id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">IItemService</span><span class="token plain"> service</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name keyword" style="color:rgb(189, 147, 249);font-style:italic">var</span><span class="token plain"> item </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> service</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">GetByIdAsync</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> item </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">is</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">?</span><span class="token plain"> TypedResults</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">Ok</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> TypedResults</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">NotFound</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-extension-methods-for-endpoint-mapping">4. Extension Methods for Endpoint Mapping<a href="https://zachcutler.me/blog/aspnet-core-minimal-apis-tips#4-extension-methods-for-endpoint-mapping" class="hash-link" aria-label="Direct link to 4. Extension Methods for Endpoint Mapping" title="Direct link to 4. Extension Methods for Endpoint Mapping" translate="no">​</a></h2>
<p>Move your endpoint definitions into static extension methods to keep <code>Program.cs</code> focused on app configuration:</p>
<div class="language-csharp codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csharp codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">static</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">ItemEndpoints</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">static</span><span class="token plain"> </span><span class="token return-type class-name">IEndpointRouteBuilder</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">MapItemEndpoints</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token plain"> </span><span class="token class-name">IEndpointRouteBuilder</span><span class="token plain"> app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name keyword" style="color:rgb(189, 147, 249);font-style:italic">var</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">group</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">MapGroup</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/api/items"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">group</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">MapGet</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> GetAll</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">group</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">MapGet</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/{id}"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> GetById</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">static</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token return-type class-name">Task</span><span class="token return-type class-name punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token return-type class-name">Ok</span><span class="token return-type class-name punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token return-type class-name">List</span><span class="token return-type class-name punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token return-type class-name">Item</span><span class="token return-type class-name punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token return-type class-name punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token return-type class-name punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">GetAll</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">IItemService</span><span class="token plain"> service</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name keyword" style="color:rgb(189, 147, 249);font-style:italic">var</span><span class="token plain"> items </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> service</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">GetAllAsync</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> TypedResults</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">Ok</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">items</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">static</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token return-type class-name">Task</span><span class="token return-type class-name punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token return-type class-name">Results</span><span class="token return-type class-name punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token return-type class-name">Ok</span><span class="token return-type class-name punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token return-type class-name">Item</span><span class="token return-type class-name punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token return-type class-name punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token return-type class-name"> NotFound</span><span class="token return-type class-name punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token return-type class-name punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">GetById</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name keyword" style="color:rgb(189, 147, 249);font-style:italic">int</span><span class="token plain"> id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">IItemService</span><span class="token plain"> service</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name keyword" style="color:rgb(189, 147, 249);font-style:italic">var</span><span class="token plain"> item </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> service</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">GetByIdAsync</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> item </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">is</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">?</span><span class="token plain"> TypedResults</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">Ok</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> TypedResults</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">NotFound</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>Then in <code>Program.cs</code> it's just:</p>
<div class="language-csharp codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csharp codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">MapItemEndpoints</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-binding-from-multiple-sources">5. Binding from Multiple Sources<a href="https://zachcutler.me/blog/aspnet-core-minimal-apis-tips#5-binding-from-multiple-sources" class="hash-link" aria-label="Direct link to 5. Binding from Multiple Sources" title="Direct link to 5. Binding from Multiple Sources" translate="no">​</a></h2>
<p>Minimal APIs can bind parameters from route values, query strings, headers, and the request body all at once. The <code>[AsParameters]</code> attribute is great for grouping related parameters:</p>
<div class="language-csharp codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csharp codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">MapGet</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/api/items"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token attribute class-name">AsParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token class-name">ItemQuery</span><span class="token plain"> query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">IItemService</span><span class="token plain"> service</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name keyword" style="color:rgb(189, 147, 249);font-style:italic">var</span><span class="token plain"> items </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> service</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">SearchAsync</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> TypedResults</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">Ok</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">items</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">record</span><span class="token plain"> </span><span class="token class-name">ItemQuery</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name keyword" style="color:rgb(189, 147, 249);font-style:italic">string</span><span class="token class-name punctuation" style="color:rgb(248, 248, 242)">?</span><span class="token plain"> Search</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name keyword" style="color:rgb(189, 147, 249);font-style:italic">int</span><span class="token plain"> Page </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name keyword" style="color:rgb(189, 147, 249);font-style:italic">int</span><span class="token plain"> PageSize </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">20</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name keyword" style="color:rgb(189, 147, 249);font-style:italic">string</span><span class="token plain"> SortBy </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Name"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>These patterns have made my Minimal API projects significantly more maintainable. If you're still organizing everything in <code>Program.cs</code>, give endpoint extension methods and route groups a try.</p>]]></content:encoded>
            <category>ASP.NET</category>
            <category>.NET</category>
            <category>C#</category>
            <category>Tips &amp; Tricks</category>
        </item>
        <item>
            <title><![CDATA[Welcome to My Blog]]></title>
            <link>https://zachcutler.me/blog/welcome</link>
            <guid>https://zachcutler.me/blog/welcome</guid>
            <pubDate>Sun, 22 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Introducing my new tech blog — what to expect and why I'm writing.]]></description>
            <content:encoded><![CDATA[<p>Welcome! I'm Zach Cutler, and this is my new tech blog.</p>
<p>After 10+ years of building software professionally, I've decided it's time to start writing about the things I discover, the projects I work on, and the knowledge I think is worth sharing with other developers.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-to-expect">What to Expect<a href="https://zachcutler.me/blog/welcome#what-to-expect" class="hash-link" aria-label="Direct link to What to Expect" title="Direct link to What to Expect" translate="no">​</a></h2>
<p>This blog will cover a mix of topics, but you can generally expect posts in three categories:</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="discoveries">Discoveries<a href="https://zachcutler.me/blog/welcome#discoveries" class="hash-link" aria-label="Direct link to Discoveries" title="Direct link to Discoveries" translate="no">​</a></h3>
<p>New tools, libraries, patterns, or techniques I've come across. The web ecosystem moves fast and there's always something interesting around the corner. When I find something that makes my life easier or changes how I think about a problem, I'll write about it here.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="project-walkthroughs">Project Walkthroughs<a href="https://zachcutler.me/blog/welcome#project-walkthroughs" class="hash-link" aria-label="Direct link to Project Walkthroughs" title="Direct link to Project Walkthroughs" translate="no">​</a></h3>
<p>I'll share retrospectives and deep-dives on things I've built. What worked, what didn't, and what I'd do differently. These will span the stack — from ASP.NET Core APIs to Angular and React frontends, from SQL Server queries to Blazor components.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="knowledge-sharing--psas">Knowledge Sharing &amp; PSAs<a href="https://zachcutler.me/blog/welcome#knowledge-sharing--psas" class="hash-link" aria-label="Direct link to Knowledge Sharing &amp; PSAs" title="Direct link to Knowledge Sharing &amp; PSAs" translate="no">​</a></h3>
<p>Tips, gotchas, and heads-up posts for fellow developers. The kind of thing you'd share with your team on Slack, but longer form and searchable.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-stack">The Stack<a href="https://zachcutler.me/blog/welcome#the-stack" class="hash-link" aria-label="Direct link to The Stack" title="Direct link to The Stack" translate="no">​</a></h2>
<p>For the curious, this site is built with <a href="https://docusaurus.io/" target="_blank" rel="noopener noreferrer" class="">Docusaurus</a> — a static site generator that lets me write posts in Markdown and commit them straight to the repo. No database, no CMS, no friction. Just write, commit, and deploy.</p>
<p>Thanks for stopping by. I hope you find something useful here.</p>]]></content:encoded>
            <category>Discovery</category>
        </item>
    </channel>
</rss>