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.
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.
- 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 versionOn your host machine:
# Create a workspace directory
mkdir -p ~/nanoclaw-workspace
# Create a shell sandbox with the workspace mounted
docker sandbox create shell ~/nanoclaw-workspaceIf 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-workspaceInside the sandbox:
sudo apt-get update && sudo apt-get install -y build-essential python3
npm config set strict-ssl falseNanoClaw 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-agentNanoClaw needs several patches to work inside a Docker Sandbox. These handle proxy routing, CA certificates, and Docker-in-Docker mount restrictions.
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 truePatch 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 \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 mountForward 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
}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 stopIn 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() optionsPatch setup/container.ts to pass the same proxy --build-arg flags as build.sh (Step 4b).
npm run build
bash container/build.sh# 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-requiredTo 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.toolTelegram 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-requiredImportant: 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.
Apply both skills, patch both for proxy support, combine the .env variables, and register each chat separately.
npm startYou don't need to set ANTHROPIC_API_KEY manually. The sandbox proxy intercepts requests and replaces proxy-managed with your real key automatically.
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.
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
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.
npm config set strict-ssl falsedocker build \
--build-arg http_proxy=$http_proxy \
--build-arg https_proxy=$https_proxy \
-t nanoclaw-agent:latest container/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?
Verify proxy env vars are forwarded to agent containers. Check container logs for HTTP_PROXY=http://host.docker.internal:3128.
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.
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"- Check the grammy proxy patch is applied (look for
HttpsProxyAgentinsrc/channels/telegram.ts) - Check Group Privacy is disabled in @BotFather if using in groups
Clone to a non-workspace path first, then move:
cd ~ && git clone https://github.com/qwibitai/nanoclaw.git && mv nanoclaw /path/to/workspace/nanoclawRun 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