#Preconfigured runs in docker

63 messages · Page 1 of 1 (latest)

short minnow
#

What are the best practices to get openclaw running completely inside docker? No system services just docker compose. Additionally, I would like to have preconfigured runs -- maybe there is onboarding non-interactive mode that lets me do a full setup?

vernal plazaBOT
#

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

frail auroraBOT
#

Let me verify the docs location and then I’ll pull the exact Docker/onboarding bits from the docs/source.

#

I found the Docker docs plus the non-interactive onboarding path. I’m pulling one more bit from the setup script so I don’t hand-wave the recommended Compose pattern.

#

Yes, this is doable, and the cleanest pattern is:

  • No system service inside Docker
  • One openclaw-gateway service
  • One openclaw-cli helper service
  • Persist only config + workspace via volumes
  • Use non-interactive onboarding before first compose up

A few best practices from the docs/source:

  1. Do not use --install-daemon in the container flow. For Compose-only, let Docker be the supervisor.
  2. Persist these paths:
    • OPENCLAW_CONFIG_DIR → /home/node/.openclaw
    • OPENCLAW_WORKSPACE_DIR → /home/node/.openclaw/workspace
  3. Before the gateway is started, run setup through openclaw-gateway with --entrypoint node, not openclaw-cli.
    • openclaw-cli is meant for post-start use because it shares the gateway network namespace.
  4. For Docker, set:
#
  • gateway.mode = local
    • gateway.bind = lan
    • gateway.controlUi.allowedOrigins = ["http://localhost:18789","http://127.0.0.1:18789"]
  1. Use SecretRefs/env-backed secrets instead of baking tokens into openclaw.json when possible.
  2. Use --skip-health during bootstrap, otherwise non-interactive onboarding waits for a gateway that is not up yet.

So yes, there is a non-interactive onboarding path.

Example bootstrap:

docker compose run --rm --no-deps --entrypoint node openclaw-gateway \
  dist/index.js onboard --non-interactive \
  --mode local \
  --auth-choice openai-api-key \
  --secret-input-mode ref \
#
  --gateway-auth token \
  --gateway-token-ref-env OPENCLAW_GATEWAY_TOKEN \
  --skip-skills \
  --skip-health \
  --accept-risk

docker compose run --rm --no-deps --entrypoint node openclaw-gateway \
  dist/index.js config set --batch-json '[{"path":"gateway.mode","value":"local"},{"path":"gateway.bind","value":"lan"},{"path":"gateway.controlUi.allowedOrigins","value":["http://localhost:18789","http://127.0.0.1:18789"]}]'

docker compose up -d openclaw-gateway

Then post-start, use the CLI container for extra setup:

#
docker compose run --rm openclaw-cli dashboard --no-open
docker compose run --rm openclaw-cli channels add --channel discord --token "$DISCORD_BOT_TOKEN"

One important caveat: fully non-interactive only works for API-key/token based setup. Things like WhatsApp QR login or some OAuth flows still need a human step.

Relevant docs:

If you want, I can turn this into a ready-to-run docker-compose.yml + bootstrap script for your provider.

short minnow
#

I want to centrally monitor all messages and discussions from these agents. What are my options? Ideally I want to put them in a database

frail auroraBOT
#

I’m checking the docs/source for the supported ways to capture agent conversations and whether there’s a built-in database sink versus file/session export.

#

Short version: today the canonical conversation store is the on-disk session transcripts, not a built-in SQL database backend.

