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-1778347658.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 in theircontainer_configs row (managed via ncl 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
- Rebuilds are triggered by
ncl groups restart --rebuildor by theinstall_packagesself-mod tool (config writes alone don’t restart or rebuild)
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
container_configs row, materializes it to groups/<folder>/container.json for the container runner, 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 view of the container_configs row, regenerated each spawn |
| 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- On close/error, the session is marked stopped and typing indicators are cleared
killContaineraccepts an optionalonExitcallback that fires after the container process has fully exited, used by the restart flow to guarantee race-free respawn
Restart and on-wake messages
ncl groups restart (and the agent-side self-mod handlers) kills running containers for the group and respawns them. When a wake message is supplied, it is written to messages_in with on_wake = 1, and the container poll loop only delivers on_wake rows on its first iteration after spawn. This prevents a dying container — still draining its SIGTERM grace window — from picking up the wake message intended for the fresh process.
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