OpenClaw Fleet CLI: Provision and Manage Per-Tenant Cells

This page covers the openclaw fleet command for provisioning and managing isolated OpenClaw cells. It is intended for operators deploying per-tenant deployments with Docker or Podman.

Read this when

  • You host multiple tenant trust domains on one machine
  • You need to create, inspect, upgrade, or remove fleet cells

openclaw fleet

openclaw fleet handles full OpenClaw deployments known as cells. A cell includes its own Gateway, state, credentials, channel accounts, container, and a loopback-only host port. Assign one cell per tenant trust boundary; never use a shared Gateway as a hostile multi-tenant boundary.

Fleet is experimental. Command names, flags, output formats, and the container profile may change between releases without a deprecation period.

Fleet works with Docker and Podman. The default image is ghcr.io/openclaw/openclaw:latest.

Fleet is validated on Linux and macOS hosts. Windows hosts have not been tested yet.

Quick start

openclaw fleet create acme
openclaw fleet status acme
openclaw fleet list

fleet create outputs the generated Gateway token one time along with the cell URL. Save the token right away, then set up each tenant's channel accounts inside that tenant's cell.

Tenant IDs

Tenant IDs must satisfy these rules:

^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$

This permits 1 to 40 lowercase letters, digits, and internal hyphens. An ID must begin and end with a letter or digit. Uppercase letters, underscores, slashes, dots, whitespace, and traversal strings like ../acme are not allowed.

The ID is included in the container name: openclaw-cell-<tenant>.

fleet create

Create and launch a cell:

openclaw fleet create acme

Create a Podman cell on a specific port without launching it:

openclaw fleet create acme \
  --runtime podman \
  --port 19125 \
  --no-start

Provide tenant-specific environment variables by repeating --env:

openclaw fleet create acme \
  --env TZ=America/Los_Angeles \
  --env OPENCLAW_DISABLE_BONJOUR=1

Environment keys consist of letters, digits, and underscores and cannot start with a digit. Values must be single-line because Fleet passes them through a protected runtime environment file. Fleet blocks attempts to override the managed container-path and Gateway-token variables listed under Storage and container layout.

Create options

OptionDefaultDescription
--image <ref>ghcr.io/openclaw/openclaw:latestContainer image for the cell.
--runtime <runtime>dockerContainer CLI: docker or podman.
--port <number>Automatically allocated from 19100Loopback host port. An explicitly selected port must not belong to another registered cell.
--memory <value>2gContainer memory limit in Docker/Podman syntax.
--cpus <value>2Container CPU limit.
--disk <size>NoneCap the container writable layer when the storage backend supports quotas.
--network <mode>bridgeOutbound network mode: bridge or internal.
--pids-limit <number>512Maximum number of processes in the container.
--env <KEY=VALUE>NonePass an environment variable to the cell. Repeat for multiple values.
--gateway-token <value>Random 32-character hexadecimal tokenUse a supplied Gateway token instead of generating one. See Token handling.
--no-startCell startsCreate the container without starting it.
--jsonHuman-readable outputPrint machine-readable output.

Automatic allocation picks the first unused registry port at or above 19100. Fleet rejects duplicate tenant IDs and explicit ports already assigned to another cell.

Image references are passed as a single container-runtime argument. Empty references and values starting with - are rejected so an image cannot be interpreted as a Docker or Podman option.

The chosen Docker or Podman endpoint must be local. Fleet rejects remote Docker contexts, DOCKER_HOST endpoints, and remote Podman services before reserving a port or creating local state. Remote cell hosts are not supported.

When Fleet starts a new cell, create waits up to about a minute for its Gateway to respond to /healthz. If the cell does not become healthy, Fleet leaves its container and registry row intact for fleet status, fleet logs, or explicit removal. --no-start skips this health gate. The generated Gateway token of an unhealthy new cell is not lost - it remains in the container environment (docker|podman inspect), and because the cell has served no traffic yet, fleet rm --force followed by a fresh create is always a safe alternative.

