How do you run OpenClaw with Docker Compose in 2026?

OpenClaw docker compose setup vs managed hosting. Compare DIY compose.yml on VPS with Agent 37 from $3.99/mo and ClawHosters for EU.

Published on

•

How do you run OpenClaw with Docker Compose in 2026?
Do not index
You run OpenClaw with Docker Compose by cloning the openclaw/openclaw repository, running ./docker-setup.sh to create the host directories and .env file, building openclaw:local or pointing the compose file at a published image, and starting the openclaw-gateway service with docker compose up -d. If you would rather not own the YAML at all, Agent 37 runs OpenClaw on managed, persistent, always-on instances from $3.99/mo as of October 2026.
This guide covers the compose file line by line, the exact command sequence from a fresh clone, post-start configuration for the gateway token and AI provider key, Telegram and Discord setup, image updates without data loss, the differences between Docker Desktop on Mac, Docker Engine on Ubuntu, and Portainer, and the failures we see most often. For Docker without Compose, see OpenClaw Docker.
notion image

What does the official OpenClaw docker-compose.yml contain?

The upstream docker-compose.yml in the openclaw/openclaw repository defines a single openclaw-gateway service. It builds or pulls the image, loads secrets from a .env file, maps the gateway port, mounts a config directory and a workspace directory as volumes, and adds a host-gateway extra_hosts entry so Linux Docker Engine can reach services on the host.
The file sits at the root of the openclaw/openclaw repository on GitHub next to docker-setup.sh and the Dockerfile. Below is an annotated version with the same shape as the upstream file as of October 2026. The variable names follow the convention docker-setup.sh writes into .env. Diff this against the current upstream file before you deploy, because the project ships often and keys get renamed.
services:
  openclaw-gateway:                      # the only required service
    image: openclaw:local                # local build tag; swap for a published image to skip the build
    build:
      context: .                         # builds from the cloned repo
      dockerfile: Dockerfile
    container_name: openclaw-gateway
    restart: unless-stopped              # comes back after a host reboot on Linux Docker Engine
    env_file:
      - .env                             # gateway token, provider keys, port; never committed to git
    environment:
      - OPENCLAW_GATEWAY_TOKEN=${OPENCLAW_GATEWAY_TOKEN}
      - OPENCLAW_GATEWAY_BIND=lan        # listen on the container interface so the port map works
    ports:
      - "127.0.0.1:${OPENCLAW_GATEWAY_PORT}:${OPENCLAW_GATEWAY_PORT}"   # loopback only; put a proxy in front
    volumes:
      - ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw                   # config, sessions, channel state
      - ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace       # files the agent reads and writes
    extra_hosts:
      - "host.docker.internal:host-gateway"   # Linux Docker Engine only; lets the container reach the host
Here is what each block does in plain language:
  • `openclaw-gateway` is the service name. Every docker compose exec and docker compose logs command below uses it. If you rename it, update your commands.
  • `image` and `build` together mean "use openclaw:local if it exists, otherwise build it from this directory." Delete the build block and change image to a published tag if you want to pull instead of compile.
  • `restart: unless-stopped` is the restart policy. On a VPS this is what keeps the gateway alive after a kernel update reboot. It does nothing while Docker Desktop is closed on a Mac, which is why a laptop is not a host.
  • `env_file` loads .env from the project directory. This is where the gateway token, the provider API key, and the port live.
  • `ports` maps the gateway port to the host loopback address only. That is deliberate: the web UI should sit behind a reverse proxy with TLS or an SSH tunnel, never on 0.0.0.0 with a token as the only lock.
  • `volumes` are two bind mounts to directories on the host. The config directory holds the gateway config, sessions, and channel state. The workspace directory holds the files the agent works on. Everything you care about lives in these two paths.
  • `extra_hosts` adds host.docker.internal pointing at the Docker bridge gateway. Docker Desktop provides this name automatically; Linux Docker Engine does not, so without this line a container on Ubuntu cannot reach an Ollama server or a local database on the host.
