How-toAi Tools11 min read
OpenClaw Docker Setup: Complete 2026 Tutorial with Docker Compose
OpenClaw ships as a Node.js package — there's no official Docker image. This is the community-tested Dockerfile and docker-compose.yml we use to run OpenClaw in a container, with persistent volumes, reverse-proxy HTTPS, and clean update semantics. Deployed on a Hostinger VPS in under 20 minutes.
Omer YLD
Founder & Editor-in-Chief
11 min · 2,200 words
Photo: Technerdo
By the end of this guide you'll have OpenClaw running in a clean Docker Compose stack on a fresh VPS, with persistent credential storage, reverse-proxy HTTPS via Caddy, and a one-command update path. The whole setup takes about 20 minutes on a server with Docker already installed, and gives you the same functionality as a native OpenClaw install with the operational benefits of containers.
A note up front: OpenClaw does not publish an official Docker image. The canonical install path in the OpenClaw documentation is npm install -g openclaw. The setup below is a community pattern — a lightweight wrapper image built from the official Node base — that we've run continuously for eight weeks on a Hostinger VPS without issues.
If you want to run OpenClaw natively instead, our OpenClaw self-hosting guide for beginners covers the npm install flow. This post is for people who prefer Docker for every service on principle — and that's a reasonable principle.
What You'll Need
- A Linux VPS with root SSH access. We use a Hostinger VPS with 4 GB RAM; anything from 2 GB up handles OpenClaw cleanly.
- Docker and Docker Compose installed on the VPS. If not,
curl -fsSL https://get.docker.com | sudo shhandles it. - A domain name pointed at the VPS, with DNS under your control. A subdomain like
openclaw.yourdomain.comworks fine. - An LLM provider API key (Anthropic, OpenAI, OpenRouter, or whichever provider you'll use with OpenClaw).
- About 20 minutes start to finish.
Understand Why Docker and Not Native
Before we build: understand the tradeoff you're making.
Native OpenClaw is a global npm install plus a daemon registration. It's the official path, the documentation assumes it, and it works fine. The downside is that your OpenClaw install is entangled with your host system's Node version — if another tool needs a different Node, you have a problem.
Docker OpenClaw pins the Node version inside the container, isolates the install from the rest of your system, makes updates atomic (pull new image, restart), and makes the whole service easy to back up (back up one volume instead of ~/.openclaw plus whatever npm global path). The downsides are an extra layer to reason about when something goes wrong, and slightly higher idle memory.
For anyone running Docker for other services already, this is an easy call. For a fresh VPS with nothing else on it, either approach is fine.
Prepare a Working Directory on the VPS
SSH into the VPS as a non-root user with sudo access:
ssh you@your.vps.ip
Create a project directory:
mkdir -p ~/openclaw-docker && cd ~/openclaw-docker
This is where our Dockerfile, Compose file, and Caddy configuration will live.
Create the Dockerfile
OpenClaw requires Node 22.14+ LTS or Node 24. We'll use the official node:22-bookworm-slim image as a base — small, stable, and reliable.
Create Dockerfile:
FROM node:22-bookworm-slim
# OpenClaw version pin — bump deliberately, never use "latest" in production
ARG OPENCLAW_VERSION=latest
# System dependencies — git for any plugins that need it, tini for proper signal handling
RUN apt-get update \
&& apt-get install -y --no-install-recommends git ca-certificates tini \
&& rm -rf /var/lib/apt/lists/*
# Install OpenClaw globally
RUN npm install -g openclaw@${OPENCLAW_VERSION} \
&& npm cache clean --force
# Create a non-root user so OpenClaw doesn't run as root
RUN useradd -m -u 1000 openclaw
USER openclaw
WORKDIR /home/openclaw
# OpenClaw dashboard port
EXPOSE 18789
# Tini as PID 1 for clean signal handling on shutdown
ENTRYPOINT ["/usr/bin/tini", "--"]
# Default command — run OpenClaw in foreground (no daemon flag inside a container)
CMD ["openclaw", "serve"]
A few deliberate choices worth noting:
- Pin the OpenClaw version via build arg. For testing use
latest, for production pin to a specific version (for example2.1.0) and update deliberately. - Non-root user — always. A service that doesn't need root should never have it.
- Tini as PID 1 — Node doesn't handle Unix signals cleanly as PID 1, and Tini is the canonical fix inside Docker containers.
openclaw serveinstead of--install-daemon— inside a container, foreground is correct. Docker handles the daemon lifecycle viarestart: unless-stopped.
Create the docker-compose.yml
Create docker-compose.yml alongside the Dockerfile:
services:
openclaw:
build:
context: .
args:
OPENCLAW_VERSION: "latest"
container_name: openclaw
restart: unless-stopped
volumes:
- ./data:/home/openclaw/.openclaw
environment:
- OPENCLAW_DASHBOARD_HOST=0.0.0.0
- OPENCLAW_DASHBOARD_PORT=18789
expose:
- "18789"
networks:
- web
caddy:
image: caddy:2
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
networks:
- web
networks:
web:
volumes:
caddy_data:
caddy_config:
Why this shape:
- The
datavolume mounts./dataon the host to~/.openclawinside the container. Credentials (paired WhatsApp accounts, channel tokens) andopenclaw.jsonpersist here and survive rebuilds. OPENCLAW_DASHBOARD_HOST=0.0.0.0overrides the default loopback-only bind so the dashboard is reachable from the Caddy container on the sharedwebnetwork.expose(notports) — the dashboard is only reachable from inside Docker, not directly from the internet. Caddy provides the public entry point with HTTPS.- Caddy handles Let's Encrypt automatically and proxies to OpenClaw.
Create the Caddyfile
Create Caddyfile:
openclaw.yourdomain.com {
reverse_proxy openclaw:18789
}
Replace openclaw.yourdomain.com with your actual subdomain. That's the entire reverse-proxy config — Caddy handles TLS acquisition, renewal, and the proxy in one block.
Point DNS at Your VPS
In your DNS provider's dashboard, create an A record for openclaw.yourdomain.com pointing at the VPS's IPv4 address. TTL 300 seconds is fine for initial setup.
Verify from a different machine:
dig +short openclaw.yourdomain.com
You should see the VPS IP. If it's empty, wait 2–5 minutes for propagation.
Build and Start the Stack
From ~/openclaw-docker:
docker compose build
docker compose up -d
Watch the startup logs:
docker compose logs -f
You should see Caddy acquire a Let's Encrypt certificate (about 30 seconds) and OpenClaw initialize its dashboard. When the logs settle, open https://openclaw.yourdomain.com in a browser. The OpenClaw Control UI appears with a valid HTTPS lock icon.
Configure OpenClaw for First Use
The dashboard will prompt you to provide an LLM provider API key and select channels to enable. This is identical to the native install flow — the Docker container just puts it behind HTTPS.
For channel setup that requires QR pairing (WhatsApp, Signal), use docker exec:
docker exec -it openclaw openclaw channels login --channel whatsapp
The QR code renders in your terminal. Scan with the WhatsApp app. Credentials write to /home/openclaw/.openclaw/credentials/whatsapp/... inside the container, which maps to ./data/credentials/whatsapp/... on the host via the volume. Rebuilds and restarts preserve them.
For a full walkthrough of WhatsApp-specific configuration, see our OpenClaw WhatsApp integration guide — every step applies inside the container.
Lock Down the Dashboard
By default the dashboard accepts connections on the shared Docker network. Before you invite anyone else to use the gateway, add authentication to the Caddy config:
openclaw.yourdomain.com {
basicauth {
you $2a$14$BcryptedPasswordHashHere
}
reverse_proxy openclaw:18789
}
Generate a bcrypt hash with caddy hash-password. Reload Caddy with docker compose restart caddy. Now the dashboard requires a username and password.
For production, consider putting the dashboard behind something stronger — a Tailscale network, a WireGuard endpoint, or a proper SSO proxy like Authelia. Basic auth is acceptable for a single-operator setup; multi-user deployments deserve more.
Update OpenClaw Cleanly
The update flow is deliberately deliberate. In docker-compose.yml, change the version arg:
build:
context: .
args:
OPENCLAW_VERSION: "2.1.1"
Then:
docker compose build --no-cache openclaw
docker compose up -d
The --no-cache flag ensures a fresh npm install of the pinned version. Credentials and config persist in the data volume, so the rebuild doesn't re-pair channels or reset configuration.
Always read OpenClaw's changelog for the version you're upgrading to — breaking changes in agent frameworks are rare but do happen.
Back Up Your Config and Credentials
The ./data directory is everything — credentials, config, session state. Back it up daily:
tar czf ~/backups/openclaw-$(date +%Y-%m-%d).tar.gz -C ~/openclaw-docker data
For a production-grade backup strategy, pipe this through Restic to offsite object storage. A Backblaze B2 bucket is pennies per month and gives you encrypted, versioned backups. We cover this pattern in our self-hosted Bitwarden guide — the backup script transfers directly.
Troubleshooting Common Issues
Caddy can't acquire a TLS certificate. Check that your domain's A record actually points at the VPS IP (dig +short openclaw.yourdomain.com from the VPS itself), that port 80 is open (sudo ufw status), and that no other process is listening on port 80. Let's Encrypt needs port 80 for the HTTP-01 challenge.
Dashboard returns 502 Bad Gateway. Means Caddy is up but OpenClaw isn't reachable. Check docker compose logs openclaw — the most common cause is OpenClaw binding to loopback only. Confirm OPENCLAW_DASHBOARD_HOST=0.0.0.0 is set in the Compose file.
QR code doesn't display over SSH. Some terminal emulators render the inline QR poorly. Run docker exec -it openclaw openclaw channels login --channel whatsapp from a local terminal that you know handles Unicode cleanly. Alternatively, redirect the QR output to a file and render it locally.
Container restarts repeatedly. docker compose logs openclaw | tail -50 will usually show a clear error. Common causes: malformed openclaw.json in the data volume, invalid API keys, or a Node version mismatch. The fix is almost always in the log.
Channel pairings disappear after rebuild. The data volume isn't mounted correctly — run docker compose config to confirm the volume mapping matches ./data:/home/openclaw/.openclaw. If it does, check that the files are actually present on the host (ls ~/openclaw-docker/data/credentials/).
Is This Docker Setup Better Than Running OpenClaw Natively?
Better in some ways, not all. It's better for reproducibility, cleaner updates, and isolating OpenClaw from the rest of the host. It's not better for absolute minimum footprint or for first-time learners who haven't worked with Docker before. For anyone already running Docker for other services, the Docker setup is clearly the right call. For a learner who's new to containers and just wants OpenClaw working, the native install is the friendlier path.
What to Do Next
Now that OpenClaw is running in Docker:
- Configure your channels. Start with WhatsApp, then add Telegram, Discord, and any others you need.
- Compare architectures — if you're choosing between agent frameworks, our OpenClaw vs NanoClaw and OpenClaw vs Hermes Agent pieces cover the tradeoffs in detail. Both competitors have Docker-native deployment paths if that's the direction you want to go.
- Harden the VPS. Firewall, SSH keys only, fail2ban if you're exposed to the public internet. Our Bitwarden self-hosting guide covers baseline VPS hardening — the same steps apply here.
- Add monitoring. A free-tier Uptime Kuma plus Healthchecks.io tells you the moment a channel stops responding. Worth doing before you depend on the agent for anything real.
A containerized OpenClaw install is the mature self-hoster's setup. It takes a little more thought than a straight npm install, but the reproducibility and clean operations pay back every time you update, migrate, or restore from backup.
Was this piece worth your five minutes?
Join the conversation — sign in to leave a comment and engage with other readers.
Loading comments...