Pinning by digest

Create and upgrade accept digest-pinned image references such as --image ghcr.io/openclaw/openclaw@sha256:<digest>. Fleet passes the image reference through verbatim to Docker or Podman, which lets an operator keep a cell on immutable image bytes instead of a moving tag.

The create result includes the tenant ID, container name, host port, Gateway token, and local URL. Even in JSON output, treat the result as secret-bearing because it contains the token.

Disk limits

--disk limits only the container writable layer. The bind-mounted per-tenant state and auth directories remain host storage; use host filesystem project quotas when those directories also need a hard limit.

Runtime/storage backend--disk support
Docker overlay2 on XFSRequires the XFS pquota mount option.
Docker btrfs or zfsSupported by the storage driver.
Podman overlayRequires XFS backing storage.
Other backendsContainer creation fails with the daemon error and Fleet's backend guidance.

Egress policy

ModeDockerPodman
bridgeSupported; outbound egress is unrestricted by default.Supported; outbound egress is unrestricted by default.
internalRejected because Docker does not preserve the published loopback Gateway port on an internal network.Supported; the loopback Gateway remains published while outbound egress is blocked.

For Docker, keep the bridge mode and enforce outbound policy with host firewall rules such as the DOCKER-USER chain.

fleet list

Display cells sorted by tenant ID:

openclaw fleet list
openclaw fleet ls
openclaw fleet list --json

The following columns are included:

ColumnMeaning
tenantUnique tenant identifier.
stateCurrent container state as reported by Docker or Podman inspection. unknown indicates the runtime could not be reached, or a container exists with the cell's name but its Fleet ownership labels do not align with the registry entry (possible collision or tampering; inspect manually before taking action).
portLoopback host port assigned to the cell's Gateway.
imageContainer image recorded in the registry.
createdTimestamp of cell creation.

Registry entries persist even when Docker or Podman is unreachable; only live state shows as unknown.

fleet status

Examine a single cell:

openclaw fleet status acme
openclaw fleet status acme --json

The status combines the fleet registry record, a live container inspection, and a brief best-effort probe sent to:

http://127.0.0.1:<host-port>/healthz

Health returns ok, failed, or skipped. /healthz confirms the Gateway is alive, not that every channel or plugin is fully ready. The probe is omitted when no usable local endpoint is available.

fleet logs

Pipe a cell's container logs directly to the terminal:

openclaw fleet logs acme
openclaw fleet logs acme --follow
openclaw fleet logs acme --tail 200
openclaw fleet logs acme --since 10m

Fleet checks the registered container's ownership labels before reading any logs, so a foreign container using the expected cell name is rejected. The stream is locked to the inspected container ID, so a concurrent replacement cannot redirect it to a newer instance. Press Ctrl-C to exit --follow without marking the operator stop as a command failure. Log output passes through a redaction filter that replaces the cell's current Gateway token with <redacted> before anything reaches the terminal.

fleet logs does not support a --json mode because container logs are a raw stdout/stderr stream. For scripting, limit output with --tail and use standard shell redirection or pipelines.

fleet start, fleet stop, and fleet restart

Manage an existing cell using its recorded runtime:

openclaw fleet start acme
openclaw fleet stop acme
openclaw fleet restart acme

These commands act on the registered container name. They fail if the tenant is unknown or the recorded runtime cannot perform the requested operation.

fleet upgrade

Re-download the recorded image and recreate the cell container:

openclaw fleet upgrade acme

Move the cell to a different image:

openclaw fleet upgrade acme --image ghcr.io/openclaw/openclaw:<version>

Upgrade fetches the target image, inspects the existing container and per-cell network, stops and removes the container, then creates and starts a replacement. The new container retains the same host port, data directories, per-cell bridge network, runtime profile, resource limits, restart policy, Fleet-managed environment, and values originally set with --env. Mounted state persists across container replacement; image-default environment may change with the target image.

