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-container-config-db-1778347989.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
Each agent group has a row in thecontainer_configs table holding its custom apt and npm package lists. 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
ncl groups config add-package / remove-package. Restart the running container with ncl groups restart --id <group-id> --rebuild to apply changes — package CLI operations no longer auto-kill containers.
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 agent group’s row from the
container_configs table, materializes a snapshot to container.json for the container to read, 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 snapshot of the container_configs row |
| 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 container process exits — used by restart flows to guarantee the old container is fully gone before respawn - On close/error, the session is marked stopped and typing indicators are cleared
Explicit restart and on-wake messages
Container restart is always explicit — config CLI operations do not auto-kill containers. Use the admin CLI:--messagewrites an on-wake message that is only delivered to the fresh container’s first poll iteration (via theon_wakeflag onmessages_in)--rebuildrebuilds the per-agent-group image first- From inside a container,
--idis auto-filled and only the calling session is restarted
install_packages, add_mcp_server) use the same flow.
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