One tenant, one cell
Hosting OpenClaw for more than one customer
You already run OpenClaw for yourself, and now a second customer wants one. The tempting move is to add them to the Gateway you already have, give them their own session, and treat that session as the line between the two of them. OpenClaw's own documentation closes that door in its first sentence, then describes the shape that does hold: one complete instance per tenant, supervised by openclaw fleet.
Why a session is not a tenant boundary
- An authenticated operator inside one Gateway holds a trusted control-plane role. That role is not scoped per customer, so a second operator on the same Gateway is a second operator over everything that Gateway can reach, not a second tenant.
- Session IDs select routing. They decide which context a message lands in, and they do not authorise one tenant against another, so handing every customer their own session key buys tidy addressing and no isolation at all.
- Agent sandboxing is still worth having, because it reduces the effect of untrusted content and tool execution. What it does not do is turn one shared Gateway into a tenant authorisation boundary, and the documentation says so plainly rather than leaving it to be discovered.
- The supported shape is one cell per tenant, so each trust domain gets a separate Gateway process, container, persistent state tree and Gateway credential. That follows the rule the Gateway security model already states: do not co-locate mutually untrusted users in one OpenClaw process or one OS user.
OpenClaw's default security model is one trusted operator boundary per Gateway, not hostile multi-tenant isolation inside one shared Gateway.
A cell is a whole OpenClaw, not a tenant row
openclaw fleet calls each isolated instance a cell, and a cell is a full Gateway in a hardened container with its own state, credentials, workspace, channel accounts, token and loopback-only host port. The CLI itself is a host-side lifecycle supervisor: it records cells in the OpenClaw state database and asks a local Docker or Podman runtime to create, inspect, start, stop, replace and remove their containers. It does not proxy tenant messages and adds no shared application-level data path between cells, so nothing inside one cell can address another one through Fleet. Remote runtime endpoints are refused outright, because Fleet's bind paths and loopback URLs belong to the local host. Each cell runs the official ghcr.io/openclaw/openclaw image on its own user-defined bridge network and listens on port 18789 inside the container, while the runtime publishes that port only to 127.0.0.1 and an allocated host port.
The four commands, and what they preserve
- openclaw fleet create acme builds the cell and prints a generated Gateway token exactly once, so store it before the terminal scrolls. The reported loopback URL is where that tenant authenticates and then configures their own provider credentials and channel accounts, inside their own instance.
- openclaw fleet status acme combines the registry row, live container inspection and a short request to the cell's health endpoint. A healthy result proves the Gateway is alive, not that every configured channel or plugin is ready.
- openclaw fleet upgrade acme keeps the host port, mounted data, resource profile, user-supplied environment and Gateway token across the container replacement. Removal is the mirror image: openclaw fleet rm acme --force retains tenant data, and adding --purge-data deletes it irreversibly after a resolved-path containment check.
Persistent state comes from a per-tenant directory under the OpenClaw state directory, fleet/cells/acme, mounted inside the container at /home/node/.openclaw, and current OAuth access, refresh and ID-token values sit there as plaintext in SQLite. The consequence is blunt: a copy of a cell's state tree is a copy of that tenant's live credentials, which is the same frame OpenClaw backup and restore applies to archives of a single instance. A second host path, fleet/auth-profile-secrets/acme, is mounted at /home/node/.config/openclaw so the layout matches the persistence model in OpenClaw in Docker, but it holds only the key for legacy encrypted-sidecar recovery and encrypts nothing in the current database.
The isolation ladder, and the surfaces Fleet leaves empty
The first rung is the hardened container baseline: all Linux capabilities dropped, no-new-privileges set, PID, memory, CPU and optional writable-layer disk limits applied, separate persistent mounts and per-cell networks, and publishing only to host loopback. Bridge networking still leaves outbound egress unrestricted, and this is where the two runtimes part company. A Podman cell can run with --network internal to block egress while keeping the published loopback Gateway port, which is one good reason to read OpenClaw with Podman before you pick a runtime, whereas Docker breaks that published port on an internal network, so Fleet rejects the combination and expects host firewall rules such as the DOCKER-USER chain instead. The second rung is a stronger isolation runtime such as gVisor or Kata Containers, or cells placed in microVMs, and that is infrastructure configuration rather than a Fleet flag, because the runtime option chooses the container CLI and not the OCI isolation backend. The third rung is separate machines, for tenants who do not trust the same host operator. No rung changes the application trust model, and none of them protects a tenant from the operator: a host administrator can inspect container configuration and environment, read mounted cell data, replace images or enter containers, and resistance to a compromised host is a stated non-goal. Fleet also stops well short of a hosting product, with no shared channel accounts or shared ingress router, no slimmed-down per-tenant processes, no remote cell hosts under one supervisor, and no tenant self-service portal, billing plane or delegated administration UI. If the people you are hosting do trust each other, the page you want is OpenClaw multi-user mode, which is a different problem with different defaults.
On Diali
Diali hosts OpenClaw, so the tenant boundary is drawn by the platform instead of by you. Each customer runs their own assistant, with its own state directory rather than a share of a common one, and the runtime configuration behind it is generated from the dashboard and replaced at each release. That state lives on a persistent volume, with daily snapshots and one-click restore available through the Backups add-on (included on Max). Hosted OpenClaw on Diali sets out what the hosted assistant includes, and Diali pricing lists the plans it comes in.
- One Gateway is one trusted operator domain, never a tenant boundary.
- A cell is a complete instance with its own container, state, token and loopback port.
- Cell state holds plaintext OAuth, so every copy is a credential.
Stop reading about it, build one
Set up an agent, pick a channel, and have it working inside the app you already keep open.