The replacement is committed only after its Gateway responds with /healthz on the cell's loopback port, matching the health contract defined in the official compose file. If the replacement exits, crash-loops, or fails to become healthy within roughly a minute, it is removed and the previous container is restored, so a broken image does not disable a working cell.

The Gateway token is deliberately not stored in the fleet registry. Before removing the old container, Fleet reads its environment and passes OPENCLAW_GATEWAY_TOKEN to the replacement. Do not manually delete the old container before an upgrade if the token exists nowhere else you control.

fleet backup and fleet restore

Create a backup of one stopped cell:

openclaw fleet stop acme
openclaw fleet backup acme --out ./acme.tgz

Restore that archive into the registered cell:

openclaw fleet restore acme --from ./acme.tgz

These commands require host operator privileges. Archives contain tenant state and authentication secrets, are created with mode 0600, and must be treated like credentials. Backup refuses a running cell to ensure SQLite state is captured consistently. Restore refuses a running cell unless --force is provided, replaces only that tenant's state, rotates the Gateway token, and prints the new token once. Fleet backs up one tenant at a time; all-tenant backup is a separate operator action.

Restore requires an existing stopped container because its inspected runtime profile provides the replacement limits, user mapping, environment provenance, and image. If the registered container was removed out of band, first run fleet rm <tenant> --force without --purge-data, recreate the cell with the intended image and --no-start, then retry restore. The initial removal keeps both tenant data directories intact.

Both commands accept --max-bytes <bytes> to limit archived or extracted file data, and both apply the same fixed one-million budget of archive path segments so metadata-only archive bombs cannot exhaust host inodes and every accepted backup remains restorable. Backup accepts --out <path> and both commands support --json.

Archives contain only regular files and directories. Backup never follows or stores symlinks, hard links, sockets, or device nodes; skipped counts are reported in the result. Restore rejects archives containing any other entry type. Recreatable symlink trees such as workspace node_modules must be reinstalled inside the cell after a restore.

fleet doctor

Inspect every cell or a single tenant without altering runtime or filesystem state:

openclaw fleet doctor
openclaw fleet doctor acme --json

Doctor checks runtime locality, ownership labels, health, hardening, resource limits, loopback port binding, token presence, network ownership and egress mode, and private state-directory permissions. Warnings indicate stopped cells or ownership discrepancies; any failed finding produces a nonzero process exit code.

fleet rm

Delete a stopped cell from the runtime and registry while preserving tenant data:

openclaw fleet rm acme

A running container requires --force:

openclaw fleet rm acme --force

Also permanently delete the cell data:

openclaw fleet rm acme --purge-data --force

Fleet removes the cell container before removing its dedicated bridge network. --purge-data requires --force. Before recursive deletion, Fleet resolves both Fleet-owned roots and both per-tenant directories. Each target must be the exact expected tenant leaf, strictly inside its root, and not a symlink. These containment checks prevent a corrupted registry path or cross-tenant symlink from redirecting deletion elsewhere.

Purge is retryable when an exact expected tenant directory is already absent. This allows a later invocation to finish cleanup after a partial filesystem failure without relaxing the path checks for directories that still exist.

Storage and container layout

Cell state and auth-profile encryption keys use separate per-tenant host paths under the active OpenClaw state directory:

<state-dir>/fleet/cells/<tenant>/
<state-dir>/fleet/auth-profile-secrets/<tenant>/

The first directory is mounted at /home/node/.openclaw. The second is mounted at /home/node/.config/openclaw, matching the official Docker setup's encryption-key mount. The encryption key is therefore not exposed beneath the ordinary state mount or included when only the cell-state directory is backed up or shared. Both directories survive normal removal and upgrade; fleet rm --purge-data --force deletes both after separate containment checks.

Before first start, Fleet initializes the cell config with gateway.mode=local, token auth, the LAN container bind, and Control UI origins for the allocated host port. The token value is not written to that config; it remains in the container environment.

Fleet pins the official image's container paths using these environment values:

