#How to install OpenClaw on Docker WSL 2 with custom port + coding environment?

1 messages Ā· Page 1 of 1 (latest)

still void
#

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)

little dewBOT
tender elmBOT
#

To help others find answers, you can mark your question as solved via Right click solution message -> Apps -> āœ… Mark Solution

little dewBOT
frank lodgeBOT
#

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/.openclaw mount (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 ~/.gradle gives Gradle home/caches, not Java. gradle or ./gradlew still 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:

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.

still void
#

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

frank lodgeBOT
#

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_PACKAGES is 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>

Docs: https://docs.openclaw.ai/concepts/multi-agent

#

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.1 only
  • 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/.env or 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:

  1. the toolchains you want baked in
  2. whether you want one gateway + many agents or many gateways
  3. 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:

  1. one Gateway container
  2. custom image based on ghcr.io/openclaw/openclaw
  3. bake Bun / Volta / Rustup / Zig / JDK / Gradle into that image
  4. 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:

still void
#

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 the DOCKERFILE and I ask a personal assistant to help with the exact contents for the file. Thank you in advice regardless!!
frank lodgeBOT
#

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_HOME to JDK 17, then expose the newer one as JAVA_LATEST_HOME.
  • For Gradle projects, I still prefer ./gradlew over global gradle when 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 via tools.exec.pathPrepend: https://docs.openclaw.ai/tools/exec

So yes, this is in scope, and this is roughly the shape I’d use.

still void
#

How exactly do I handle auth tokens for Discord API, OpenRouter API and Anthropic API inside dockerized OpenClaw?

#

Krill?

frank lodgeBOT
#

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:

  1. Dockerfile
  2. docker-compose.yml
  3. openclaw.json
  4. secret env file layout

and make it copy-pasteable.

still void
#

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?

frank lodgeBOT
#

Yep, totally. Here’s the rough checklist version for multiple agents on one Gateway:

  1. Give each agent a clear job

    • ex: main, coding, ops, discord
  2. Keep workspaces separate

    • each agent should have its own workspace
    • avoids file/persona/history mess
  3. 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
  4. Be deliberate about bindings

    • decide which chats/accounts go to which agent
    • this is the biggest ā€œwhy is the wrong bot answering?ā€ footgun
#
  1. 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
  2. Name models per agent on purpose

    • cheap/default model for casual agents
    • stronger model for coding / harder reasoning agents
  3. Watch memory + session sprawl

    • more agents means more sessions, auth state, logs, workspaces
    • keep names and routing tidy early
  4. If you enable sandboxing, re-check tool availability

    • gateway image tools and sandbox image tools are different things
    • this bites people a lot
  5. Keep secrets out of agent workspaces

#
  • use env vars / SecretRefs, not plaintext files in random workspaces
  1. 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
still void
#

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?

frank lodgeBOT
#

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"

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:

  1. get OpenClaw running in foreground first
still void
#

Which files are required by OpenClaw for an agent? How to stop an agent from editing these files?

frank lodgeBOT
#

Short version:

What files does an agent ā€œneedā€?

OpenClaw expects these bootstrap/workspace files:

  • AGENTS.md
  • SOUL.md
  • TOOLS.md
  • IDENTITY.md
  • USER.md
  • BOOTSTRAP.md (only for first-run / brand-new workspace)

Common optional ones:

  • HEARTBEAT.md
  • BOOT.md
#
  • MEMORY.md
  • memory/YYYY-MM-DD.md
  • skills/
  • 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"
    or
  • workspaceAccess: "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:

  • write
  • edit
  • apply_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