feat: convert container runtime from Apple Container to Docker (#323)

Swap container-runtime.ts to the Docker variant:
- CONTAINER_RUNTIME_BIN: 'container' → 'docker'
- readonlyMountArgs: --mount bind,readonly → -v host:container:ro
- ensureContainerRuntimeRunning: container system status → docker info
- cleanupOrphans: Apple Container JSON format → docker ps --filter
- build.sh default: container → docker

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-02-20 13:20:13 +02:00
committed by GitHub
parent 51a50d402b
commit 607623aa59
3 changed files with 50 additions and 100 deletions

View File

@@ -1,17 +1,17 @@
/**
* Container runtime abstraction for NanoClaw.
* Swap this file to switch between container runtimes (e.g. Docker).
* All runtime-specific logic lives here so swapping runtimes means changing one file.
*/
import { execSync } from 'child_process';
import { logger } from './logger.js';
/** The container runtime binary name. */
export const CONTAINER_RUNTIME_BIN = 'container';
export const CONTAINER_RUNTIME_BIN = 'docker';
/** Returns CLI args for a readonly bind mount. */
export function readonlyMountArgs(hostPath: string, containerPath: string): string[] {
return ['--mount', `type=bind,source=${hostPath},target=${containerPath},readonly`];
return ['-v', `${hostPath}:${containerPath}:ro`];
}
/** Returns the shell command to stop a container by name. */
@@ -22,52 +22,46 @@ export function stopContainer(name: string): string {
/** Ensure the container runtime is running, starting it if needed. */
export function ensureContainerRuntimeRunning(): void {
try {
execSync(`${CONTAINER_RUNTIME_BIN} system status`, { stdio: 'pipe' });
execSync(`${CONTAINER_RUNTIME_BIN} info`, { stdio: 'pipe', timeout: 10000 });
logger.debug('Container runtime already running');
} catch {
logger.info('Starting container runtime...');
try {
execSync(`${CONTAINER_RUNTIME_BIN} system start`, { stdio: 'pipe', timeout: 30000 });
logger.info('Container runtime started');
} catch (err) {
logger.error({ err }, 'Failed to start container runtime');
console.error(
'\n╔════════════════════════════════════════════════════════════════╗',
);
console.error(
'║ FATAL: Container runtime failed to start ║',
);
console.error(
'║ ║',
);
console.error(
'║ Agents cannot run without a container runtime. To fix: ║',
);
console.error(
'║ 1. Ensure your container runtime is installed ║',
);
console.error(
'║ 2. Start the runtime, then restart NanoClaw ║',
);
console.error(
'╚════════════════════════════════════════════════════════════════╝\n',
);
throw new Error('Container runtime is required but failed to start');
}
} catch (err) {
logger.error({ err }, 'Failed to reach container runtime');
console.error(
'\n╔════════════════════════════════════════════════════════════════╗',
);
console.error(
'║ FATAL: Container runtime failed to start ║',
);
console.error(
'║ ║',
);
console.error(
'║ Agents cannot run without a container runtime. To fix: ║',
);
console.error(
'║ 1. Ensure Docker is installed and running ║',
);
console.error(
'║ 2. Run: docker info ║',
);
console.error(
'║ 3. Restart NanoClaw ║',
);
console.error(
'╚════════════════════════════════════════════════════════════════╝\n',
);
throw new Error('Container runtime is required but failed to start');
}
}
/** Kill orphaned NanoClaw containers from previous runs. */
export function cleanupOrphans(): void {
try {
const output = execSync(`${CONTAINER_RUNTIME_BIN} ls --format json`, {
stdio: ['pipe', 'pipe', 'pipe'],
encoding: 'utf-8',
});
const containers: { status: string; configuration: { id: string } }[] = JSON.parse(output || '[]');
const orphans = containers
.filter((c) => c.status === 'running' && c.configuration.id.startsWith('nanoclaw-'))
.map((c) => c.configuration.id);
const output = execSync(
`${CONTAINER_RUNTIME_BIN} ps --filter name=nanoclaw- --format '{{.Names}}'`,
{ stdio: ['pipe', 'pipe', 'pipe'], encoding: 'utf-8' },
);
const orphans = output.trim().split('\n').filter(Boolean);
for (const name of orphans) {
try {
execSync(stopContainer(name), { stdio: 'pipe' });