VariableContainer value
HOME/home/node
OPENCLAW_HOME/home/node
OPENCLAW_STATE_DIR/home/node/.openclaw
OPENCLAW_CONFIG_PATH/home/node/.openclaw/openclaw.json
OPENCLAW_WORKSPACE_DIR/home/node/.openclaw/workspace
OPENCLAW_GATEWAY_TOKENGenerated or supplied cell token

By default, the official image uses the non-root node user with UID 1000. Fleet ensures that private 0700 bind mounts remain writable without exposing them to all users. When running with rootful Docker, the cell adopts the invoking non-root UID and GID; rootless Docker assigns container UID 0, which corresponds to the calling unprivileged host user within the daemon's user namespace. Podman uses keep-id together with the invoking UID and GID. If Fleet itself runs as root against a rootful runtime, it keeps the image user and assigns UID/GID 1000 to the initial mount files.

On SELinux systems, Docker and Podman mounts get a private :Z relabel. When restoring or relocating cell data, ensure the bind-mounted paths remain writable by the effective container user. The profile supports rootless operation, but Docker or Podman must already be set up for rootless use on the host; Fleet does not convert a rootful daemon into a rootless one.

Security profile

Every cell receives the following profile from Fleet:

ControlApplied profileWhy
Linux capabilities--cap-drop=ALLThe Gateway runs as a Node.js process and does not require any extra Linux capabilities.
Privilege escalation--security-opt no-new-privilegesBlocks processes from gaining privileges via setuid or setgid binaries.
Init process--initHandles reaping of descendant processes and forwards container lifecycle signals.
Process limit--pids-limit 512 by defaultLimits fork and process exhaustion.
Memory limit--memory 2g by defaultRestricts cell memory usage.
CPU limit--cpus 2 by defaultRestricts cell CPU usage.
Writable-layer diskOptional --diskLimits the container layer when the runtime storage backend supports quotas.
Restart policy--restart unless-stoppedRestarts a failed cell without overriding an intentional stop.
Host publishing127.0.0.1:<host-port>:18789 onlyPrevents the Gateway from binding to wildcard host interfaces.
Cell networkOne bridge or Podman internal network per cellSeparates container-IP traffic and optionally blocks Podman outbound egress.
Container identityHost-matched user mappingKeeps private bind mounts writable without granting world access.
Persistent statePer-cell mounts; no shared state mountEnsures tenant configuration, credentials, sessions, and workspaces stay in that tenant's data tree.
Container commandnode dist/index.js gateway --bind lan --port 18789Listens on the container network so the loopback-only host port mapping can reach it.

Fleet never mounts /var/run/docker.sock, uses --privileged or host networking, or adds capabilities. The per-cell bridge acts as a cross-cell separation boundary, not an outbound firewall: cells retain the network egress required for providers and channels. Front the loopback port with a proxy, SSH tunnel, or tailnet configuration matching your deployment. http://127.0.0.1:<port> is only reachable directly from the Fleet host.

This profile isolates tenant containers but does not protect tenants from the Fleet operator, the container runtime administrator, or a compromised host. For the complete trust model and stronger isolation options, refer to Multi-tenant hosting.

Token handling

By default, fleet create generates a cryptographically random 32-character hexadecimal Gateway token and outputs it once in the create result. Store this token in your approved secret manager and avoid capturing create output in logs.

--gateway-token passes a custom token in the local process arguments, which may appear in shell history or process listings. Use the generated token unless an existing secret-management workflow requires a supplied value.

The token and all values passed with --env reside in the container environment. Fleet writes them to a short-lived mode-0600 environment file, passes only that file's path to Docker or Podman, and removes it after the runtime command finishes. Values explicitly typed in openclaw fleet create --gateway-token ... or --env KEY=VALUE can still be visible in the outer openclaw process arguments and shell history.

Container environment values are not hidden from the trusted host operator: Docker or Podman administrators can read them through container inspection. Fleet's "shown once" note describes normal CLI output, not resistance to a host administrator.