Before running it, change four things: the two directory paths (absolute paths are safest, especially in Portainer), the port if something already listens on it, and the token if you did not let docker-setup.sh generate one.
[Book a 30 minute call with Agent 37](https://cal.com/vishnukool/30min)

What are the exact commands to get OpenClaw running with Docker Compose from a fresh clone?

From a fresh clone, the sequence is: install Docker Engine with the Compose v2 plugin, clone openclaw/openclaw, run ./docker-setup.sh to create the host directories and .env, then either build openclaw:local with docker compose build or point the image line at a published tag, and finish with docker compose up -d followed by docker compose logs -f.
Prerequisites first. On Linux you want Ubuntu 22.04 or 24.04 or Debian 12 on a VM with 2 vCPU, 4 GB RAM, and 20 GB of disk. You need Docker Engine 24 or newer and Compose v2, which runs as docker compose with a space, not the legacy docker-compose binary. The Docker Compose install documentation covers both the Linux plugin and Docker Desktop for Mac, and Docker Desktop already bundles Compose v2.
# 1. Docker Engine + Compose v2 on Ubuntu/Debian (skip on Docker Desktop)
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER && newgrp docker
docker compose version

# 2. Clone the repo
git clone https://github.com/openclaw/openclaw.git
cd openclaw

# 3. Create host directories and .env, print the generated gateway token
./docker-setup.sh

# 4a. Build the image from source (several minutes the first time)
docker compose build

# 4b. Or pull a published image instead (edit image: in docker-compose.yml first)
docker compose pull

# 5. Start detached and tail the logs
docker compose up -d
docker compose logs -f openclaw-gateway
docker-setup.sh is a convenience wrapper, not magic. It checks that Docker is running, creates the config and workspace directories if they are missing, writes a .env with a random gateway token and the default port, and on most versions offers to build and start the stack for you. If you prefer to see every step, read the script once and run the commands yourself; it is short.
Building versus pulling. Building openclaw:local compiles from whatever commit you cloned, so you get the newest code and can patch it. It costs a few minutes and a couple of gigabytes of build cache. Pulling a published image such as alpine/openclaw on Docker Hub takes seconds. Despite the name, that image is Debian-based: native Node dependencies did not behave on musl, so the maintainers moved to a glibc base. If you pull, delete the build block so a stray docker compose build cannot overwrite your tag.

How do you configure the gateway token and AI provider API key inside the container?

The gateway token and your AI provider key both live in the .env file on the host, which Compose injects as environment variables at start. The gateway writes its own config into the mounted config directory, so both survive docker compose down. Never bake keys into the image or the compose file itself.
A working .env looks like this. Replace the values and keep the file out of git with a .gitignore entry.
OPENCLAW_GATEWAY_TOKEN=paste-the-token-docker-setup-generated
OPENCLAW_GATEWAY_PORT=18789
OPENCLAW_CONFIG_DIR=/home/ubuntu/.openclaw
OPENCLAW_WORKSPACE_DIR=/home/ubuntu/.openclaw/workspace

# one or more providers; the onboarding flow asks which to use
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...

# optional: a local Ollama server on the host, reached through the host-gateway mapping
OLLAMA_BASE_URL=http://host.docker.internal:11434
The gateway token is what the web UI and the CLI present to the gateway. If you rotate it, change it in .env and run docker compose up -d again; Compose notices the environment changed and recreates the container. The provider key goes directly from the container to Anthropic or OpenAI. Nothing else on the host needs it.
On first start the gateway runs an onboarding flow, either in the web UI or through docker compose exec openclaw-gateway openclaw onboard (check the upstream docs for the current subcommand name). Whatever you set there is written to the config directory you mounted, which is why that mount matters: skip it and every docker compose down erases your onboarding.

How do you connect Telegram or Discord and reach the OpenClaw web UI?

Once the container reports healthy, open the web UI at http://localhost followed by the mapped gateway port, paste the gateway token when prompted, then add channels from the onboarding flow or by running the channel setup command through docker compose exec. Telegram needs a bot token from BotFather; Discord needs a bot token and server invite.
On your own machine the UI is at http://127.0.0.1:18789 (or whatever port you set). On a VPS the port is bound to loopback, so either open an SSH tunnel with ssh -L 18789:127.0.0.1:18789 user@your-vps and browse locally, or put Caddy or Traefik in front with a real hostname and TLS. We cover the proxy block in the platform section below.
Telegram. Create a bot with BotFather, copy the token, and add it through the channel setup in the UI or by running the gateway's channel command through docker compose exec openclaw-gateway. Message the bot once; the gateway logs will show the pairing request so you can approve the chat.
Discord. Create an application in the Discord developer portal, add a bot user, enable the message content intent, and invite it to your server with the permissions the OpenClaw channel docs list. Paste the bot token into the channel setup the same way.
Slack and WhatsApp. Slack uses an app-level token plus a bot token. WhatsApp pairs by QR code, which the gateway prints to the logs, so keep docker compose logs -f openclaw-gateway open while you scan.
All channel sessions are stored in the mounted config directory. Back it up and your bots keep their pairings across hosts.

How do you run admin commands, view logs, or get a root shell in the running container?

Every day-two operation maps to a Compose subcommand run from the project directory: docker compose logs -f for output, docker compose exec openclaw-gateway for CLI commands as the container user, docker compose exec -u root openclaw-gateway bash for a root shell, and docker compose restart to bounce the gateway without touching volumes.
The project used to ship a helper called ClawDock that wrapped these. It has been retired in favor of plain Compose commands, which is better: there is nothing extra to learn or keep updated. Run everything from the directory that holds docker-compose.yml, or pass -f /path/to/docker-compose.yml from anywhere.
Task
Command
Notes
Start the stack
docker compose up -d
Recreates only what changed
Stop and remove containers
docker compose down
Volumes and host directories are untouched
Restart the gateway
docker compose restart openclaw-gateway
Keeps the container, reloads the process
Tail logs
docker compose logs -f --tail=200 openclaw-gateway
Add --since 10m to narrow
Run a CLI command
docker compose exec openclaw-gateway openclaw status
Runs as the container user
Root shell
docker compose exec -u root openclaw-gateway bash
For package installs and permission fixes
Check resource use
docker stats openclaw-gateway
Watch RAM during browser tool calls
Validate the file
docker compose config
Prints the resolved YAML with env substituted
Throwaway copy
docker compose -p scratch up -d
Separate project name, separate containers
The last row is how you get a disposable gateway environment for testing a new version or a risky skill: a different project name and different directory paths give you a second, isolated stack on the same host. Delete it with docker compose -p scratch down when you are done. This is the Compose equivalent of the Docker VM runtime that OpenClaw can use for sandboxing individual tool runs, and the distinction matters: a disposable sandbox forgets everything by design, while your main install must persist.

How do you update the OpenClaw image without losing data?

Updating never touches your data as long as state lives in the mounted host directories, not inside the container. Pull or rebuild the image, then run docker compose up -d and Compose recreates only the gateway container. Pin a tag or digest in production so a Friday pull cannot silently change the runtime underneath you.
For a published image:
docker compose pull
docker compose up -d
docker image prune -f   # remove the old layers
For a local build:
git pull
docker compose build
docker compose up -d
Add --no-cache to the build only when a dependency step is misbehaving; it roughly doubles build time.
Pinning. image: alpine/openclaw:latest is fine on a laptop and a liability on a server. Use a version tag, or better, a digest: image: alpine/openclaw@sha256:.... Get the digest with docker inspect --format '{{index .RepoDigests 0}}' alpine/openclaw:latest after a pull you have tested. Commit the compose file with the pin so a rollback is a git revert plus docker compose up -d.
Migrating existing state. Because everything lives in the two mounted directories plus .env, moving to a new host is four steps: docker compose down on the old host, tar czf openclaw-state.tgz ~/.openclaw .env docker-compose.yml, copy the archive to the new host, extract it in place, and docker compose up -d. Channel pairings, sessions, and workspace files all come along. If the user ID inside the container differs from the file owner on the new host, see the permissions fix in the troubleshooting section.

Does OpenClaw Docker Compose work on Mac, Ubuntu VPS, and Portainer?

The same compose file runs on Docker Desktop for Mac, Docker Engine on Ubuntu or Debian, and inside Portainer as a stack. The one real difference is networking: Docker Desktop resolves host.docker.internal automatically, while Linux Docker Engine needs the host-gateway extra_hosts line. On a VPS, add a firewall and a reverse proxy.
Docker Desktop on Mac. Install Docker Desktop, confirm docker compose version prints a v2 number, then follow the clone and setup steps unchanged. The extra_hosts line is harmless here. Two Mac-specific notes: bind mounts into the macOS filesystem are slower than native Linux, so a large workspace will feel sluggish on heavy file operations, and the gateway stops the moment Docker Desktop quits or the laptop sleeps. A Mac is a fine place to develop and a poor place to leave a 24/7 channel bot.
Ubuntu or Debian VPS. Keep the port bound to 127.0.0.1 and open only SSH and the proxy ports. With ufw, the Ubuntu community guide to UFW covers the basics; the three rules you need are ufw allow 22/tcp, ufw allow 80,443/tcp, and ufw enable. A minimal Caddy reverse proxy gives you TLS with no extra config:
openclaw.example.com {
    reverse_proxy 127.0.0.1:18789
}
The restart: unless-stopped policy brings the gateway back after a reboot as long as the Docker service itself is enabled, which get.docker.com does by default. You do not need a systemd unit for the container; you do need systemctl enable docker if you installed from distribution packages. For a multi-tenant host, NIST SP 800-190 is the reference for the container isolation risks you take on when several clients share one Docker Engine.
Portainer. In Portainer, go to Stacks, Add stack, choose the web editor, and paste the compose file. Portainer does not run docker-setup.sh, so create the config and workspace directories on the host first, use absolute paths in the environment variables, and enter the variables in the stack's Environment section instead of relying on a .env file. Deploy, then use the container console in Portainer for the same exec commands listed above.
[Book a 30 minute call with Agent 37](https://cal.com/vishnukool/30min)

What breaks most often in an OpenClaw Compose setup and how do you fix it?

Most first-run failures fall into five buckets: permission errors on the mounted host directories, a port already in use on the host, a gateway token that does not match between .env and the stored config, an image build that fails on a dependency step, and Mac versus Linux networking differences. Each has a two-minute fix.
  1. `EACCES` or `permission denied` on `/home/node/.openclaw`. The container runs as a non-root user with a fixed UID. Fix the owner on the host: docker compose exec -u root openclaw-gateway chown -R node:node /home/node/.openclaw, or sudo chown -R 1000:1000 ~/.openclaw on the host if the image uses UID 1000. Check with docker compose exec openclaw-gateway id.
  1. `bind: address already in use`. Something on the host already listens on the gateway port. Find it with sudo ss -ltnp | grep 18789, then either stop it or change OPENCLAW_GATEWAY_PORT in .env and run docker compose up -d.
  1. Web UI rejects the token. You changed .env but the stored config still has the old value, or you pasted the token with a trailing newline. Run docker compose config to see what Compose is actually injecting, fix .env, then docker compose up -d to recreate the container.
  1. Build fails on a native dependency. Usually a stale build cache after a git pull. Run docker compose build --no-cache. If it still fails, switch to the published image for now and file the error upstream with the failing layer in the log.
  1. Container cannot reach Ollama or a host database. On Linux this is the missing extra_hosts line; on Mac it is Ollama binding only to 127.0.0.1 instead of 0.0.0.0. Test from inside the container with docker compose exec openclaw-gateway curl -s http://host.docker.internal:11434/api/tags.
A sixth one that is not a bug: the gateway looks fine, then dies during a browser tool call. That is an out-of-memory kill, and the fix is the sizing section below.

How much RAM and CPU does the gateway need, and how do you back up the volumes?

Give the gateway at least 2 vCPU and 4 GB RAM for stable tool calls and channel polling, keep all state in the two mounted directories, and back them up with a nightly tar to object storage. Migrating hosts is then a copy of those directories plus the .env file and a docker compose up -d.
Idle, the gateway uses a few hundred megabytes. A headless Chromium tool call can spike it past 2 GB, and that is where 2 GB VPS plans fall over. If you must run on 2 GB, add a deploy.resources.limits.memory of 1800m so the container, not the whole host, is what gets killed, and add 2 GB of swap.
A backup is one line in cron:
0 3 * * * tar czf /backups/openclaw-$(date +%F).tgz -C /home/ubuntu .openclaw && rclone copy /backups remote:openclaw-backups
Restore is the migration procedure from the update section. Test it once before you need it.

When is managed OpenClaw hosting better than running Compose yourself?

Run Compose yourself when you already operate Docker in production and want custom sidecars, networks, or image pins. Choose managed OpenClaw hosting when you want the same container isolation without owning patches, TLS, and upgrades. Agent 37 runs OpenClaw on persistent, always-on instances from $3.99 a month, with a browser terminal and file browser.
notion image
Everything above is roughly two hours the first time and about thirty minutes per upstream release after that, plus whatever a 3 a.m. restart costs you. That is a reasonable trade for a platform engineer and a bad one for a founder who wanted a Telegram bot before lunch. Gartner expects task-specific agents in 40 percent of enterprise applications by 2026, and most of the teams shipping those will not want to maintain a compose file per agent.
Managed OpenClaw hosting on Agent 37 gives you the same model as the compose file: one isolated container per instance, your API keys sent directly to Anthropic or OpenAI with Agent 37 never touching them, files and memory that survive between tasks, automatic runtime updates and security patches, and a full TTY terminal, file browser, and live Linux desktop in the browser instead of SSH. It deploys in one click from $3.99/mo as of October 2026, with a bring-your-own-keys model and 1000+ integrations through Composio. If you already have a Docker image you like, Agent 37 Cloud provisions persistent instances from Docker images through one API call from $4.76/mo per instance, metered per minute from a prepaid balance, and a $5 starter credit covers the first instance.
Path
What you manage
Entry cost (October 2026)
Best for
Compose on Ubuntu VPS
Everything: Docker, firewall, TLS, updates, backups
About $4 to $6/mo compute plus your time
Platform engineers with Docker already in production
Compose on Docker Desktop (Mac)
The compose file; the host is your laptop
$0
Development and testing, not 24/7 bots
Compose via Portainer
Stack YAML and env vars through a UI
VPS cost plus Portainer
Teams that already run Portainer
Agent 37 managed instance
Nothing below the agent; browser terminal and task board included
From $3.99/mo
Founders, operators, agencies
Agent 37 Cloud (API)
Your image or template; Agent 37 runs the instance
From $4.76/mo per instance
Products that need one agent per user
notion image
The deciding question is whether the compose file is the product or the chore. If you enjoy owning it and your clients benefit from the control, keep it in git and pin your digests. If the agent is the product and the YAML is overhead, persistent AI agent hosting is the cheaper hour.

FAQ

Do I need the docker-setup.sh script, or can I just write the compose file myself?
You can skip it. The script only creates the two host directories, writes a .env with a random token and port, and offers to build. If you create the directories, write .env by hand with the variables shown above, and run docker compose up -d, you end up in the same place. Portainer users skip it by necessity.
How do I restart the OpenClaw gateway with Docker Compose after changing .env?
Use docker compose up -d, not docker compose restart. A plain restart reuses the existing container, which still carries the old environment. up -d compares the resolved config, sees the environment changed, and recreates the container with the new values. Your mounted config and workspace directories are not affected either way.
Can I run OpenClaw Docker Compose with Ollama instead of a cloud API key?
Yes. Run Ollama on the host, bind it to 0.0.0.0:11434, keep the host.docker.internal:host-gateway line in extra_hosts on Linux, and set the Ollama base URL to http://host.docker.internal:11434 in .env. Verify from inside the container with curl before onboarding. Budget RAM for both the model and the gateway, since they share the host.
What ports need to be open on a VPS firewall for OpenClaw?
Only SSH (22) and your reverse proxy (80 and 443). The gateway port itself should stay bound to 127.0.0.1 in the compose file so it is never reachable directly. Telegram, Discord, Slack, and WhatsApp are all outbound connections from the container, so no inbound rule is required for channels. If you expose the gateway port publicly, the token is your only protection.
Can an agency run one OpenClaw Compose stack per client on a single VPS?
You can with separate project names and directories, but every client then shares one Docker Engine, one kernel, and one failure domain, and you own patching for all of them. Agent 37 Cloud provisions one isolated, always-on instance per client through a single API call, and the white-label dashboard built on the public API lets you resell those instances under your own brand.
[Book a 30 minute call with Agent 37](https://cal.com/vishnukool/30min)