NanoClaw runs agents in isolated Linux containers to provide security through OS-level process and filesystem isolation. In v2, the container runtime uses a two-database IO model instead of stdin/stdout piping, and the agent-runner runs on Bun instead of Node.js.Documentation Index
Fetch the complete documentation index at: https://qwibitai-nanoclaw-8-mintlify-v2-0-48-changelog-1778348024.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Runtime abstraction
All runtime-specific logic lives insrc/container-runtime.ts:
- Docker (default) — cross-platform support (macOS, Linux, Windows via WSL2)
- Apple Container (macOS only) — lightweight native runtime
CONTAINER_RUNTIME_BIN:
Apple Container vs Docker
Apple Container is Apple’s native virtualization framework (macOS 15+). It runs Linux containers without a VM layer like Docker Desktop.When to use Apple Container
- You’re on macOS 15 (Sequoia) or later
- You want to avoid installing Docker Desktop
- You want faster container startup
When to stick with Docker
- You’re on Linux or Windows (WSL2)
- You need cross-platform parity
- You’re deploying to a production server
Key differences
| Docker | Apple Container | |
|---|---|---|
| Binary | docker | container |
| Bind mounts | -v host:container:ro | --mount type=bind,source=...,target=...,readonly |
| Stop command | docker stop -t 1 name | container stop name |
| Health check | docker info | container system status |
| Platform | macOS, Linux, Windows (WSL2) | macOS 15+ only |
Switching runtimes
Run the/convert-to-apple-container skill in Claude Code. To revert, use git revert.
Container image
The agent container is built fromcontainer/Dockerfile and includes:
- Node.js 22 — base image runtime
- Bun (pinned to 1.3.12) — runs agent-runner TypeScript directly (no compilation)
- Chromium — browser automation via agent-browser
- Claude Code SDK —
@anthropic-ai/claude-codeinstalled globally via pnpm - tini — PID 1 signal forwarding (ensures outbound.db writes finalize on SIGTERM)
- pnpm (via corepack) — for global Node CLI installs
- System tools —
curl,git,ca-certificates,unzip - Optional CJK fonts —
fonts-noto-cjk(~200 MB, opt-in viaINSTALL_CJK_FONTS=true)
Key design decisions
- Source is NOT baked in —
/app/srcis a read-only bind mount from the host. Source changes never require an image rebuild. only-built-dependenciesallowlist in.npmrcforagent-browserand@anthropic-ai/claude-code- Runs as
nodeuser (non-root) with/workspace/groupas working directory - Entrypoint:
tini -> entrypoint.sh -> exec bun run /app/src/index.ts
Building the image
Per-agent-group images
Agent groups can specify custom packages via their container config (managed withncl groups config add-package/remove-package). The host builds a derived Docker image:
- Tag: derived from the checkout-scoped base image and agent group
- Built on top of
nanoclaw-agent-v2-<slug>:latest - Adds custom apt and npm packages
Two-database IO model
In v2, all communication between host and container uses two SQLite databases per session. There is no stdin/stdout piping, no IPC files, and no output markers.inbound.db (host writes, container reads)
| Table | Purpose |
|---|---|
messages_in | Inbound messages, tasks, system notifications |
delivered | Tracks delivery outcomes for outbound message IDs |
destinations | Live destination map (channels and other agents) |
session_routing | Default reply routing (channel_type, platform_id, thread_id) |
outbound.db (container writes, host reads)
| Table | Purpose |
|---|---|
messages_out | Outbound messages with deliver_after and recurrence |
processing_ack | Tracks which inbound messages the container has processed |
session_state | Persistent key/value store (e.g., SDK session ID for resume) |
container_state | Tool-in-flight state for stuck-detection |
Cross-mount invariants
Three invariants are critical for correctness:journal_mode=DELETE— WAL’s mmapped-shmdoesn’t refresh across Docker mounts- Host opens-writes-closes per operation — closing invalidates the container’s page cache
- One writer per file — DELETE-mode journal unlink isn’t atomic across the mount
Container lifecycle
Spawning containers
Containers are spawned by thespawnContainer function. Wake calls are deduplicated via an in-flight promise map.
Read agent group config
The host reads the
container_configs row for the agent group, materializes it to groups/<folder>/container.json, and resolves provider contributions.Build volume mounts
Mounts are built based on the session, agent group, and validated additional mounts.
Sync skill symlinks
Skill symlinks at
/home/node/.claude/skills/ are updated to point to /app/skills/{name}. These are dangling on the host but valid inside the container.Volume mounts
| Path | Container path | Mode | Purpose |
|---|---|---|---|
| Session folder | /workspace | RW | inbound.db, outbound.db, outbox/, inbox/ |
| Agent group folder | /workspace/agent | RW | Working files |
| container.json | /workspace/agent/container.json | RO | Materialized from the container_configs table at spawn time |
| Composed CLAUDE.md | /workspace/agent/CLAUDE.md | RO | Regenerated each spawn |
| Global memory | /workspace/global | RO | Shared instructions |
| Agent-runner source | /app/src | RO | Bind mount from host |
| Container skills | /app/skills | RO | Shared skill definitions |
| Claude SDK state | /home/node/.claude | RW | SDK state + skill symlinks |
| Additional mounts | /workspace/extra/{name} | Per-config | Validated against allowlist |
| Provider mounts | Various | Per-provider | Provider-contributed |
Timeouts and stale detection
Containers have two timeout/detection mechanisms:- Container timeout — maximum runtime before force kill (default: 30 minutes)
- Stale detection — host sweep checks
.heartbeatmtime andprocessing_ackage to detect stuck containers
Container shutdown
killContainer(sessionId, reason)stops the container viadocker stop, falls back to SIGKILL- An optional
onExitcallback fires after the process exits, guaranteeing the old container is gone before any respawn happens - On close/error, the session is marked stopped and typing indicators are cleared
Explicit restart with on-wake messages
Config CLI operations no longer auto-kill containers. To restart an agent group’s container, use:--message is provided, the host writes the wake message to messages_in with the on_wake flag set, kills the running container, and respawns via the onExit callback. The on_wake flag ensures the message is picked up only by the fresh container’s first poll iteration — a dying container in its SIGTERM grace period can never steal it.
Self-mod approval handlers (install_packages, add_mcp_server) use the same race-free mechanism: rebuild the image when needed, write an on_wake message, kill the container, and respawn via onExit.
CLI scope enforcement
The agent group’scli_scope setting (disabled, group, global) controls what the in-container agent can do via ncl. Enforcement is host-side, applied in the dispatcher:
disabledrejects everycli_requestand excludes thenclinstructions from the composed CLAUDE.mdgroup(default) restricts requests togroups,sessions,destinations, andmembersfor the agent group itself, auto-fills--idand group args, rejects cross-group access, blockscli_scopemutations, and runs post-handler result filtering to prevent cross-group data leaks in list responsesglobal(set automatically for owner agent groups) is unrestricted
Credential injection
The OneCLI SDK’sapplyContainerConfig() configures each container’s network to route through the vault:
- Injects
HTTPS_PROXYand CA certs into Docker args - All container API calls route through the vault
- No raw API keys are passed via environment variables
- Each agent group gets its own
agentIdentifierfor credential scoping
Debugging containers
List running containers
List running containers
View container logs
View container logs
Inspect container mounts
Inspect container mounts
Execute commands in running container
Execute commands in running container
Stop a running container
Stop a running container
Related pages
- Security model — container isolation and security boundaries
- Troubleshooting — common container issues