I want to have multiple OpenClaw gateways running on my machine. Is that unrecommended at all? If not, how to set-up an OpenClaw gateway instance with Docker on WSL 2, allowing it to use things such as Git, Bun.js, Volta, Rustup/Cargo, Zig Build, Gradle and others? (I only need help with allowing the container to use these tools, not installing them, because I believe this is not necessarily Krill's responsibility or of any member that helps me here)
#How to install OpenClaw on Docker WSL 2 with custom port + coding environment?
1 messages Ā· Page 1 of 1 (latest)
Short version, yes, itās supported, but one Gateway per host is still the recommended default. Multiple Gateways are fine when you want hard isolation or a rescue bot. If you just want separate workspaces/auth, one Gateway with multiple agents is usually simpler: https://docs.openclaw.ai/concepts/multi-agent
For multiple Dockerized Gateways on WSL2, isolate each instance by:
- its own
/home/node/.openclawmount (config/state) - its own workspace mount
- its own published host port(s)
- its own Docker Compose project / compose file
I would not reuse one scripts/docker/setup.sh output for two long-lived instances, because it writes one shared docker-compose.extra.yml. Manual per-instance compose files are cleaner.
To let the container use your WSL2 Linux toolchains, mount the tool dirs into the container and prepend them to OpenClaw exec PATH. Example:
services:
openclaw-gateway:
image: openclaw:local
environment:
HOME: /home/node
CARGO_HOME: /home/node/.cargo
RUSTUP_HOME: /home/node/.rustup
BUN_INSTALL: /home/node/.bun
VOLTA_HOME: /home/node/.volta
GRADLE_USER_HOME: /home/node/.gradle
volumes:
- /home/YOU/.openclaw-rescue:/home/node/.openclaw
- /home/YOU/workspaces/rescue:/home/node/.openclaw/workspace
- /home/YOU/.cargo:/home/node/.cargo
- /home/YOU/.rustup:/home/node/.rustup:ro
- /home/YOU/.bun:/home/node/.bun:ro
- /home/YOU/.volta:/home/node/.volta:ro
- /home/YOU/.gradle:/home/node/.gradle
- /home/YOU/.local/zig:/home/node/.local/zig:ro
ports:
- "19001:18789"
- "19002:18790"
command: ["node","dist/index.js","gateway","--bind","lan","--port","18789"]
Then set exec PATH once for that instance:
docker compose -f compose.rescue.yml run --rm --no-deps --entrypoint node openclaw-gateway \
dist/index.js config set --batch-json '[{"path":"tools.exec.pathPrepend","value":["/home/node/.cargo/bin","/home/node/.bun/bin","/home/node/.volta/bin","/home/node/.local/zig"]}]'
Important caveats:
- Use WSL/Linux installs only. Windows binaries from
C:\...will not run in the Linux container.
- Git is already in the official image.
- Bun is fine as a tool, but Bun is not recommended as the Gateway runtime. The Gateway itself should still run on Node: https://docs.openclaw.ai/help/faq
- Mounting
~/.gradlegives Gradle home/caches, not Java.gradleor./gradlewstill need a JVM inside the container. - If you enable agent sandboxing, exec runs in sandbox containers by default, not the gateway container. Then the same tools must exist in the sandbox image too: https://docs.openclaw.ai/gateway/sandboxing
Docs:
- https://docs.openclaw.ai/gateway/multiple-gateways
- https://docs.openclaw.ai/install/docker
- https://docs.openclaw.ai/tools/exec
If you want, paste your exact WSL paths for Bun, Volta, Cargo, Zig, and Gradle, plus the two ports you want, and I can turn this into a ready-to-run 2-instance compose setup.
I mean, I don't want to share my toolchains with the Docker container. I want the Docker container to have it's own toolchains. Is it possible? Perhaps I need to use a custom DOCKERFILE? Also, how to use Docker builds from ghcr.io? But I think I'm running a single agent then. How to configure authentication in the most secure way possible?
I meant, a single gateway
Yep, thatās absolutely possible.
If you want the container to have its own isolated toolchains, the clean answer is yes, use a custom Dockerfile. Iād treat the official image as the base runtime, then layer Bun / Volta / Rustup / Zig / JDK / Gradle on top of it.
1. Use GHCR as the base image
You can run the stock image directly:
export OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:latest"
./scripts/docker/setup.sh
Published tags are latest, main, or a pinned version tag. Docs: https://docs.openclaw.ai/install/docker
If you want your own toolchains inside the container, make your own image from GHCR instead:
FROM ghcr.io/openclaw/openclaw:latest
USER root
# install Bun / Volta / Rustup / Zig / Java / Gradle here
USER node
ENV BUN_INSTALL=/home/node/.bun
ENV VOLTA_HOME=/home/node/.volta
ENV CARGO_HOME=/home/node/.cargo
ENV RUSTUP_HOME=/home/node/.rustup
ENV GRADLE_USER_HOME=/home/node/.gradle
ENV PATH="/home/node/.bun/bin:/home/node/.volta/bin:/home/node/.cargo/bin:/home/node/.local/zig:${PATH}"
Then point Compose at your image, not the stock one.
Two important notes:
- the runtime user is
node, so install user-level toolchains under/home/node, not/root OPENCLAW_DOCKER_APT_PACKAGESis fine for simple apt packages, but for Bun / Volta / Rustup / Zig, a custom Dockerfile is the better fit
2. Docker does not mean single-agent
A Dockerized Gateway can still run multiple agents.
āSingle containerā is not the same thing as āsingle agentā.
By default, OpenClaw starts with one agent, but you can add more with:
openclaw agents add <id>
3. Most secure auth setup
If your goal is āas secure as practicalā on WSL2, Iād do this:
- publish Docker ports to
127.0.0.1only - keep Gateway auth in token mode
- use a strong random token
- do not use
gateway.auth.mode: "none" - do not bake secrets into the image
- store provider keys on the gateway host, for example in
~/.openclaw/.envor via Docker secrets / env injection - if you need remote access, prefer Tailscale Serve or an SSH tunnel, not a raw public port
So your Docker port mapping should look more like:
ports:
- "127.0.0.1:19001:18789"
And for Gateway auth, the safest simple path is:
gateway.auth.mode: "token"(or let onboarding generate one)OPENCLAW_GATEWAY_TOKEN=<long-random-secret>
For long-lived provider auth, API keys are usually the most predictable path: https://docs.openclaw.ai/gateway/authentication
Also, avoid putting shared secrets in URLs. The FAQ explicitly calls that out: https://docs.openclaw.ai/help/faq
4. My recommendation
If you want:
- multiple gateways, use separate containers / compose projects / config volumes
- multiple coding personas/workspaces, keep one gateway and use multiple agents
- isolated toolchains, use a custom image based on GHCR
Thatās the cleanest model.
If you want, send:
- the toolchains you want baked in
- whether you want one gateway + many agents or many gateways
- whether access is local only or remote too
and I can sketch the exact Dockerfile + docker-compose.yml layout.
Ah, got it, single Gateway. Thatās much simpler š
Then yes, the clean setup is:
- one Gateway container
- custom image based on
ghcr.io/openclaw/openclaw - bake Bun / Volta / Rustup / Zig / JDK / Gradle into that image
- keep normal OpenClaw auth/token config on top
So the flow becomes:
FROM ghcr.io/openclaw/openclaw:latest
USER root
# install your extra toolchains here
USER node
ENV BUN_INSTALL=/home/node/.bun
ENV VOLTA_HOME=/home/node/.volta
ENV CARGO_HOME=/home/node/.cargo
ENV RUSTUP_HOME=/home/node/.rustup
ENV GRADLE_USER_HOME=/home/node/.gradle
ENV PATH="/home/node/.bun/bin:/home/node/.volta/bin:/home/node/.cargo/bin:/home/node/.local/zig:${PATH}"
Build it:
docker build -t openclaw-dev:local -f Dockerfile .
Then use it with the normal Docker setup:
export OPENCLAW_IMAGE="openclaw-dev:local"
./scripts/docker/setup.sh
For security, Iād keep:
- host port bound to
127.0.0.1 gateway.auth.mode: "token"- a strong
OPENCLAW_GATEWAY_TOKEN - provider keys injected at runtime, not baked into the image
And yep, you can still use multiple agents inside that one Gateway if you want separate workspaces/personas. Docker image choice does not limit you to one agent: https://docs.openclaw.ai/concepts/multi-agent
Docs:
If you want, I can sketch the exact single-gateway Dockerfile + compose file shape next.
If it isn't necessarily against what you can help, can you help with designing the custom Dockerfile for installing on this order:
- Useful Linux tools and libraries
- Build essentials
- Corretto JDK 17 and latest Corretto JDK, root (I believe root is necessary right?)
- Latest Rustup and Cargo nightly user
- Latest Gradle user
- Latest Bun.js user
- Latest Volta user
- Latest Zig toolchain user + bin
If it's not in the scope of what you can help with, you can just define the shape of theDOCKERFILEand I ask a personal assistant to help with the exact contents for the file. Thank you in advice regardless!!
Yep, I can help with the shape of it, and honestly this is a reasonable thing to do.
My strong recommendation first: do not use latest everywhere for a long-lived Gateway image.
For a dev box itās okay-ish, but for stability Iād pin versions with ARGs, including the base GHCR image.
Also, root is only needed for system packages + /opt JDK installs.
For Rustup / Cargo / Gradle / Bun / Volta / Zig, I would install them as the runtime user (node), not root.
So Iād structure it like this:
FROM ghcr.io/openclaw/openclaw:2026.4.19
USER root
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
ARG DEBIAN_FRONTEND=noninteractive
# Pin these, do not leave them floating forever.
ARG CORRETTO17_URL="REPLACE_ME"
ARG CORRETTO_LATEST_URL="REPLACE_ME"
ARG GRADLE_VERSION="REPLACE_ME"
ARG ZIG_VERSION="REPLACE_ME"
# 1) Useful Linux tools and libraries
# Git is already in the official image, but keeping it here is fine if you want
# the layer to be self-describing.
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl wget git jq unzip zip xz-utils tar gzip bzip2 file \
procps psmisc less nano vim-tiny tree ripgrep fd-find \
openssh-client gnupg dirmngr software-properties-common \
libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev \
libffi-dev liblzma-dev pkg-config \
&& rm -rf /var/lib/apt/lists/*
# 2) Build essentials
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential make cmake ninja-build autoconf automake libtool \
&& rm -rf /var/lib/apt/lists/*
# 3) Corretto JDK 17 + another pinned "latest" Corretto under /opt
RUN mkdir -p /opt/corretto /tmp/jdks \
&& curl -fsSL "$CORRETTO17_URL" -o /tmp/jdks/corretto17.tar.gz \
&& tar -xzf /tmp/jdks/corretto17.tar.gz -C /opt/corretto \
&& mv /opt/corretto/amazon-corretto-* /opt/corretto/jdk17 \
&& curl -fsSL "$CORRETTO_LATEST_URL" -o /tmp/jdks/corretto-latest.tar.gz \
&& tar -xzf /tmp/jdks/corretto-latest.tar.gz -C /opt/corretto \
&& mv /opt/corretto/amazon-corretto-* /opt/corretto/jdk-latest \
&& rm -rf /tmp/jdks
ENV JAVA17_HOME=/opt/corretto/jdk17
ENV JAVA_LATEST_HOME=/opt/corretto/jdk-latest
# Pick one default. I'd default to 17 unless you specifically want newer.
ENV JAVA_HOME=/opt/corretto/jdk17
# Switch back to the real runtime user for user-space toolchains
USER node
ENV HOME=/home/node
WORKDIR /home/node
RUN mkdir -p \
/home/node/.local/bin \
/home/node/.local/gradle \
/home/node/.local/zig \
/home/node/.cargo \
/home/node/.rustup \
/home/node/.volta \
/home/node/.bun \
/home/node/.gradle
# 4) Rustup + Cargo nightly (user)
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal --default-toolchain nightly
# 5) Gradle (user)
RUN curl -fsSL "REPLACE_ME_WITH_GRADLE_ZIP_URL" -o /tmp/gradle.zip \
&& unzip /tmp/gradle.zip -d /home/node/.local/gradle \
&& ln -s /home/node/.local/gradle/gradle-${GRADLE_VERSION} /home/node/.local/gradle/current \
&& rm /tmp/gradle.zip
# 6) Bun (user)
RUN curl -fsSL https://bun.sh/install | bash
# 7) Volta (user)
RUN curl https://get.volta.sh | bash
# 8) Zig (user)
RUN curl -fsSL "REPLACE_ME_WITH_ZIG_TARBALL_URL" -o /tmp/zig.tar.xz \
&& mkdir -p /home/node/.local/zig/releases \
&& tar -xf /tmp/zig.tar.xz -C /home/node/.local/zig/releases \
&& ln -s /home/node/.local/zig/releases/zig-linux-* /home/node/.local/zig/current \
&& rm /tmp/zig.tar.xz
ENV CARGO_HOME=/home/node/.cargo
ENV RUSTUP_HOME=/home/node/.rustup
ENV GRADLE_USER_HOME=/home/node/.gradle
ENV GRADLE_HOME=/home/node/.local/gradle/current
ENV BUN_INSTALL=/home/node/.bun
ENV VOLTA_HOME=/home/node/.volta
ENV ZIG_HOME=/home/node/.local/zig/current
ENV PATH="/home/node/.cargo/bin:/home/node/.local/gradle/current/bin:/home/node/.bun/bin:/home/node/.volta/bin:/home/node/.local/zig/current:${PATH}"
# Optional smoke checks
RUN rustc --version \
&& cargo --version \
&& bun --version \
&& volta --version \
&& zig version \
&& gradle --version \
&& java -version
A few honest recommendations
- Pin versions with
ARGs, especially Corretto, Gradle, Zig, and the GHCR base image. - I would default
JAVA_HOMEto JDK 17, then expose the newer one asJAVA_LATEST_HOME. - For Gradle projects, I still prefer
./gradlewover globalgradlewhen possible. - Install user-space toolchains as
node, because that is the final container user. - If OpenClaw still doesnāt see a tool for
exec, you can also prepend paths viatools.exec.pathPrepend: https://docs.openclaw.ai/tools/exec
So yes, this is in scope, and this is roughly the shape Iād use.
Docs:
If you want, I can do one more pass and turn this from shape into a more concrete Dockerfile with:
- version ARG suggestions
- a cleaner Corretto install layout
- a safer Zig symlink pattern
- a small
docker-compose.ymlfor the single Gateway container
How exactly do I handle auth tokens for Discord API, OpenRouter API and Anthropic API inside dockerized OpenClaw?
Krill?
Yep, Iām here š¦
I think my last reply probably got a bit too dense. If you want, I can simplify it into one concrete setup:
Dockerfiledocker-compose.ymlopenclaw.json- secret env file layout
and make it copy-pasteable.
I mean, I'm running multiple agents, but please don't type too dense replies. Can you offer a rough list of things I could/should do specially when running multiple agents, so I can ask for more specifics as I need them?
Yep, totally. Hereās the rough checklist version for multiple agents on one Gateway:
-
Give each agent a clear job
- ex:
main,coding,ops,discord
- ex:
-
Keep workspaces separate
- each agent should have its own workspace
- avoids file/persona/history mess
-
Decide which auth is shared vs separate
- model auth can be separate per agent
- channel routing can also be separate
- donāt assume one agent automatically shares everything cleanly
-
Be deliberate about bindings
- decide which chats/accounts go to which agent
- this is the biggest āwhy is the wrong bot answering?ā footgun
-
Use a shared Docker image only for common tooling
- if all agents need Bun/Rust/Zig/etc, bake it once into the Gateway image
- if only some agents need heavy tooling, think about sandbox/per-agent setup later
-
Name models per agent on purpose
- cheap/default model for casual agents
- stronger model for coding / harder reasoning agents
-
Watch memory + session sprawl
- more agents means more sessions, auth state, logs, workspaces
- keep names and routing tidy early
-
If you enable sandboxing, re-check tool availability
- gateway image tools and sandbox image tools are different things
- this bites people a lot
-
Keep secrets out of agent workspaces
- use env vars / SecretRefs, not plaintext files in random workspaces
- Document your layout somewhere
- one tiny note with:
- agent names
- purpose
- workspace path
- model
- channel bindings
If you want, I can break the next step into one of these:
- agent structure
- bindings/routing
- auth separation
- Docker/toolchain strategy
- sandbox strategy
If I could instead install the OpenClaw gateway on my WSL 2 instance directly (had trouble with updating that didn't worked at all, systemd misworking and the instance starting automatically when I don't want it to even though systemd was misbehaving and not allowing me to inspect it, but I could try it again), how would sandboxing work then?
Yep, if you install the Gateway directly inside WSL2, sandboxing is usually simpler than with a Dockerized Gateway.
Roughly how it works
- Gateway runs in WSL2
- Sandbox containers run next to it via Docker
- OpenClaw uses the Docker backend by default for sandboxing
So:
- sandbox off ā tools run directly on your WSL2 host
- sandbox on ā tools run in separate sandbox containers
Why this is nicer than a Dockerized Gateway
You avoid the annoying Docker-out-of-Docker path mapping stuff.
With a direct WSL install:
- OpenClaw sees normal WSL paths directly
- Docker sandbox containers are just sibling containers
- less path confusion
- easier workspace handling
The main sandbox knobs
You mostly care about:
-
agents.defaults.sandbox.mode"off"= no sandbox"non-main"= sandbox non-main sessions only"all"= sandbox everything
-
agents.defaults.sandbox.scope"agent"= one sandbox per agent
-
"session"= one sandbox per session"shared"= one shared sandbox
-
agents.defaults.sandbox.backend- default is
"docker"
- default is
Typical setup Iād suggest
For multi-agent use, a very normal starting point is:
mode: "non-main"scope: "agent"backend: "docker"
That gives you:
- normal/main chat stays on host
- other agent/session work can be sandboxed
- one container per agent, not one per message
Important practical note
If the Gateway is direct in WSL2, but Docker is not available inside WSL, the Docker sandbox backend wonāt work.
So the dependency chain becomes:
- WSL Gateway works
- Docker works from inside WSL
- then sandboxing works
My honest recommendation
If you retry WSL2 direct install, Iād do it in this order:
- get OpenClaw running in foreground first
openclaw gateway run
-
only after that, worry about
openclaw gateway install -
only after that, enable sandboxing
That keeps systemd problems separate from sandbox problems.
Docs:
If you want, next I can give you the short version of what a good first sandbox config looks like for multi-agent WSL2.
Which files are required by OpenClaw for an agent? How to stop an agent from editing these files?
Short version:
What files does an agent āneedā?
OpenClaw expects these bootstrap/workspace files:
AGENTS.mdSOUL.mdTOOLS.mdIDENTITY.mdUSER.mdBOOTSTRAP.md(only for first-run / brand-new workspace)
Common optional ones:
HEARTBEAT.mdBOOT.md
MEMORY.mdmemory/YYYY-MM-DD.mdskills/canvas/
Important nuance: they are expected, but not all are strictly required for startup.
If one is missing, OpenClaw can continue and just inject a missing-file marker.
Docs: https://docs.openclaw.ai/concepts/agent-workspace
How do you stop an agent from editing them?
The clean built-in ways are:
1. Best protection: sandbox + read-only workspace
Use sandboxing and set:
workspaceAccess: "ro"
orworkspaceAccess: "none"
"ro" is the usual answer if you want the agent to read workspace files but not modify them.
Docs: https://docs.openclaw.ai/gateway/sandboxing
2. Deny write tools
If you want a stricter policy, deny:
writeeditapply_patch
And if you really want to be strict, also think about exec, because shell commands can still modify files.
3. Stop OpenClaw from auto-creating missing bootstrap files
If you manage the workspace yourself, set:
{ agent: { skipBootstrap: true } }
That stops bootstrap file creation.
Practical recommendation
If your goal is:
-
agent can read persona files, but not change them
ā use sandbox +workspaceAccess: "ro" -
agent should not touch local files at all