Docker Deployment
Docker deployment
Docker is optional for running OpenClaw. The direct Node.js install (macOS LaunchAgent, systemd on Linux) is simpler for most people. Docker earns its place when you need the same image to run identically across machines (reproducibility), want no Node version conflicts with other projects on the host (isolation), need to move to a new server by repointing the volume mounts (portability), or are running Gateway + Langfuse + other services together in one Compose file.
One thing to be clear about: the Gateway running in Docker is just the Gateway. The LLM models still run in the cloud. You're not trying to run Claude or GPT inside a container. Docker here means OpenClaw's control plane runs in a container; it still calls out to Anthropic/OpenAI over the internet.
Quick start
From the OpenClaw repo root:
./docker-setup.sh
This script:
- Builds the gateway image (
openclaw:local) - Runs the onboarding wizard in a container
- Prints optional provider setup hints
- Starts the Gateway via Docker Compose
- Generates a gateway token and writes it to
.env
After it finishes:
# Open the control UI in your browser
open http://127.0.0.1:18789/
# Paste the token from .env into Settings → Token
The Docker Compose file
The generated docker-compose.yml looks roughly like this:
version: "3.8"
services:
openclaw-gateway:
image: openclaw:local
restart: unless-stopped
volumes:
- ~/.openclaw:/home/node/.openclaw # config + sessions
- ~/.openclaw/workspace:/home/node/.openclaw/workspace # agent workspace
ports:
- "127.0.0.1:18789:18789" # loopback only (secure default)
environment:
- NODE_ENV=production
- OPENCLAW_GATEWAY_TOKEN=${OPENCLAW_GATEWAY_TOKEN}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
# Add your other API keys here
openclaw-cli:
image: openclaw:local
volumes:
- ~/.openclaw:/home/node/.openclaw
- ~/.openclaw/workspace:/home/node/.openclaw/workspace
profiles: ["cli"] # only starts when you explicitly run it
entrypoint: ["openclaw"]
Volume mounts: what goes where
The two critical mounts are:
| Host path | Container path | What it holds |
|---|---|---|
~/.openclaw/ |
/home/node/.openclaw/ |
Config (openclaw.json), sessions, channel credentials, skills |
~/.openclaw/workspace/ |
/home/node/.openclaw/workspace/ |
Agent workspace (AGENTS.md, SOUL.md, memory, etc.) |
These mounts mean state persists outside the container. You can delete and recreate the container without losing sessions, paired channels, or workspace files.
Permissions: the image runs as
node(UID 1000). If you seeEACCESerrors on host volumes, fix ownership:sudo chown -R 1000:1000 ~/.openclaw
Optional: persist the entire container home
If you want to keep caches (Playwright browsers, model downloads) across container recreation:
export OPENCLAW_HOME_VOLUME="openclaw_home"
./docker-setup.sh
This creates a named Docker volume mounted at /home/node, with the standard config/workspace bind mounts on top.
Networking: loopback vs bridge
Default: loopback binding
The default Compose config binds port 18789 to 127.0.0.1 (loopback) only:
ports:
- "127.0.0.1:18789:18789"
This means the Gateway is only reachable from the same machine. Secure by default, the Control UI isn't exposed to the network.
To access the Control UI from another machine, use an SSH tunnel:
# From your laptop, tunnel to the Docker host
ssh -L 18789:localhost:18789 user@docker-host
# Then open http://localhost:18789 on your laptop
LAN / Tailnet binding
To expose the Gateway to your local network or Tailscale:
ports:
- "0.0.0.0:18789:18789" # all interfaces (LAN)
Or configure via OpenClaw:
openclaw config set gateway.bind lan # LAN
openclaw config set gateway.bind tailnet # Tailscale IP only
Never expose 18789 to the public internet without authentication. The default token-based auth is sufficient for Tailscale, but if you open it publicly, use a reverse proxy with TLS plus the gateway's token auth (or add a password via
gateway.auth.mode).
Host networking
For channels that need low-level network access (uncommon), you can use network_mode: host:
services:
openclaw-gateway:
network_mode: host
This gives the container direct access to the host's network interfaces. Usually not needed.
Using the CLI container
The openclaw-cli service runs one-off commands against the same config/workspace:
# Check gateway status
docker compose run --rm openclaw-cli gateway status
# Link WhatsApp (shows QR code)
docker compose run --rm openclaw-cli channels login --channel whatsapp
# Approve a pairing request
docker compose run --rm openclaw-cli pairing approve telegram <CODE>
# View logs
docker compose logs -f openclaw-gateway
Multi-container setups: Gateway and services
One of Docker's best features is Compose files that bring up your whole stack. A useful pattern:
version: "3.8"
services:
openclaw-gateway:
image: openclaw:local
restart: unless-stopped
volumes:
- ~/.openclaw:/home/node/.openclaw
ports:
- "127.0.0.1:18789:18789"
depends_on:
- langfuse
environment:
- LANGFUSE_HOST=http://langfuse:3000
- LANGFUSE_PUBLIC_KEY=${LANGFUSE_PUBLIC_KEY}
- LANGFUSE_SECRET_KEY=${LANGFUSE_SECRET_KEY}
langfuse:
image: ghcr.io/langfuse/langfuse:latest
ports:
- "127.0.0.1:3000:3000"
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/langfuse
depends_on:
- db
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=password
- POSTGRES_DB=langfuse
volumes:
pgdata:
Now openclaw gateway and your Langfuse observability stack come up with a single docker compose up -d.
Agent sandboxing in Docker
OpenClaw can run agent tool execution (the exec tool) inside isolated Docker containers, even when the Gateway itself isn't in Docker. This is the sandbox feature:
{
agents: {
defaults: {
sandbox: {
mode: "non-main", // sandbox non-main sessions only
scope: "agent", // one container per agent
docker: {
image: "openclaw-sandbox:bookworm-slim",
network: "none", // no internet access in sandbox
memory: "512m"
}
}
}
}
}
Build the sandbox image:
scripts/sandbox-setup.sh
This is separate from the Gateway container. It's a security boundary for tool execution, not a deployment choice.
Updating the Gateway container
# Pull latest
docker build -t openclaw:local -f Dockerfile .
# Or: docker pull openclaw/openclaw:latest (if using published images)
# Restart the Gateway with the new image
docker compose up -d --force-recreate openclaw-gateway
Run openclaw doctor after updates to catch any config schema changes:
docker compose run --rm openclaw-cli doctor
Troubleshooting
Container exits immediately
docker compose logs openclaw-gateway
# Look for startup errors — usually a missing required config value
Permission errors on volumes
# Fix ownership on the host
sudo chown -R 1000:1000 ~/.openclaw
WhatsApp QR not appearing
The QR scan needs an interactive terminal:
docker compose run --rm -it openclaw-cli channels login --channel whatsapp
Gateway is unreachable after container restart
Check the port binding and whether the old container is still running:
docker compose ps
docker compose down && docker compose up -d
Exercise
- If you have Docker installed, run
docker compose up -dfrom an OpenClaw project directory and watch the Gateway come up in a container. - Use
docker compose run --rm openclaw-cli gateway statusto verify the container is healthy. - Think through it: you're moving your OpenClaw setup to a new server. What do you need to copy? (Hint: what's in the volumes?)
In the next lesson, we'll cover Linux VPS deployment, the right approach for an always-on server you can rely on.