claudeclaw

claudeclaw

Docker app from paulmeier's Repository

Overview

claudeclaw runs Claude Code as a persistent background daemon with Telegram, Discord, and Slack messaging bridges, scheduled cron jobs, voice transcription via Whisper, and a web dashboard. Requires a Claude Code subscription at claude.ai/code — no API key needed.

claudeclaw-container

claudeclaw-container

Release Last release Last commit Release Please Lint Security scan Container License

Docker container for claudeclaw — a daemon that extends Claude Code into a personal assistant with Telegram, Discord, and Slack bridges, scheduled jobs, voice transcription, and a web dashboard.


Why run claudeclaw in a container?

Zero host pollution. claudeclaw depends on Bun, Node.js, and the Claude Code CLI. Running it natively means installing and maintaining all of that on your machine. The container bundles everything — your host stays clean.

Controlled access. By default the daemon can only see what you explicitly give it. Want it to access your notes? Mount that folder. Everything else on your machine is invisible to it. Running natively, claudeclaw inherits access to your entire filesystem.

Easy to run on a server. The same image runs on a VPS, home server, or cloud instance without any changes. Your personal assistant stays online even when your laptop is closed.

Instant reset. Something went wrong or you want a clean slate? docker compose down -v removes everything. No leftover config files scattered across your home directory.

Reproducible. The container always starts from a known state. No "works on my machine" issues caused by a different Bun version, a conflicting global npm package, or a PATH quirk.


Prerequisites

  • Docker with Compose
  • A Claude Code subscription (claude.ai/code) — no API key required
  • Optional: a Telegram bot token, Discord bot token, or Slack app token for messaging

Authentication

claudeclaw wraps the claude CLI directly and uses your existing Claude Code credentials — it does not require an ANTHROPIC_API_KEY. Before starting the container you need to authenticate Claude Code into the persistent volume once.

Step 1 — create the volume and log in:

docker compose run --rm claudeclaw claude login

This opens an OAuth browser flow. Complete it and your credentials are saved to the volume at /root/.claude/.credentials.json. You only need to do this once — credentials persist across container restarts.

Step 2 — start the daemon:

docker compose up -d

Alternatively, if you already have Claude Code authenticated on your host machine, you can copy your credentials directly into the volume:

docker run --rm \
  -v claudeclaw-data:/root/.claude \
  -v ~/.claude:/host-claude:ro \
  alpine cp /host-claude/.credentials.json /root/.claude/.credentials.json

Quick start

git clone https://github.com/paulmeier/claudeclaw-container
cd claudeclaw-container
docker compose run --rm claudeclaw claude login   # authenticate once
docker compose up -d

The web dashboard will be available at http://localhost:4632.

On first run the container will:

  1. Create a default settings.json on the volume
  2. Download the whisper.cpp binary and base.en model (~140 MB) for voice transcription
  3. Install the dev-browser Claude Code plugin

These are all cached in the volume and skipped on subsequent starts.

