Step-by-step guide for running NanoClaw in Docker Sandboxes from scratch without the install script. Covers proxy patches, DinD mount fixes, channel setup, networking details, and troubleshooting. Validated on macOS (Apple Silicon) with WhatsApp — other channels and environments may need additional proxy patches. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
12 KiB
Running NanoClaw in Docker Sandboxes (Manual Setup)
This guide walks through setting up NanoClaw inside a Docker Sandbox from scratch — no install script, no pre-built fork. You'll clone the upstream repo, apply the necessary patches, and have agents running in full hypervisor-level isolation.
Architecture
Host (macOS / Windows WSL)
└── Docker Sandbox (micro VM with isolated kernel)
├── NanoClaw process (Node.js)
│ ├── Channel adapters (WhatsApp, Telegram, etc.)
│ └── Container spawner → nested Docker daemon
└── Docker-in-Docker
└── nanoclaw-agent containers
└── Claude Agent SDK
Each agent runs in its own container, inside a micro VM that is fully isolated from your host. Two layers of isolation: per-agent containers + the VM boundary.
The sandbox provides a MITM proxy at host.docker.internal:3128 that handles network access and injects your Anthropic API key automatically.
Note: This guide is based on a validated setup running on macOS (Apple Silicon) with WhatsApp. Other channels (Telegram, Slack, etc.) and environments (Windows WSL) may require additional proxy patches for their specific HTTP/WebSocket clients. The core patches (container runner, credential proxy, Dockerfile) apply universally — channel-specific proxy configuration varies.
Prerequisites
- Docker Desktop v4.40+ with Sandbox support
- Anthropic API key (the sandbox proxy manages injection)
- For Telegram: a bot token from @BotFather and your chat ID
- For WhatsApp: a phone with WhatsApp installed
Verify sandbox support:
docker sandbox version
Step 1: Create the Sandbox
On your host machine:
# Create a workspace directory
mkdir -p ~/nanoclaw-workspace
# Create a shell sandbox with the workspace mounted
docker sandbox create shell ~/nanoclaw-workspace
If you're using WhatsApp, configure proxy bypass so WhatsApp's Noise protocol isn't MITM-inspected:
docker sandbox network proxy shell-nanoclaw-workspace \
--bypass-host web.whatsapp.com \
--bypass-host "*.whatsapp.com" \
--bypass-host "*.whatsapp.net"
Telegram does not need proxy bypass.
Enter the sandbox:
docker sandbox run shell-nanoclaw-workspace
Step 2: Install Prerequisites
Inside the sandbox:
sudo apt-get update && sudo apt-get install -y build-essential python3
npm config set strict-ssl false
Step 3: Clone and Install NanoClaw
NanoClaw must live inside the workspace directory — Docker-in-Docker can only bind-mount from the shared workspace path.
# Clone to home first (virtiofs can corrupt git pack files during clone)
cd ~
git clone https://github.com/qwibitai/nanoclaw.git
# Replace with YOUR workspace path (the host path you passed to `docker sandbox create`)
WORKSPACE=/Users/you/nanoclaw-workspace
# Move into workspace so DinD mounts work
mv nanoclaw "$WORKSPACE/nanoclaw"
cd "$WORKSPACE/nanoclaw"
# Install dependencies
npm install
npm install https-proxy-agent
Step 4: Apply Proxy and Sandbox Patches
NanoClaw needs several patches to work inside a Docker Sandbox. These handle proxy routing, CA certificates, and Docker-in-Docker mount restrictions.
4a. Dockerfile — proxy args for container image build
npm install inside docker build fails with SELF_SIGNED_CERT_IN_CHAIN because the sandbox's MITM proxy presents its own certificate. Add proxy build args to container/Dockerfile:
Add these lines after the FROM line:
# Accept proxy build args
ARG http_proxy
ARG https_proxy
ARG no_proxy
ARG NODE_EXTRA_CA_CERTS
ARG npm_config_strict_ssl=true
RUN npm config set strict-ssl ${npm_config_strict_ssl}
And after the RUN npm install line:
RUN npm config set strict-ssl true
4b. Build script — forward proxy args
Patch container/build.sh to pass proxy env vars to docker build:
Add these --build-arg flags to the docker build command:
--build-arg http_proxy="${http_proxy:-$HTTP_PROXY}" \
--build-arg https_proxy="${https_proxy:-$HTTPS_PROXY}" \
--build-arg no_proxy="${no_proxy:-$NO_PROXY}" \
--build-arg npm_config_strict_ssl=false \
4c. Container runner — proxy forwarding, CA cert mount, /dev/null fix
Three changes to src/container-runner.ts:
Replace /dev/null shadow mount. The sandbox rejects /dev/null bind mounts. Find where .env is shadow-mounted to /dev/null and replace it with an empty file:
// Create an empty file to shadow .env (Docker Sandbox rejects /dev/null mounts)
const emptyEnvPath = path.join(DATA_DIR, 'empty-env');
if (!fs.existsSync(emptyEnvPath)) fs.writeFileSync(emptyEnvPath, '');
// Use emptyEnvPath instead of '/dev/null' in the mount
Forward proxy env vars to spawned agent containers. Add -e flags for HTTP_PROXY, HTTPS_PROXY, NO_PROXY and their lowercase variants.
Mount CA certificate. If NODE_EXTRA_CA_CERTS or SSL_CERT_FILE is set, copy the cert into the project directory and mount it into agent containers:
const caCertSrc = process.env.NODE_EXTRA_CA_CERTS || process.env.SSL_CERT_FILE;
if (caCertSrc) {
const certDir = path.join(DATA_DIR, 'ca-cert');
fs.mkdirSync(certDir, { recursive: true });
fs.copyFileSync(caCertSrc, path.join(certDir, 'proxy-ca.crt'));
// Mount: certDir -> /workspace/ca-cert (read-only)
// Set NODE_EXTRA_CA_CERTS=/workspace/ca-cert/proxy-ca.crt in the container
}
4d. Container runtime — prevent self-termination
In src/container-runtime.ts, the cleanupOrphans() function matches containers by the nanoclaw- prefix. Inside a sandbox, the sandbox container itself may match (e.g., nanoclaw-docker-sandbox). Filter out the current hostname:
// In cleanupOrphans(), filter out os.hostname() from the list of containers to stop
4e. Credential proxy — route through MITM proxy
In src/credential-proxy.ts, upstream API requests need to go through the sandbox proxy. Add HttpsProxyAgent to outbound requests:
import { HttpsProxyAgent } from 'https-proxy-agent';
const proxyUrl = process.env.HTTPS_PROXY || process.env.https_proxy;
const upstreamAgent = proxyUrl ? new HttpsProxyAgent(proxyUrl) : undefined;
// Pass upstreamAgent to https.request() options
4f. Setup script — proxy build args
Patch setup/container.ts to pass the same proxy --build-arg flags as build.sh (Step 4b).
Step 5: Build
npm run build
bash container/build.sh
Step 6: Add a Channel
Telegram
# Apply the Telegram skill
npx tsx scripts/apply-skill.ts .claude/skills/add-telegram
# Rebuild after applying the skill
npm run build
# Configure .env
cat > .env << EOF
TELEGRAM_BOT_TOKEN=<your-token-from-botfather>
ASSISTANT_NAME=nanoclaw
ANTHROPIC_API_KEY=proxy-managed
EOF
mkdir -p data/env && cp .env data/env/env
# Register your chat
npx tsx setup/index.ts --step register \
--jid "tg:<your-chat-id>" \
--name "My Chat" \
--trigger "@nanoclaw" \
--folder "telegram_main" \
--channel telegram \
--assistant-name "nanoclaw" \
--is-main \
--no-trigger-required
To find your chat ID: Send any message to your bot, then:
curl -s --proxy $HTTPS_PROXY "https://api.telegram.org/bot<TOKEN>/getUpdates" | python3 -m json.tool
Telegram in groups: Disable Group Privacy in @BotFather (/mybots > Bot Settings > Group Privacy > Turn off), then remove and re-add the bot.
Important: If the Telegram skill creates src/channels/telegram.ts, you'll need to patch it for proxy support. Add an HttpsProxyAgent and pass it to grammy's Bot constructor via baseFetchConfig.agent. Then rebuild.
Make sure you configured proxy bypass in Step 1 first.
# Apply the WhatsApp skill
npx tsx scripts/apply-skill.ts .claude/skills/add-whatsapp
# Rebuild
npm run build
# Configure .env
cat > .env << EOF
ASSISTANT_NAME=nanoclaw
ANTHROPIC_API_KEY=proxy-managed
EOF
mkdir -p data/env && cp .env data/env/env
# Authenticate (choose one):
# QR code — scan with WhatsApp camera:
npx tsx src/whatsapp-auth.ts
# OR pairing code — enter code in WhatsApp > Linked Devices > Link with phone number:
npx tsx src/whatsapp-auth.ts --pairing-code --phone <phone-number-no-plus>
# Register your chat (JID = your phone number + @s.whatsapp.net)
npx tsx setup/index.ts --step register \
--jid "<phone>@s.whatsapp.net" \
--name "My Chat" \
--trigger "@nanoclaw" \
--folder "whatsapp_main" \
--channel whatsapp \
--assistant-name "nanoclaw" \
--is-main \
--no-trigger-required
Important: The WhatsApp skill files (src/channels/whatsapp.ts and src/whatsapp-auth.ts) also need proxy patches — add HttpsProxyAgent for WebSocket connections and a proxy-aware version fetch. Then rebuild.
Both Channels
Apply both skills, patch both for proxy support, combine the .env variables, and register each chat separately.
Step 7: Run
npm start
You don't need to set ANTHROPIC_API_KEY manually. The sandbox proxy intercepts requests and replaces proxy-managed with your real key automatically.
Networking Details
How the proxy works
All traffic from the sandbox routes through the host proxy at host.docker.internal:3128:
Agent container → DinD bridge → Sandbox VM → host.docker.internal:3128 → Host proxy → api.anthropic.com
"Bypass" does not mean traffic skips the proxy. It means the proxy passes traffic through without MITM inspection. Node.js doesn't automatically use HTTP_PROXY env vars — you need explicit HttpsProxyAgent configuration in every HTTP/WebSocket client.
Shared paths for DinD mounts
Only the workspace directory is available for Docker-in-Docker bind mounts. Paths outside the workspace fail with "path not shared":
/dev/null→ replace with an empty file in the project dir/usr/local/share/ca-certificates/→ copy cert to project dir/home/agent/→ clone to workspace instead
Git clone and virtiofs
The workspace is mounted via virtiofs. Git's pack file handling can corrupt over virtiofs during clone. Workaround: clone to /home/agent first, then mv into the workspace.
Troubleshooting
npm install fails with SELF_SIGNED_CERT_IN_CHAIN
npm config set strict-ssl false
Container build fails with proxy errors
docker build \
--build-arg http_proxy=$http_proxy \
--build-arg https_proxy=$https_proxy \
-t nanoclaw-agent:latest container/
Agent containers fail with "path not shared"
All bind-mounted paths must be under the workspace directory. Check:
- Is NanoClaw cloned into the workspace? (not
/home/agent/) - Is the CA cert copied to the project root?
- Has the empty
.envshadow file been created?
Agent containers can't reach Anthropic API
Verify proxy env vars are forwarded to agent containers. Check container logs for HTTP_PROXY=http://host.docker.internal:3128.
WhatsApp error 405
The version fetch is returning a stale version. Make sure the proxy-aware fetchWaVersionViaProxy patch is applied — it fetches sw.js through HttpsProxyAgent and parses client_revision.
WhatsApp "Connection failed" immediately
Proxy bypass not configured. From the host, run:
docker sandbox network proxy <sandbox-name> \
--bypass-host web.whatsapp.com \
--bypass-host "*.whatsapp.com" \
--bypass-host "*.whatsapp.net"
Telegram bot doesn't receive messages
- Check the grammy proxy patch is applied (look for
HttpsProxyAgentinsrc/channels/telegram.ts) - Check Group Privacy is disabled in @BotFather if using in groups
Git clone fails with "inflate: data stream error"
Clone to a non-workspace path first, then move:
cd ~ && git clone https://github.com/qwibitai/nanoclaw.git && mv nanoclaw /path/to/workspace/nanoclaw
WhatsApp QR code doesn't display
Run the auth command interactively inside the sandbox (not piped through docker sandbox exec):
docker sandbox run shell-nanoclaw-workspace
# Then inside:
npx tsx src/whatsapp-auth.ts