Your main options are:

  1. Use the session JSONL files as the source of truth
    Best if you want the full chat history / tool calls / discussions.

    Paths on the gateway host:

    • ~/.openclaw/agents/<agentId>/sessions/sessions.json
    • ~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl

    In your Docker setup, that means reading from the mounted OPENCLAW_CONFIG_DIR, for example:

    • OPENCLAW_CONFIG_DIR/agents/<agentId>/sessions/*.jsonl

    This is what I’d use for a DB ingest pipeline into Postgres, ClickHouse, Elasticsearch, etc.

  2. Enable diagnostics + OpenTelemetry export

#

Best if you want central monitoring, dashboards, alerts, latency/cost/message-flow metrics.

OpenClaw supports OTLP export via the diagnostics-otel plugin. That gives you:

  • metrics
  • traces
  • logs

And the spans/events already include useful fields like:

  • sessionKey
  • sessionId
  • chatId
  • messageId
  • provider/model
  • token/cost info

Good fit for Grafana/Tempo/Loki, Elastic, Datadog, Honeycomb, etc.

#
  1. Ship the gateway log file
    Useful for ops, but not the best canonical transcript source.

    Default file log:

    • /tmp/openclaw/openclaw-YYYY-MM-DD.log

My recommendation:

  • If you want exact conversations in a database: ingest the session *.jsonl transcripts
  • If you want observability: also enable OTel diagnostics
  • If you want both: do both, they solve different problems

A good Docker pattern is:

  • mount OPENCLAW_CONFIG_DIR
  • run OpenClaw normally
  • add a sidecar that tails agents/*/sessions/*.jsonl
short minnow
#

is it possible to monitor and log tool calls?

frail auroraBOT
#

I’m checking whether tool calls are exposed through transcripts only, regular logs, or the diagnostics/OTel path too, so I can answer this cleanly.

#

Yes, but with an important nuance:

There isn’t a dedicated built-in “tool audit log” database sink that I can see.
The supported ways to monitor tool calls today are:

  1. Session transcripts (best audit trail)
    Tool calls + tool results are persisted in the session transcript JSONL files.
    That is the most complete source if you want exact history.

    Path:

    • ~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl

    In Docker, read them from your mounted OPENCLAW_CONFIG_DIR.

  2. Live verbose output (good for watching runs)

    • /verbose on shows tool calls as separate tool-summary bubbles
    • /verbose full also shows tool outputs
#

I’d be careful with this in group chats, because it can expose internals/tool output.

  1. Plugin hooks (best for real-time custom logging to DB/queue)
    OpenClaw exposes hooks for:

    • before_tool_call
    • after_tool_call
    • tool_result_persist

    So if you want “every tool call goes to Postgres/Kafka/Redis/HTTP collector”, a small plugin is the cleanest real-time path.

  2. Gateway logs
    Some tool-related summaries/output can land in logs, and those can be shipped centrally, but logs are not as clean/canonical as the session transcript.

One very important security note:

  • logging.redactSensitive: "tools" only masks console tool summaries
  • it does not rewrite the stored transcript
#
  • and it does not protect file logs / OTLP logs the same way

So if you export logs/transcripts to a DB, treat them as sensitive.

My recommendation:

  • For exact tool auditing: ingest the session *.jsonl
  • For real-time monitoring: add a plugin using before_tool_call + after_tool_call
  • For dashboards/ops: use diagnostics/OTel alongside that

Docs:

#

If you want, I can sketch a minimal plugin design that writes tool calls into Postgres.

short minnow
#

lets go back to docker question. what's the best image to use for docker-based runs?

frail auroraBOT
#

I’m checking the Docker docs for the supported image tags so I don’t guess and accidentally steer you to the wrong one.

#

For most Docker-based runs, I’d use:

ghcr.io/openclaw/openclaw:<version>

So, a pinned version tag, not latest.

My practical recommendation:

  • Production / stable setup: use a specific version tag
    Example: ghcr.io/openclaw/openclaw:2026.2.26
  • Quickstart / personal testing: ghcr.io/openclaw/openclaw:latest
  • Bleeding edge / bugfix verification: ghcr.io/openclaw/openclaw:main

Why:

  • Pinned version is best for reproducibility and predictable upgrades
  • latest is convenient, but less controlled
#
  • main is for “I want the newest fix right now and accept breakage”

If you need custom extras, then I’d build your own image instead of relying purely on GHCR, especially if you want:

  • extra apt packages
  • bundled extensions
  • sandbox-related customization

The docs call out the prebuilt image here:

export OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:latest"
./scripts/docker/setup.sh

And the common tags are:

  • main
  • latest
short minnow
#

okay, let's say I update the image and mount an old volume - how do I ensure smooth upgrade? what if something does not work?

frail auroraBOT
#

The safe mental model is:

the container is disposable, the mounted state is not.

So for smooth Docker upgrades, I’d do this:

  1. Pin image tags

    • Prefer ghcr.io/openclaw/openclaw:<version>
    • Avoid blindly tracking latest for anything important
  2. Back up the mounted state before switching images

    • Back up OPENCLAW_CONFIG_DIR
    • Also back up OPENCLAW_WORKSPACE_DIR if it’s a separate mount
  3. Replace the container, don’t “update inside” it

    • docker compose pull
    • docker compose up -d
#
  • I would not treat openclaw update inside the container as the main Docker upgrade path
  1. Run doctor after the new image starts

    • docker compose exec openclaw-gateway openclaw doctor --non-interactive
    • If you want automatic repair/migration:
      docker compose exec openclaw-gateway openclaw doctor --repair --non-interactive
  2. Verify immediately

    • docker compose logs -f openclaw-gateway
    • curl -fsS http://127.0.0.1:18789/healthz
    • Deep check:
      docker compose exec openclaw-gateway node dist/index.js health --token "$OPENCLAW_GATEWAY_TOKEN"

A couple important nuances:

  • OpenClaw does auto-run some doctor migrations on startup when it detects legacy config
  • But I’d still run an explicit doctor --non-interactive after an upgrade so you can see what changed
#

If something breaks:

Fast rollback

  • switch back to the previous image tag
  • restore the volume backup/snapshot
  • start the old container again

That second step matters. Rolling back only the container image is not always enough if the newer version already migrated state/config.

Best-practice upgrade flow

If you want near-zero drama, do upgrades like this:

  • snapshot/copy volume
  • start the new image against the copied volume in a staging compose project
  • run doctor + health checks there
  • only then point prod at the new image
short minnow
#

regarding onboarding - I do not want to do that actual onboarding but I want to initialize workspace correctly - how do I do that?

I'll copy my AGENT.md, SOUL.md etc from somewhere else

frail auroraBOT
#

Let me check the workspace docs so I can tell you the minimum files/layout to preseed without inventing anything.

#

Yep, for that you want openclaw setup, not onboarding.

Plain openclaw setup does exactly this:

  • initializes ~/.openclaw/openclaw.json
  • creates/points the agent workspace
  • does not run the full onboarding wizard unless you add wizard/onboarding flags

So the simplest Docker-friendly flow is:

docker compose exec openclaw-gateway \
  openclaw setup --workspace /home/node/.openclaw/workspace

Then put your files into that mounted workspace:

#
  • AGENTS.md
  • SOUL.md
  • USER.md
  • IDENTITY.md
  • TOOLS.md

Optional but useful:

  • HEARTBEAT.md
  • MEMORY.md
  • memory/

A few important details:

  • You do not need BOOTSTRAP.md if you’re restoring an already-defined personality/workspace
  • openclaw setup is fine here, because it initializes config/workspace without forcing the interactive ritual
  • OpenClaw expects the workspace to just be a normal directory with those files, there’s no hidden “workspace database” you need to initialize separately
#

Also, workspace is separate from state/config:

  • workspace/persona files live in the workspace dir
  • auth, sessions, channel credentials, config live under ~/.openclaw/

So if you later want a fully preseeded install, you usually preseed both:

  1. the workspace
  2. the state dir

Docs:

If you want, I can give you a minimal Docker bootstrap recipe for:

  • mounted preseeded workspace
  • mounted state dir
#
  • zero onboarding on first boot
short minnow
#

how do I ensure that both are there?

#

give me that recipe

frail auroraBOT
#

I’m checking whether the docs spell out the exact mount pattern so I don’t suggest a Docker layout that fights the workspace path.

#

The reliable way is:

1) mount them explicitly

In Compose, mount both paths, not just one:

services:
  openclaw-gateway:
    image: ghcr.io/openclaw/openclaw:<version>
    volumes:
      - ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
      - ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace

That is the same pattern the Docker docs/useful setup script use.

#

2) pre-create both host directories

Before first start:

mkdir -p "$OPENCLAW_CONFIG_DIR"
mkdir -p "$OPENCLAW_WORKSPACE_DIR"

If you want them preseeded:

  • put openclaw.json, auth/session state, etc. into OPENCLAW_CONFIG_DIR
  • put AGENTS.md, SOUL.md, USER.md, etc. into OPENCLAW_WORKSPACE_DIR

3) initialize config/workspace once

If config is not already there:

#
docker compose run --rm openclaw-cli \
  setup --workspace /home/node/.openclaw/workspace

That gives you a valid state dir + workspace without full onboarding.

4) verify inside the container

docker compose exec openclaw-gateway ls -la /home/node/.openclaw
docker compose exec openclaw-gateway ls -la /home/node/.openclaw/workspace

You should see:

  • config/state under /home/node/.openclaw
#
  • your persona/workspace files under /home/node/.openclaw/workspace

practical rule

If you’re doing this by hand, think of it as:

  • state dir = machine/runtime state
  • workspace dir = agent personality + memory files

If you want, I can write you a minimal compose + bootstrap script that guarantees both exist before the gateway starts.

#

I’m grabbing the repo’s Docker layout so I can give you a recipe that matches the actual service names and paths.

#

Yep, here’s a minimal no-onboarding Docker recipe that guarantees both state and workspace exist.

1) directory layout

openclaw-docker/
  .env
  docker-compose.yml
  data/
    state/
    workspace/

2) .env

#
OPENCLAW_IMAGE=ghcr.io/openclaw/openclaw:latest
OPENCLAW_CONFIG_DIR=./data/state
OPENCLAW_WORKSPACE_DIR=./data/workspace
OPENCLAW_GATEWAY_PORT=18789
OPENCLAW_BRIDGE_PORT=18790
OPENCLAW_GATEWAY_BIND=lan
OPENCLAW_GATEWAY_TOKEN=replace-me-with-a-random-token
OPENCLAW_TZ=UTC

3) docker-compose.yml

services:
  openclaw-gateway:
#
    image: ${OPENCLAW_IMAGE}
    environment:
      HOME: /home/node
      TERM: xterm-256color
      OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN}
      TZ: ${OPENCLAW_TZ}
    volumes:
      - ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
      - ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
    ports:
      - "${OPENCLAW_GATEWAY_PORT}:18789"
      - "${OPENCLAW_BRIDGE_PORT}:18790"
    init: true
    restart: unless-stopped
    command:
#
      [
        "node",
        "dist/index.js",
        "gateway",
        "--bind",
        "${OPENCLAW_GATEWAY_BIND}",
        "--port",
        "18789",
      ]

  openclaw-cli:
    image: ${OPENCLAW_IMAGE}
    network_mode: "service:openclaw-gateway"
    environment:
      HOME: /home/node
#
      TERM: xterm-256color
      OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN}
      BROWSER: echo
      TZ: ${OPENCLAW_TZ}
    volumes:
      - ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
      - ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
    stdin_open: true
    tty: true
    init: true
    entrypoint: ["node", "dist/index.js"]
    depends_on:
      - openclaw-gateway

4) preseed both directories

#

Run this from the openclaw-docker/ folder:

mkdir -p ./data/state ./data/workspace
mkdir -p ./data/state/identity
mkdir -p ./data/state/agents/main/agent
mkdir -p ./data/state/agents/main/sessions

Now copy in what you already have:

preseed workspace

cp /somewhere/AGENTS.md ./data/workspace/
cp /somewhere/SOUL.md ./data/workspace/
cp /somewhere/USER.md ./data/workspace/
#
cp /somewhere/IDENTITY.md ./data/workspace/ 2>/dev/null || true
cp /somewhere/TOOLS.md ./data/workspace/ 2>/dev/null || true
cp /somewhere/MEMORY.md ./data/workspace/ 2>/dev/null || true

optional: preseed state too

If you are restoring an existing install:

rsync -a /somewhere/.openclaw/ ./data/state/

That carries over config, auth, sessions, credentials, etc.

5) initialize config/workspace without onboarding

#

Before starting the gateway, run:

docker compose run --rm --no-deps --entrypoint node openclaw-gateway \
  dist/index.js setup --workspace /home/node/.openclaw/workspace

Optional but nice to pin local Docker behavior:

docker compose run --rm --no-deps --entrypoint node openclaw-gateway \
  dist/index.js config set --batch-json '[{"path":"gateway.mode","value":"local"},{"path":"gateway.bind","value":"lan"},{"path":"gateway.controlUi.allowedOrigins","value":["http://localhost:18789","http://127.0.0.1:18789"]}]'

6) start it

#
docker compose up -d openclaw-gateway

7) verify both mounts are really there

docker compose exec openclaw-gateway ls -la /home/node/.openclaw
docker compose exec openclaw-gateway ls -la /home/node/.openclaw/workspace
docker compose exec openclaw-gateway node dist/index.js health --token "$OPENCLAW_GATEWAY_TOKEN"

how this works

  • data/state = config/auth/sessions/credentials
  • data/workspace = AGENTS.md, SOUL.md, memory files, etc.
  • setup initializes things without running full onboarding
short minnow
frail auroraBOT
#

Yep, here’s a ready-to-run minimal Docker Compose setup.

.env

OPENCLAW_IMAGE=ghcr.io/openclaw/openclaw:latest
OPENCLAW_CONFIG_DIR=./data/state
OPENCLAW_WORKSPACE_DIR=./data/workspace
OPENCLAW_GATEWAY_PORT=18789
OPENCLAW_BRIDGE_PORT=18790
OPENCLAW_GATEWAY_BIND=lan
OPENCLAW_GATEWAY_TOKEN=replace-this-with-a-random-hex-token
OPENCLAW_TZ=UTC

docker-compose.yml

#
services:
  openclaw-gateway:
    image: ${OPENCLAW_IMAGE}
    environment:
      HOME: /home/node
      TERM: xterm-256color
      OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN}
      TZ: ${OPENCLAW_TZ}
    volumes:
      - ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
      - ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
    ports:
      - "${OPENCLAW_GATEWAY_PORT}:18789"
      - "${OPENCLAW_BRIDGE_PORT}:18790"
    init: true
#
    restart: unless-stopped
    command:
      [
        "node",
        "dist/index.js",
        "gateway",
        "--bind",
        "${OPENCLAW_GATEWAY_BIND}",
        "--port",
        "18789",
      ]
    healthcheck:
      test:
        [
          "CMD",
#
          "node",
          "-e",
          "fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))",
        ]
      interval: 30s
      timeout: 5s
      retries: 5
      start_period: 20s

  openclaw-cli:
    image: ${OPENCLAW_IMAGE}
    network_mode: "service:openclaw-gateway"
    environment:
      HOME: /home/node
      TERM: xterm-256color
#
      OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN}
      BROWSER: echo
      TZ: ${OPENCLAW_TZ}
    volumes:
      - ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
      - ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
    stdin_open: true
    tty: true
    init: true
    entrypoint: ["node", "dist/index.js"]
    depends_on:
      - openclaw-gateway

first boot

#
mkdir -p ./data/state ./data/workspace
mkdir -p ./data/state/identity
mkdir -p ./data/state/agents/main/agent
mkdir -p ./data/state/agents/main/sessions

If you already have your workspace files:

cp /somewhere/AGENTS.md ./data/workspace/
cp /somewhere/SOUL.md ./data/workspace/
cp /somewhere/USER.md ./data/workspace/
cp /somewhere/IDENTITY.md ./data/workspace/ 2>/dev/null || true
cp /somewhere/TOOLS.md ./data/workspace/ 2>/dev/null || true
cp /somewhere/MEMORY.md ./data/workspace/ 2>/dev/null || true
#

Initialize without onboarding:

docker compose run --rm --no-deps --entrypoint node openclaw-gateway \
  dist/index.js setup --workspace /home/node/.openclaw/workspace

Optional but nice:

docker compose run --rm --no-deps --entrypoint node openclaw-gateway \
  dist/index.js config set --batch-json '[{"path":"gateway.mode","value":"local"},{"path":"gateway.bind","value":"lan"},{"path":"gateway.controlUi.allowedOrigins","value":["http://localhost:18789","http://127.0.0.1:18789"]}]'

Start it:

#
docker compose up -d openclaw-gateway

Check it:

docker compose logs -f openclaw-gateway
docker compose exec openclaw-gateway node dist/index.js health --token "$OPENCLAW_GATEWAY_TOKEN"

Then post-start stuff goes through openclaw-cli, for example:

docker compose run --rm openclaw-cli dashboard --no-open
docker compose run --rm openclaw-cli channels add --channel discord --token "$DISCORD_BOT_TOKEN"