A practical guide to running multiple OpenClaw AI gateway instances on one server. Each agent gets its own identity, tools, memory system, and access controls. This was built for ARM64 VPS hosts, secured with Tailscale, and designed to scale from two agents to as many as the hardware allows.
Why this setup?
Running your own AI gateway gives you control over your agents: their tools, memory, model providers, and who can access them. This guide sets up shared infrastructure where each agent is managed independently but uses a single Docker image and provisioning system.
Use cases:
- Personal agent and partner/family agent on the same server
- Team of specialized agents under one roof: research, ops, comms
- Development and production agents side by side
Architecture
/opt/openclaw/
├── repo/ # Cloned openclaw source
├── Dockerfile # Custom image with extra CLI tools
├── templates/
│ ├── docker-compose.template.yml # Per-agent compose template
│ └── env.template # Per-agent .env template
├── scripts/
│ ├── provision-agent.sh # One-command agent provisioning
│ └── status.sh # Multi-agent status dashboard
├── total-recall/ # Shared Total Recall source
├── docker-compose.<agent>.yml # Generated per agent
├── .env.<agent> # Secrets per agent (mode 0600)
└── ports.conf # Port registry
/home/<agent>/.openclaw/ # Per-agent persistent data
├── openclaw.json # Agent config
├── workspace/ # Working directory
│ ├── memory/ # Total Recall observations
│ └── logs/ # Observer/reflector logs
└── skills/
└── total-recall/ # Memory system installation
Each agent runs in its own Docker container with:
- Dedicated Linux user and home directory
- Unique port bound to
127.0.0.1 - Independent systemd unit for auto-start
- Its own gateway token and secrets
- Total Recall memory system with autonomous observation
Access is exposed via Tailscale Serve, with HTTPS inside the tailnet, and locked down with Tailscale ACLs per user.
Prerequisites
- A VPS or dedicated server, ARM64 or x86_64
- Ubuntu 22.04+ or Debian 12+
- Docker Engine installed
- Tailscale installed and authenticated
- At least 2 GB RAM per agent, plus OS overhead
Tested on
- Hetzner CAX11: 4 ARM64 cores, 8 GB RAM, 80 GB disk, Ubuntu 24.04
- Comfortable for 2 agents, feasible for 3
Step 1: System preparation
Add your user to the Docker group
sudo usermod -aG docker $USER
newgrp docker # or re-login
Create swap
This is recommended for a small VPS.
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# Low swappiness: prefer RAM, use swap as safety net
sudo sysctl vm.swappiness=10
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-openclaw.conf
Configure Docker log rotation
Prevent logs from eating the disk:
sudo tee /etc/docker/daemon.json << 'EOF'
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
EOF
sudo systemctl restart docker
Step 2: Directory structure
sudo groupadd openclaw
sudo usermod -aG openclaw $USER
sudo mkdir -p /opt/openclaw/{repo,templates,scripts}
sudo chown -R $USER:openclaw /opt/openclaw
chmod 750 /opt/openclaw
Step 3: Build the Docker image
Clone OpenClaw
git clone https://github.com/openclaw/openclaw.git /opt/openclaw/repo
Create a custom Dockerfile
This extends the official image with additional CLI tools. Adjust the tool list to your needs. The example includes Gmail, WhatsApp, and Google Places CLIs.
# /opt/openclaw/Dockerfile
# Stage 1: Build any tools that lack prebuilt binaries for your arch
FROM golang:1.25-bookworm AS wacli-builder
RUN apt-get update && apt-get install -y --no-install-recommends gcc libc6-dev git ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build
RUN git clone --depth 1 https://github.com/steipete/wacli.git .
RUN CGO_ENABLED=1 go build -tags sqlite_fts5 -o /usr/local/bin/wacli ./cmd/wacli
# Stage 2: Extend the base image
FROM openclaw:base
USER root
# System dependencies for Total Recall (inotify, cron) and networking (socat)
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
socat \
inotify-tools \
cron \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Install prebuilt CLI tools (adjust URLs for your architecture)
RUN curl -fsSL https://github.com/steipete/gogcli/releases/download/v0.11.0/gogcli_0.11.0_linux_arm64.tar.gz \
| tar -xz -C /usr/local/bin gog \
&& chmod +x /usr/local/bin/gog
RUN curl -fsSL https://github.com/steipete/goplaces/releases/download/v0.3.0/goplaces_0.3.0_linux_arm64.tar.gz \
| tar -xz -C /usr/local/bin goplaces \
&& chmod +x /usr/local/bin/goplaces
# Install tools built from source
COPY --from=wacli-builder /usr/local/bin/wacli /usr/local/bin/wacli
RUN chmod +x /usr/local/bin/wacli
USER node
For x86_64, replace arm64 with amd64 in the download URLs.
Build
# Build base image from repo
docker build -t openclaw:base -f /opt/openclaw/repo/Dockerfile /opt/openclaw/repo
# Build custom image with tools
docker build -t openclaw:latest -f /opt/openclaw/Dockerfile /opt/openclaw
# Verify
docker run --rm openclaw:latest sh -c "which gog wacli goplaces"
Step 4: Create templates
Docker Compose template
# /opt/openclaw/templates/docker-compose.template.yml
services:
openclaw-__AGENT_NAME__:
image: openclaw:latest
container_name: openclaw-__AGENT_NAME__
env_file:
- /opt/openclaw/.env.__AGENT_NAME__
environment:
HOME: /home/node
TERM: xterm-256color
volumes:
- /home/__AGENT_NAME__/.openclaw:/home/node/.openclaw
- /home/__AGENT_NAME__/.openclaw/workspace:/home/node/.openclaw/workspace
ports:
- "127.0.0.1:__AGENT_PORT__:18789"
init: true
restart: unless-stopped
deploy:
resources:
limits:
memory: 2560M
cpus: "2"
healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:18789/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
command: ["node", "openclaw.mjs", "gateway", "--allow-unconfigured", "--bind", "lan"]
Key design choices:
127.0.0.1binding: ports are never exposed to the public internetinit: true: proper PID 1 for signal handling and zombie reaping- Resource limits: prevents one agent from starving the other
- Health checks: Docker and systemd know when an agent is actually working
Environment template
# /opt/openclaw/templates/env.template
# OpenClaw Gateway: __AGENT_NAME__
OPENCLAW_GATEWAY_TOKEN=__GATEWAY_TOKEN__
OPENAI_API_KEY=
GOG_KEYRING_PASSWORD=__GOG_KEYRING_PASSWORD__
# Total Recall (memory system LLM: cheap model via OpenRouter)
LLM_BASE_URL=https://openrouter.ai/api/v1
LLM_API_KEY=
LLM_MODEL=google/gemini-2.5-flash
Step 5: Install Total Recall
Total Recall gives each agent autonomous memory. It watches conversations, compresses them into observations, and consolidates nightly. With cheap models through OpenRouter, it costs about $0.10/month per agent.
git clone https://github.com/gavdalf/total-recall.git /opt/openclaw/total-recall
The provisioning script handles per-agent installation automatically.
Step 6: The provisioning script
This is the core automation. Running provision-agent.sh <name> creates everything an agent needs.
# /opt/openclaw/scripts/provision-agent.sh
What it does:
- Allocates the next available port from
ports.conf - Creates a dedicated Linux user with a nologin shell and
openclawgroup - Creates the agent’s data directories with correct ownership, using uid 1000 for the container’s
nodeuser - Generates a unique gateway token and keyring password
- Renders the compose file and
.envfrom templates - Installs Total Recall into the agent’s workspace
- Sets up host-level cron for memory observation every 15 minutes, reflection hourly, and dream cycles at 3am nightly
- Creates and enables a systemd unit for auto-start on boot
The full script is available in the repository. This is the key pattern for the systemd unit it generates:
[Unit]
Description=OpenClaw Gateway - <agent_name>
After=docker.service
Requires=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/openclaw
ExecStart=/usr/bin/docker compose -f <compose_file> up -d
ExecStop=/usr/bin/docker compose -f <compose_file> down
ExecReload=/usr/bin/docker compose -f <compose_file> restart
TimeoutStartSec=120
[Install]
WantedBy=multi-user.target
And the Total Recall cron entries it installs:
# Observer: compress recent conversations every 15 minutes
*/15 * * * * docker exec openclaw-<agent> bash -c 'OPENCLAW_WORKSPACE=... bash .../observer-agent.sh'
# Reflector: consolidate when observations grow large
0 * * * * docker exec openclaw-<agent> bash -c 'OPENCLAW_WORKSPACE=... bash .../reflector-agent.sh'
# Dream Cycle: nightly memory consolidation
0 3 * * * docker exec openclaw-<agent> bash -c 'OPENCLAW_WORKSPACE=... bash .../dream-cycle.sh preflight'
Step 7: Provision your agents
sudo /opt/openclaw/scripts/provision-agent.sh alice
sudo /opt/openclaw/scripts/provision-agent.sh bob
Each run prints the gateway token and next steps. Save those tokens. You need them to connect.
Configure the gateway
Each agent needs an openclaw.json with allowed origins for the Control UI. Create one per agent:
# Replace the hostname and port for each agent
sudo tee /home/alice/.openclaw/openclaw.json << 'EOF'
{
"gateway": {
"mode": "local",
"controlUi": {
"allowedOrigins": [
"https://your-tailscale-hostname:18789",
"http://127.0.0.1:18789"
]
}
}
}
EOF
sudo chown 1000:1000 /home/alice/.openclaw/openclaw.json
Configure model access
Option A: add OPENAI_API_KEY to /opt/openclaw/.env.<agent>.
Option B: use a ChatGPT/Codex subscription with OAuth:
docker exec -it openclaw-alice openclaw models auth login --provider openai-codex
Start
sudo systemctl start openclaw-alice openclaw-bob
Step 8: Expose via Tailscale Serve
Tailscale Serve gives you HTTPS access within your tailnet: no port forwarding, no certificates to manage, and no exposure to the public internet.
sudo tailscale serve --bg --https=18789 http://127.0.0.1:18789
sudo tailscale serve --bg --https=18790 http://127.0.0.1:18790
You may need to enable Serve for your node first in the Tailscale admin console.
Access from any device on your tailnet:
https://your-vps-hostname:18789/: Alicehttps://your-vps-hostname:18790/: Bob
Step 9: Lock down access with Tailscale ACLs
This is where multi-user access gets interesting. Tailscale ACLs let you control exactly who can reach which agent.
Go to https://login.tailscale.com/admin/acls and set your policy:
{
"tagOwners": {
"tag:openclaw": ["your-login@example.com"]
},
"groups": {
"group:admin": ["your-login@example.com"],
"group:family": ["partner@example.com"]
},
"acls": [
// Admin: full access to everything
{
"action": "accept",
"src": ["group:admin"],
"dst": ["*:*"]
},
// Family: only their agent, nothing else
{
"action": "accept",
"src": ["group:family"],
"dst": ["your-vps-hostname:18790"]
}
],
// Restrict SSH to admins only
"ssh": [
{
"action": "accept",
"src": ["group:admin"],
"dst": ["tag:openclaw"],
"users": ["your-ssh-user"]
}
],
// Prevent accidental Funnel exposure
"nodeAttrs": [
{
"target": ["tag:openclaw"],
"attr": ["funnel": false]
}
]
}
Then tag your VPS as tag:openclaw in the admin console: Machines, your VPS, Edit, Tags.
Invite additional users
- Tailscale admin console: Users, Invite by email
- They install Tailscale and join with their email
- Add their email to the appropriate group in your ACL policy
- Add an ACL rule granting access to their specific agent port
- Share only their agent’s gateway token
Step 10: Connecting your client
Install the OpenClaw client on your local machine following the official installation docs. Once installed, point it at your self-hosted gateway:
openclaw gateway connect \
--url "https://your-tailscale-hostname:18789" \
--token "your-gateway-token-here"
The gateway URL is your VPS’s Tailscale hostname plus the agent’s port. The token was printed during provisioning and is stored in /opt/openclaw/.env.<agent> on the server.
Per-agent profiles
If you connect to multiple agents from the same machine, use profiles to switch between them:
# Save each agent as a named profile
openclaw gateway connect \
--url "https://your-tailscale-hostname:18789" \
--token "..." \
--profile alice
openclaw gateway connect \
--url "https://your-tailscale-hostname:18790" \
--token "..." \
--profile bob
# Switch between agents
openclaw gateway use alice
openclaw gateway use bob
For other users
Share only what they need:
- The gateway URL for their agent, for example
https://your-tailscale-hostname:18790 - Their agent’s gateway token
- A link to install the OpenClaw client
They do not need SSH access, Docker access, or server credentials. Tailscale handles authentication and encryption. They just need Tailscale installed and access to your tailnet.
# What you send them:
openclaw gateway connect \
--url "https://your-tailscale-hostname:18790" \
--token "their-token-here"
Web UI alternative
Every agent also serves a Control UI in the browser. No client install needed. Visit the gateway URL directly:
https://your-tailscale-hostname:18790/
The browser prompts for the gateway token on first visit.
Scaling
Adding a new agent
sudo /opt/openclaw/scripts/provision-agent.sh <name>
# Add OPENAI_API_KEY to /opt/openclaw/.env.<name>
# Create /home/<name>/.openclaw/openclaw.json with allowed origins
sudo systemctl start openclaw-<name>
sudo tailscale serve --bg --https=<port> http://127.0.0.1:<port>
# Update Tailscale ACLs if new users need access
Checking status
sudo /opt/openclaw/scripts/status.sh
Output:
========================================
OpenClaw Agent Status
========================================
AGENT PORT CONTAINER SYSTEMD HEALTH
----- ---- --------- ------- ------
alice 18789 running active healthy
bob 18790 running active healthy
Resource Usage:
openclaw-alice: CPU=0.01% MEM=334MiB / 2.5GiB
openclaw-bob: CPU=0.00% MEM=324MiB / 2.5GiB
Capacity planning
| Agents | Min RAM | Comfortable RAM |
|---|---|---|
| 2 | 4 GB | 8 GB |
| 3 | 6 GB | 8 GB |
| 4+ | 8 GB | 12+ GB |
Each agent idles at about 330 MiB and peaks around 1-1.5 GiB under load.
Verification checklist
After deployment, run through these checks:
- [ ]
docker ps: all containers running and healthy - [ ]
curl http://127.0.0.1:<port>/: each gateway returns HTTP 200 - [ ] Access
https://your-hostname:<port>/from your device: UI loads - [ ] Access from restricted user’s device: only their agent is reachable
- [ ]
sudo /opt/openclaw/scripts/status.sh: all agents show healthy - [ ] Reboot the VPS: agents auto-start via systemd
- [ ]
docker exec openclaw-<agent> which gog wacli goplaces: CLI tools present - [ ]
ls /home/<agent>/.openclaw/workspace/memory/:observations.mdexists - [ ]
sudo crontab -l: Total Recall cron entries present for all agents
Troubleshooting
Gateway fails with an allowedOrigins error
The gateway refuses to start when binding to LAN without explicit CORS origins. Create or update openclaw.json:
{
"gateway": {
"mode": "local",
"controlUi": {
"allowedOrigins": ["https://your-hostname:PORT", "http://127.0.0.1:PORT"]
}
}
}
Container keeps restarting
docker logs openclaw-<agent> --tail 20
Common causes: missing config, port conflict, permission issues on mounted volumes.
Total Recall is not observing
Check that the cron entries exist and the container is named correctly:
sudo crontab -l | grep <agent>
docker exec openclaw-<agent> ls /home/node/.openclaw/skills/total-recall/scripts/
Check observer logs:
tail -f /home/<agent>/.openclaw/workspace/logs/observer.log
Tailscale Serve will not start
Visit the link in the error output to enable Serve for your node in the Tailscale admin console. This is a one-time approval per node.