Convert to Docker
This skill migrates NanoClaw from Apple Container (macOS-only) to Docker for cross-platform support (macOS and Linux).
What this changes:
-
Container runtime: Apple Container → Docker
-
Mount syntax: --mount type=bind,...,readonly → -v path:path:ro
-
Startup check: container system status → docker info
-
Build commands: container build/run → docker build/run
What stays the same:
-
Dockerfile (already Docker-compatible)
-
Agent runner code
-
Mount security/allowlist validation
-
All other functionality
Prerequisites
Verify Docker is installed before starting:
docker --version && docker info >/dev/null 2>&1 && echo "Docker ready" || echo "Install Docker first"
If Docker is not installed:
-
macOS: Download from https://docker.com/products/docker-desktop
-
Linux: curl -fsSL https://get.docker.com | sh && sudo systemctl start docker
- Update Container Runner
Edit src/container-runner.ts :
1a. Update module comment (around line 3)
// Before:
- Spawns agent execution in Apple Container and handles IPC
// After:
- Spawns agent execution in Docker container and handles IPC
1b. Update directory mount comment (around line 88)
// Before: // Apple Container only supports directory mounts, not file mounts
// After: // Docker bind mounts work with both files and directories
1c. Update env workaround comment (around line 120)
// Before: // Environment file directory (workaround for Apple Container -i env var bug)
// After: // Environment file directory (keeps credentials out of process listings)
1d. Update buildContainerArgs function
Replace the entire function with Docker mount syntax:
function buildContainerArgs(mounts: VolumeMount[]): string[] { const args: string[] = ['run', '-i', '--rm'];
// Docker: -v with :ro suffix for readonly
for (const mount of mounts) {
if (mount.readonly) {
args.push('-v', ${mount.hostPath}:${mount.containerPath}:ro);
} else {
args.push('-v', ${mount.hostPath}:${mount.containerPath});
}
}
args.push(CONTAINER_IMAGE);
return args; }
1e. Update spawn command (around line 204)
// Before: const container = spawn('container', containerArgs, {
// After: const container = spawn('docker', containerArgs, {
- Update Startup Check
Edit src/index.ts :
2a. Replace the container system check function
Find ensureContainerSystemRunning() and replace entirely with:
function ensureDockerRunning(): void { try { execSync('docker info', { stdio: 'pipe', timeout: 10000 }); logger.debug('Docker daemon is running'); } catch { logger.error('Docker daemon is not running'); console.error('\n╔════════════════════════════════════════════════════════════════╗'); console.error('║ FATAL: Docker is not running ║'); console.error('║ ║'); console.error('║ Agents cannot run without Docker. To fix: ║'); console.error('║ macOS: Start Docker Desktop ║'); console.error('║ Linux: sudo systemctl start docker ║'); console.error('║ ║'); console.error('║ Install from: https://docker.com/products/docker-desktop ║'); console.error('╚════════════════════════════════════════════════════════════════╝\n'); throw new Error('Docker is required but not running'); } }
2b. Update the function call in main()
// Before: ensureContainerSystemRunning();
// After: ensureDockerRunning();
- Update Build Script
Edit container/build.sh :
3a. Update build command (around line 15-16)
Before:
Build with Apple Container
container build -t "${IMAGE_NAME}:${TAG}" .
After:
Build with Docker
docker build -t "${IMAGE_NAME}:${TAG}" .
3b. Update test command (around line 23)
Before:
echo " echo '{...}' | container run -i ${IMAGE_NAME}:${TAG}"
After:
echo " echo '{...}' | docker run -i ${IMAGE_NAME}:${TAG}"
- Update Documentation
Update references in documentation files:
File Find Replace
CLAUDE.md
"Apple Container (Linux VMs)" "Docker containers"
README.md
"Apple containers" "Docker containers"
README.md
"Apple Container" "Docker"
README.md
Requirements section Update to show Docker instead
docs/REQUIREMENTS.md
"Apple Container" "Docker"
docs/SPEC.md
"APPLE CONTAINER" "DOCKER CONTAINER"
docs/SPEC.md
All Apple Container references Docker equivalents
Key README.md updates:
Requirements section:
Requirements
- macOS or Linux
- Node.js 20+
- Claude Code
- Docker
FAQ - "Why Docker?":
Why Docker?
Docker provides cross-platform support (macOS and Linux), a large ecosystem, and mature tooling. Docker Desktop on macOS uses a lightweight Linux VM similar to other container solutions.
FAQ - "Can I run this on Linux?":
Can I run this on Linux?
Yes. NanoClaw uses Docker, which works on both macOS and Linux. Just install Docker and run /setup.
- Update Skills
5a. Update .claude/skills/setup/SKILL.md
Replace Section 2 "Install Apple Container" with Docker installation:
2. Install Docker
Check if Docker is installed and running:
```bash docker --version && docker info >/dev/null 2>&1 && echo "Docker is running" || echo "Docker not running or not installed" ```
If not installed or not running, tell the user:
Docker is required for running agents in isolated environments.
macOS:
- Download Docker Desktop from https://docker.com/products/docker-desktop
- Install and start Docker Desktop
- Wait for the whale icon in the menu bar to stop animating
Linux: ```bash curl -fsSL https://get.docker.com | sh sudo systemctl start docker sudo usermod -aG docker $USER # Then log out and back in ```
Let me know when you've completed these steps.
Wait for user confirmation, then verify:
```bash docker run --rm hello-world ```
Update build verification:
Verify the build succeeded:
```bash docker images | grep nanoclaw-agent echo '{}' | docker run -i --entrypoint /bin/echo nanoclaw-agent:latest "Container OK" || echo "Container build failed" ```
Update troubleshooting section to reference Docker commands.
5b. Update .claude/skills/debug/SKILL.md
Replace all container commands with docker equivalents:
Before After
container run
docker run
container system status
docker info
container builder prune
docker builder prune
container images
docker images
--mount type=bind,source=...,readonly
-v ...:ro
Update the architecture diagram header:
Host (macOS/Linux) Container (Docker)
- Build and Verify
After making all changes:
Compile TypeScript
npm run build
Build Docker image
./container/build.sh
Verify image exists
docker images | grep nanoclaw-agent
- Test the Migration
7a. Test basic container execution
echo '{}' | docker run -i --entrypoint /bin/echo nanoclaw-agent:latest "Container OK"
7b. Test readonly mounts
mkdir -p /tmp/test-ro && echo "test" > /tmp/test-ro/file.txt
docker run --rm --entrypoint /bin/bash -v /tmp/test-ro:/test:ro nanoclaw-agent:latest
-c "cat /test/file.txt && touch /test/new.txt 2>&1 || echo 'Write blocked (expected)'"
rm -rf /tmp/test-ro
Expected: Read succeeds, write fails with "Read-only file system".
7c. Test read-write mounts
mkdir -p /tmp/test-rw
docker run --rm --entrypoint /bin/bash -v /tmp/test-rw:/test nanoclaw-agent:latest
-c "echo 'test write' > /test/new.txt && cat /test/new.txt"
cat /tmp/test-rw/new.txt && rm -rf /tmp/test-rw
Expected: Both operations succeed.
7d. Full integration test
npm run dev
Send @AssistantName hello via WhatsApp
Verify response received
Troubleshooting
Docker not running:
-
macOS: Start Docker Desktop from Applications
-
Linux: sudo systemctl start docker
-
Verify: docker info
Permission denied on Docker socket (Linux):
sudo usermod -aG docker $USER
Log out and back in
Image build fails:
Clean rebuild
docker builder prune -af ./container/build.sh
Container can't write to mounted directories: Check directory permissions on the host. The container runs as uid 1000.
Summary of Changed Files
File Type of Change
src/container-runner.ts
Mount syntax, spawn command, comments
src/index.ts
Startup check function
container/build.sh
Build and run commands
CLAUDE.md
Quick context
README.md
Requirements, FAQ
docs/REQUIREMENTS.md
Architecture references
docs/SPEC.md
Architecture diagram, tech stack
.claude/skills/setup/SKILL.md
Installation instructions
.claude/skills/debug/SKILL.md
Debug commands