The image ships Chromium runtime libraries pre-installed (Playwright's canonical Debian 13 dependency set), so the dev-browser plugin launches Chromium without needing apt-get inside the container — which is blocked anyway in hardened deployments that drop CAP_SETGID. The previous bookworm-slim base required consumers to swap the dev-browser binary for its musl variant to work around an out-of-date glibc; that workaround is no longer needed — trixie-slim ships glibc 2.41, satisfying the upstream dev-browser-linux-{x64,arm64} binaries directly on both linux/amd64 and linux/arm64.


Configuration

All configuration lives in settings.json inside the named volume at /root/.claude/claudeclaw/settings.json. The easiest ways to edit it:

Option A — edit in place after first run:

docker compose up -d
docker compose exec claudeclaw cat /root/.claude/claudeclaw/settings.json
# copy, edit locally, then:
docker compose cp settings.json claudeclaw:/root/.claude/claudeclaw/settings.json
docker compose restart

Option B — start from the example file:

cp settings.example.json settings.json
# edit settings.json with your tokens, then:

Uncomment the bind-mount line in docker-compose.yml:

volumes:
  - ./settings.json:/root/.claude/claudeclaw/settings.json:ro

Do not commit settings.json — it contains your API tokens. It is already in .gitignore. Use settings.example.json as a reference; it is safe to commit.

Settings reference

{
  "model": "sonnet", // "opus", "sonnet", or "haiku"

  "web": {
    "enabled": true,
    "host": "0.0.0.0", // do not change — required for container networking
    "port": 4632,
  },

  "telegram": {
    "token": "", // BotFather token
    "allowedUserIds": [], // numeric Telegram user IDs who can interact
    "receiveEnabled": true, // set true to listen for incoming messages
  },

  "discord": {
    "token": "", // Discord bot token
    "allowedUserIds": [], // Discord snowflake user IDs (as strings)
    "listenChannels": [], // channel IDs to listen in
    "listenGuilds": [], // guild IDs (leave empty to listen in all guilds)
  },

  "slack": {
    "botToken": "", // xoxb-... bot token
    "appToken": "", // xapp-... app-level token (Socket Mode)
    "allowedUserIds": [], // Slack member IDs
    "listenChannels": [], // channel IDs to listen in
  },

  "heartbeat": {
    "enabled": false, // periodic check-ins from the daemon
    "interval": 60, // minutes between heartbeats
    "prompt": "...", // prompt sent each heartbeat
    "forwardToTelegram": false,
  },

  "security": {
    "level": "moderate", // "locked", "strict", "moderate", or "unrestricted"
  },
}

Messaging setup

Telegram

  1. Create a bot via @BotFather and copy the token
  2. Get your numeric user ID from @userinfobot
  3. Set in settings.json:
    "telegram": {
      "token": "123456:ABC-...",
      "allowedUserIds": [987654321],
      "receiveEnabled": true
    }
    
  4. Restart the container — no extra ports needed, Telegram uses outbound polling

Discord

  1. Create a bot at discord.com/developers
  2. Under Bot, enable Message Content Intent
  3. Copy the bot token
  4. Invite the bot to your server with the bot scope and Send Messages + Read Message History permissions
  5. Get channel/guild IDs by enabling Developer Mode in Discord (Settings → Advanced), then right-clicking a channel or server
  6. Set in settings.json:
    "discord": {
      "token": "your-bot-token",
      "allowedUserIds": ["your-snowflake-id"],
      "listenChannels": ["channel-id"],
      "listenGuilds": ["guild-id"]
    }
    
  7. Restart the container — Discord uses outbound WebSockets, no extra ports needed

Slack

  1. Create a Slack app at api.slack.com/apps with Socket Mode enabled
  2. Under OAuth & Permissions, add chat:write, channels:history, im:history scopes and install to workspace
  3. Copy the Bot User OAuth Token (xoxb-...)
  4. Under Basic Information → App-Level Tokens, create a token with connections:write scope
  5. Copy the App-Level Token (xapp-...)
  6. Set in settings.json:
    "slack": {
      "botToken": "xoxb-...",
      "appToken": "xapp-...",
      "allowedUserIds": ["U012AB3CD"],
      "listenChannels": ["C012AB3CD"]
    }
    
  7. Restart the container

Mounting additional directories

You can give claudeclaw access to any directory on your host — notes, documents, code, media — by adding bind mounts to docker-compose.yml.

Read-only access

Use :ro when you want claudeclaw to read files but never modify them:

services:
  claudeclaw:
    volumes:
      - claudeclaw-data:/root/.claude # always keep this one
      - /Users/you/Notes:/mnt/notes:ro
      - /Users/you/Documents:/mnt/documents:ro

Inside the container those directories appear at /mnt/notes and /mnt/documents. claudeclaw can read, search, and reference them but cannot write back to your host.

Read-write access

Omit :ro to allow claudeclaw to create, edit, and delete files:

volumes:
  - claudeclaw-data:/root/.claude
  - /Users/you/Notes:/mnt/notes # full read-write

Use this when you want claudeclaw to save notes, update files, or write output back to your machine.

Tips

Use absolute paths. Relative paths and ~ don't expand in docker-compose.yml. Use the full path or an environment variable:

- ${HOME}/Notes:/mnt/notes

Choose mount paths that are easy to reference. claudeclaw will see whatever path you pick on the right side of the :. Keeping them short and under /mnt/ makes it easy to refer to them in prompts and job definitions — for example: "summarise everything in /mnt/notes from this week".

Apply least privilege. Mount read-write only for directories claudeclaw actually needs to write to. Everything else should be :ro.

Changes take effect after a restart:

docker compose down && docker compose up -d

Web dashboard

Available at http://localhost:4632 when web.enabled is true. Shows active jobs, logs, and session status. To access it from a remote host, either expose the port via a reverse proxy or change the port mapping in docker-compose.yml.


Building a specific claudeclaw version

By default the image clones the main branch. To pin to a tag or commit:

docker build --build-arg CLAUDECLAW_REF=v1.0.34 -t claudeclaw .

Persistent data

Everything is stored in the claudeclaw-data named volume at /root/.claude/:

Path Contents
claudeclaw/settings.json Your configuration
claudeclaw/logs/ Job and session logs
claudeclaw/jobs/ Scheduled job definitions
claudeclaw/whisper/ whisper.cpp binary + model files
plugins/ Installed Claude Code plugins
npm-global/ Globally installed npm packages + bins
npm-cache/ npm download cache; _npx/ subdir holds the npx tarball cache
python-user/ pip install site-packages + bins
pip-cache/ pip download cache
pnpm-global/ pnpm content-addressable store, manifest, and bin/ shims
uv-tools/ uv tool install isolated venvs
uv-tool-bin/ UV tool shims + bins
uv-cache/ UV download cache; uvx environments
uv-python/ UV-managed Python installations

To back up or inspect the volume:

# Dump to a tar archive
docker run --rm -v claudeclaw-data:/data -v $(pwd):/backup alpine \
  tar czf /backup/claudeclaw-backup.tar.gz -C /data .

# Restore
docker run --rm -v claudeclaw-data:/data -v $(pwd):/backup alpine \
  tar xzf /backup/claudeclaw-backup.tar.gz -C /data

Adding npm packages

Some Claude Code skills (and your own prompts) call out to CLI tools via npm install -g or npx. By default those land in /usr/lib/node_modules and /root/.npm — both inside the container's writable layer and wiped on every image pull. To avoid re-installing on every update, the container redirects npm into the persistent volume so packages and the npx cache survive container recreation and image rebuilds.

How it works

On every start, entrypoint.sh exports:

Variable Effect
NPM_CONFIG_PREFIX=/root/.claude/npm-global Global installs go to npm-global/lib/node_modules/, executables to npm-global/bin/
NPM_CONFIG_CACHE=/root/.claude/npm-cache npm and npx tarball cache
PATH=/root/.claude/npm-global/bin:$PATH Global binaries are on PATH for the daemon and every process it spawns (skills, jobs, docker exec)

Because /root/.claude is the named volume, anything written under these paths sticks around across docker compose down && up, docker compose pull, and image rebuilds. The persistent layout adds these to the volume:

/root/.claude/
├── npm-global/
│   ├── bin/        # binaries on PATH
│   └── lib/node_modules/
└── npm-cache/      # npm + npx tarball cache

NPM_CONFIG_* env vars take precedence over .npmrc, so this also works if you bind-mount your own .npmrc.

Installing a package

From a running container, or from a Claude Code skill:

docker compose exec claudeclaw npm install -g cowsay
docker compose exec claudeclaw cowsay hello   # binary persists across restarts

npx

npx requires no separate setup. Because NPM_CONFIG_CACHE already points into the volume, npx's download cache (npm-cache/_npx/) is persisted automatically — the tarball is fetched once and reused across container recreations:

docker compose exec claudeclaw npx cowsay hello   # downloads on first call
docker compose exec claudeclaw npx cowsay hello   # reads from volume cache

Note that npx does not maintain a persistent global install — it downloads, runs, and exits. The volume caches the tarball so subsequent calls are fast, but there is no equivalent of npm install -g to migrate. If a Node major version bump breaks a cached npx package with native addons, clear the cache and npx will redownload a fresh copy on the next call:

docker compose exec claudeclaw rm -rf /root/.claude/npm-cache/_npx

Bake packages into a custom image

If you'd rather not depend on the entrypoint running before a package is available (for example, packages needed at image-build time or referenced by other tooling), extend the base image:

FROM ghcr.io/paulmeier/claudeclaw-container:latest
RUN npm install -g cowsay some-other-pkg

Then point docker-compose.yml at the new image:

services:
  claudeclaw:
    image: my/claudeclaw:latest

Node version migration

npm global packages all live in a single npm-global/lib/node_modules/ directory — there is no version-keyed subdirectory like Python's lib/pythonX.Y/. Pure JavaScript packages keep working across Node major versions without any action. The problem is native addons (packages that compile .node binaries via node-gyp): those are built against a specific Node ABI and will fail to load under a different major version. Reinstalling forces npm to recompile them for the current runtime.

Run migrate-npm.sh inside the container after a base image update that bumps the Node major version:

docker compose exec claudeclaw /migrate-npm.sh

The script reads name and version from each top-level package.json in npm-global/lib/node_modules/ (including scoped packages), then calls npm install -g name@version for all of them, recompiling any native addons for the current Node ABI.

For npx, there is nothing to reinstall — npx redownloads on demand. If a cached npx package fails due to a stale native addon, clear the npx cache:

docker compose exec claudeclaw rm -rf /root/.claude/npm-cache/_npx

Caveats

  • Wiping the volume (docker compose down -v) removes installed packages along with everything else. Use backup.sh if you want them preserved.
  • If the base image's Node major version bumps, native addons will break. Run /migrate-npm.sh to reinstall and recompile them.
  • The volume is shared across all claudeclaw state, so a runaway npm install can consume significant space. du -sh /root/.claude/npm-* to audit.

Adding Python packages

Same story as npm. Some Claude Code skills shell out to pip install for Python tooling, and the default install location (/root/.local/ or system site-packages) sits inside the writable image layer — wiped on every image pull. The container redirects pip into the persistent volume so installed packages and the pip cache survive container recreation and image rebuilds.

How it works

On every start, entrypoint.sh exports:

Variable Effect
PYTHONUSERBASE=/root/.claude/python-user pip install --user site-packages and scripts go to python-user/lib/pythonX.Y/site-packages/ and python-user/bin/
PIP_USER=1 pip install defaults to --user mode (no need to pass it every time)
PIP_BREAK_SYSTEM_PACKAGES=1 Bypasses Debian's PEP 668 "externally managed" warning. Safe here because we're never touching system site-packages — only the relocated user-base
PIP_CACHE_DIR=/root/.claude/pip-cache pip download cache
PATH=/root/.claude/python-user/bin:$PATH Installed Python scripts are on PATH for the daemon and every process it spawns

Layout added to the volume:

/root/.claude/
├── python-user/
│   ├── bin/        # scripts on PATH
│   └── lib/pythonX.Y/site-packages/
└── pip-cache/      # pip download cache

Installing a package

From a running container, or from a Claude Code skill:

docker compose exec claudeclaw pip install httpie
docker compose exec claudeclaw http --version   # binary persists across restarts

Bake packages into a custom image

Same pattern as npm — extend the base image:

FROM ghcr.io/paulmeier/claudeclaw-container:latest
RUN pip install httpie ruff

Python version migration

If you are upgrading from a pre-trixie image (anything built on the old node:24-slim / bookworm-slim base), the first start on a trixie-based image triggers exactly this case: bookworm-slim shipped python3.11; trixie-slim ships python3.13. Existing pip-installed packages under python-user/lib/python3.11/ become invisible to python3.13. The healthcheck on startup prints a warning pointing here; run docker compose exec claudeclaw /migrate-python.sh to restore them.

Python user-base directories are keyed by minor version (lib/python3.11/, lib/python3.12/, …). When the base image's Python minor version bumps, packages installed under the old version become invisible to the new interpreter — the old site-packages/ directory still exists on disk but is simply not on the new Python's search path. Run migrate-python.sh inside the container to reinstall them:

docker compose exec claudeclaw /migrate-python.sh

The script scans every old pythonX.Y directory under python-user/lib/, reads each package's name and version from .dist-info/METADATA, and calls pip install to reinstall them under the current version. The old directories are left in place so you can verify nothing is missing before removing them:

# Confirm your packages work, then clean up the old directory
docker compose exec claudeclaw rm -rf /root/.claude/python-user/lib/python3.11

Caveats

  • Python user-base is keyed by Python minor version (python3.11/site-packages etc.). If the base image's Python minor version ever bumps, previously installed packages become invisible — run /migrate-python.sh to recover them.
  • For Python CLI tools specifically, uv tool install avoids this problem: UV tools live in named venvs, not version-keyed directories, so they survive Python minor version changes more gracefully.
  • du -sh /root/.claude/python-* to audit space usage.

Adding pnpm packages

pnpm is pre-installed in the image. Its global package shims and content-addressable store are both redirected into the persistent volume so packages added with pnpm add -g survive container recreation and image rebuilds.

How it works

On every start, entrypoint.sh exports:

Variable Effect
PNPM_HOME=/root/.claude/pnpm-global pnpm's content-addressable store, global manifest, and bin/ shims all land under here
PATH=/root/.claude/pnpm-global/bin:$PATH Shims are on PATH for the daemon and every process it spawns

Layout added to the volume:

/root/.claude/pnpm-global/
├── bin/        # shim scripts on PATH (one per globally installed package)
├── global/     # pnpm global manifest
└── store/      # content-addressable package store

Installing a package

From a running container, or from a Claude Code skill:

docker compose exec claudeclaw pnpm add -g cowsay
docker compose exec claudeclaw cowsay hello   # shim persists across restarts

Bake packages into a custom image

FROM ghcr.io/paulmeier/claudeclaw-container:latest
RUN pnpm add -g cowsay some-other-pkg

Then point docker-compose.yml at the new image:

services:
  claudeclaw:
    image: my/claudeclaw:latest

Node version migration

pnpm global packages face the same native addon problem as npm globals — .node binaries compiled against the old ABI fail silently under a new Node major version. Run migrate-pnpm.sh to reinstall:

docker compose exec claudeclaw /migrate-pnpm.sh

The script calls pnpm ls --global --json --depth=0 to enumerate installed packages, then reinstalls each pinned name@version with pnpm add -g, forcing recompilation of any native addons.

Caveats

  • Wiping the volume (docker compose down -v) removes all pnpm global packages and the store along with everything else. Use backup.sh to preserve them.
  • If the base image's Node major version bumps, native addons will break. Run /migrate-pnpm.sh to reinstall and recompile them.
  • du -sh /root/.claude/pnpm-* to audit space usage. The content-addressable store deduplicates package content but can still grow large if many different versions are installed over time.

Adding UV packages

uv is a fast Python package and project manager from Astral. It is pre-installed in the image. Unlike pip install --user, UV installs tools into isolated virtual environments so each tool has its own dependency tree with no conflicts.

How it works

On every start, entrypoint.sh exports:

Variable Effect
UV_TOOL_DIR=/root/.claude/uv-tools Isolated venvs for each uv tool install-ed package
UV_TOOL_BIN_DIR=/root/.claude/uv-tool-bin Shim scripts for tool executables; added to PATH
UV_CACHE_DIR=/root/.claude/uv-cache Download cache; also holds uvx ephemeral environments
UV_PYTHON_INSTALL_DIR=/root/.claude/uv-python Python versions downloaded via uv python install

Layout added to the volume:

/root/.claude/
├── uv-tools/       # one subdirectory per installed tool, each containing an isolated venv
├── uv-tool-bin/    # shim scripts on PATH
├── uv-cache/       # download cache; uvx/_/... for uvx ephemeral environments
└── uv-python/      # UV-managed Python installations

Installing a tool

uv tool install installs a Python application into its own isolated venv and creates a shim in uv-tool-bin/ so the executable is available on PATH:

docker compose exec claudeclaw uv tool install ruff
docker compose exec claudeclaw ruff --version   # shim persists across restarts

Upgrade a tool to a newer version:

docker compose exec claudeclaw uv tool install --upgrade ruff

List installed tools:

docker compose exec claudeclaw uv tool list

uvx

uvx runs a Python tool without permanently installing it — equivalent to npx for Python. UV downloads the package into a temporary environment in UV_CACHE_DIR and runs it:

docker compose exec claudeclaw uvx cowsay hello   # downloads on first call
docker compose exec claudeclaw uvx cowsay hello   # reads from volume cache

No persistent entry is left behind. The volume cache means subsequent calls are fast, even after container recreation.

Bake tools into a custom image

FROM ghcr.io/paulmeier/claudeclaw-container:latest
RUN uv tool install ruff httpie

Python version migration

UV tools run in isolated venvs that record the absolute path of the Python interpreter they were created with (e.g. /usr/bin/python3.11). This is different from the pip problem: pip packages become invisible because the version-keyed directory is no longer searched; UV tool venvs become invalid because the recorded interpreter path no longer exists. The symptom is also different — pip-installed scripts silently produce import errors, while UV tool shims fail immediately at exec time.

If the base image's system Python minor version changes (e.g. 3.11 → 3.12), run migrate-uv.sh to recreate each venv under the current Python:

docker compose exec claudeclaw /migrate-uv.sh

The script reads the original package name and version specifier from each tool's uv-receipt.json, then calls uv tool install --reinstall to rebuild the venv.

uvx environments are simpler to recover — they are ephemeral by design. The cache just makes repeat calls fast; nothing needs to be reinstalled. If a uvx environment is broken, clear the cache and it will be rebuilt on the next call:

docker compose exec claudeclaw uv cache clean

Caveats

  • Wiping the volume (docker compose down -v) removes all UV tool venvs and the cache. Use backup.sh to preserve them.
  • If the base image's Python minor version bumps, tool venvs become stale. Run /migrate-uv.sh to recreate them.
  • UV-managed Pythons (uv python install) are persisted under uv-python/ in the volume. If you rely on a specific UV-managed Python for your tools, it will survive image rebuilds without being re-fetched.
  • du -sh /root/.claude/uv-* to audit space usage.

Backups

backup.sh snapshots the entire claudeclaw data — credentials, settings, logs, jobs, whisper models, plugins, and session history — into a timestamped archive. It can be run from the host or from inside the container.

From the host

./backup.sh
# Saved: ./backups/claudeclaw-2026-05-09-143022.tar.gz (187M)

Archives are written to ./backups/ by default. Override with CLAUDECLAW_BACKUP_DIR:

CLAUDECLAW_BACKUP_DIR=~/Backups/claudeclaw ./backup.sh

The script accesses the volume via a temporary Docker container, so it is safe to run while the daemon is running.

From inside the container

Mount a backup destination into the container, then run /backup.sh:

# One-off via docker compose run
docker compose run -v ~/Backups/claudeclaw:/backup claudeclaw /backup.sh

# Or exec into a running container
docker compose exec -e CLAUDECLAW_BACKUP_DIR=/backup claudeclaw /backup.sh

To make this permanent, uncomment the backup mount in docker-compose.yml:

volumes:
  - claudeclaw-data:/root/.claude
  - ${HOME}/Backups/claudeclaw:/backup

Then from any shell inside the container:

/backup.sh
# or with a custom path:
CLAUDECLAW_BACKUP_DIR=/backup /backup.sh

Restore

docker compose down
docker volume rm claudeclaw-data
docker run --rm \
  -v claudeclaw-data:/data \
  -v /path/to/backups:/backup:ro \
  alpine tar xzf /backup/claudeclaw-2026-05-09-143022.tar.gz -C /data
docker compose up -d

zsh alias

Add to your ~/.zshrc to run a host-side backup from anywhere:

alias claudeclaw-backup='/bin/zsh -l /Users/you/Projects/claudeclaw-container/backup.sh'

Then source ~/.zshrc and call claudeclaw-backup whenever you want a snapshot.


Desktop terminal access

shell.sh starts the container if it isn't running and drops you straight into the Claude CLI inside it:

./shell.sh

iTerm2 profile

Create a dedicated iTerm2 profile so you can open a claudeclaw terminal from the menu or a hotkey:

  1. iTerm2 → Settings → Profiles → +
  2. Name it claudeclaw
  3. Under Command, select Command and enter:
    /bin/zsh -l /Users/you/Projects/claudeclaw-container/shell.sh
    
  4. Optionally assign a hotkey under Keys → Hotkey Window for instant access

The -l flag loads your login shell environment so docker is on the PATH.

zsh alias

Add to your ~/.zshrc for one-word access from any terminal:

alias claudeclaw='/bin/zsh -l /Users/you/Projects/claudeclaw-container/shell.sh'

Then run source ~/.zshrc and type claudeclaw anywhere.


Health check

healthcheck.sh runs automatically at every container start and is available as /healthcheck (no .sh) at any time:

docker compose exec claudeclaw /healthcheck

It reports:

  • Runtimes — Node version and ABI, npm, pnpm, Bun, Python, pip, and uv versions
  • npm globals — installed packages with versions; warns if the Node ABI has changed since the last start (indicating native addons may be broken)
  • pnpm globals — same ABI check for pnpm-managed globals
  • pip user packages — lists installed packages; warns if stale pythonX.Y/site-packages/ directories from an older Python minor version are found
  • uv tools — lists installed tools; detects broken venvs by checking that the Python interpreter path recorded in each venv's pyvenv.cfg still exists on disk
  • Volume disk usage — size of every package manager directory under /root/.claude/
  • Environment — the active values of all package-manager env vars (NPM_CONFIG_PREFIX, PYTHONUSERBASE, UV_TOOL_DIR, etc.) so you can verify they point where expected

Warnings include the exact migration command to run. The script exits 0 regardless of warnings — they are advisory and do not block startup.


Troubleshooting

Container exits immediately Check logs: docker compose logs. Usually a missing or malformed settings.json.

Web dashboard not loading Ensure web.enabled is true and web.host is "0.0.0.0" in settings. The entrypoint auto-corrects 127.0.0.10.0.0.0, but other values are left as-is.

Bot not responding to messages

  • Confirm the token is correct and the user ID is in allowedUserIds
  • For Discord: verify Message Content Intent is enabled in the developer portal
  • Check logs: docker compose logs -f

Whisper download fails on first start The container needs outbound internet access. If behind a proxy, set HTTP_PROXY / HTTPS_PROXY environment variables in docker-compose.yml.

Packages or tools stopped working after pulling a new image A base image update may have bumped the Node or Python minor version, breaking previously installed packages. Run the relevant migration script(s) inside the container:

What broke Script Trigger
npm install -g packages with native addons /migrate-npm.sh Node major version bump
pnpm add -g packages with native addons /migrate-pnpm.sh Node major version bump
npx cached packages with native addons rm -rf /root/.claude/npm-cache/_npx Node major version bump
pip install packages (import errors / missing) /migrate-python.sh Python minor version bump
uv tool install tools (exec fails immediately) /migrate-uv.sh Python minor version bump
uvx cached environments uv cache clean Python minor version bump

Pure JavaScript npm/pnpm packages and pure Python pip packages with no native extensions survive version bumps without any migration.

Install Claudeclaw on Unraid in a few clicks.

Find Claudeclaw in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.

Open the Apps tab on your Unraid server Search Community Apps for Claudeclaw Review the template variables and paths Click Install

Requirements

A Claude Code subscription (claude.ai/code) is required. After starting the container for the first time, open a terminal to it and run: claude login

Categories

Download Statistics

738
Total Downloads

Related apps

Explore more like this

Explore all

Details

Repository
ghcr.io/paulmeier/claudeclaw-container:latest
Last Updated2026-05-15
First Seen2026-05-17

Runtime arguments

Web UI
http://[IP]:[PORT:4632]
Network
bridge
Shell
sh
Privileged
false
Extra Params
--restart=unless-stopped

Template configuration

Web Dashboard PortPorttcp

Port for the claudeclaw web dashboard.

Target
4632
Default
4632
Value
4632
IS_SANDBOXVariable

Tells Claude Code that this is a sandboxed environment, so it accepts --dangerously-skip-permissions when running as root. Required for claudeclaw to function inside this container. Do not unset.

Default
1
Value
1
App DataPathrw

Stores Claude Code credentials, claudeclaw settings, logs, jobs, and Whisper models. Must persist across restarts.

Target
/root/.claude
Default
/mnt/user/appdata/claudeclaw
Value
/mnt/user/appdata/claudeclaw
Notes DirectoryPathro

Optional host directory for claudeclaw to read. Mounted inside the container at /root/notes so it falls within the default 'moderate' security scope. Reference it in prompts and jobs as /root/notes.

Target
/root/notes
Documents DirectoryPathro

Optional host directory for claudeclaw to read. Mounted inside the container at /root/documents so it falls within the default 'moderate' security scope. Reference it in prompts and jobs as /root/documents.

Target
/root/documents
Backup DirectoryPathrw

Optional host directory for backups. Run /backup.sh inside the container to create a timestamped snapshot of all claudeclaw data here.

Target
/backup
TailScale Fallback State DirectoryVariable

Container directory where Unraid's CA Tailscale integration stores its state (machine key, node info). Kept under /root/.claude so it persists across container recreation and image updates. Only used when Tailscale is enabled via the Docker template's Advanced View.

Target
CA_TS_FALLBACK_DIR
Default
/root/.claude/tailscale
Value
/root/.claude/tailscale