fix(docker): atomized volume setup and migration robustness

- src/infra/state-migrations.ts: Handle EBUSY errors on rename (Docker bind mount safe)

- Dockerfile: Add global CLI symlink and fix volume permissions

- docker-compose.yml: Use named volumes for isolation and explicit entrypoints
This commit is contained in:
Ricardo Trevisan 2026-01-29 19:49:33 -03:00
parent 4583f88626
commit c7bfcadf02
4 changed files with 35 additions and 32 deletions

View File

@ -32,6 +32,13 @@ RUN pnpm ui:build
ENV NODE_ENV=production
# Create global CLI symlink
RUN ln -s /app/moltbot.mjs /usr/local/bin/moltbot
# Ensure state directories exist and are owned by node
RUN mkdir -p /var/lib/moltbot /home/node/clawd && \
chown -R node:node /var/lib/moltbot /home/node/clawd
# Security hardening: Run as non-root user
# The node:22-bookworm image includes a 'node' user (uid 1000)
# This reduces the attack surface by preventing container escape via root privileges

View File

@ -1,16 +1,18 @@
services:
moltbot-gateway:
container_name: moltbot
image: ${CLAWDBOT_IMAGE:-moltbot:local}
environment:
HOME: /home/node
MOLTBOT_STATE_DIR: /var/lib/moltbot
TERM: xterm-256color
CLAWDBOT_GATEWAY_TOKEN: ${CLAWDBOT_GATEWAY_TOKEN}
CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY}
CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY}
CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE}
volumes:
- ${CLAWDBOT_CONFIG_DIR}:/home/node/.clawdbot
- ${CLAWDBOT_WORKSPACE_DIR}:/home/node/clawd
- moltbot_data:/var/lib/moltbot
- moltbot_workspace:/home/node/clawd
ports:
- "${CLAWDBOT_GATEWAY_PORT:-18789}:18789"
- "${CLAWDBOT_BRIDGE_PORT:-18790}:18790"
@ -19,27 +21,15 @@ services:
command:
[
"node",
"dist/index.js",
"/app/moltbot.mjs",
"gateway",
"--bind",
"${CLAWDBOT_GATEWAY_BIND:-lan}",
"--port",
"${CLAWDBOT_GATEWAY_PORT:-18789}"
"${CLAWDBOT_GATEWAY_PORT:-18789}",
"--allow-unconfigured"
]
moltbot-cli:
image: ${CLAWDBOT_IMAGE:-moltbot:local}
environment:
HOME: /home/node
TERM: xterm-256color
BROWSER: echo
CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY}
CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY}
CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE}
volumes:
- ${CLAWDBOT_CONFIG_DIR}:/home/node/.clawdbot
- ${CLAWDBOT_WORKSPACE_DIR}:/home/node/clawd
stdin_open: true
tty: true
init: true
entrypoint: ["node", "dist/index.js"]
volumes:
moltbot_data:
moltbot_workspace:

View File

@ -21,11 +21,11 @@ if ! docker compose version >/dev/null 2>&1; then
exit 1
fi
mkdir -p "${CLAWDBOT_CONFIG_DIR:-$HOME/.clawdbot}"
mkdir -p "${CLAWDBOT_WORKSPACE_DIR:-$HOME/clawd}"
mkdir -p "${CLAWDBOT_CONFIG_DIR:-$HOME/.moltbot}"
mkdir -p "${CLAWDBOT_WORKSPACE_DIR:-$HOME/moltbot}"
export CLAWDBOT_CONFIG_DIR="${CLAWDBOT_CONFIG_DIR:-$HOME/.clawdbot}"
export CLAWDBOT_WORKSPACE_DIR="${CLAWDBOT_WORKSPACE_DIR:-$HOME/clawd}"
export CLAWDBOT_CONFIG_DIR="${CLAWDBOT_CONFIG_DIR:-$HOME/.moltbot}"
export CLAWDBOT_WORKSPACE_DIR="${CLAWDBOT_WORKSPACE_DIR:-$HOME/moltbot}"
export CLAWDBOT_GATEWAY_PORT="${CLAWDBOT_GATEWAY_PORT:-18789}"
export CLAWDBOT_BRIDGE_PORT="${CLAWDBOT_BRIDGE_PORT:-18790}"
export CLAWDBOT_GATEWAY_BIND="${CLAWDBOT_GATEWAY_BIND:-lan}"
@ -62,7 +62,7 @@ YAML
if [[ -n "$home_volume" ]]; then
printf ' - %s:/home/node\n' "$home_volume" >>"$EXTRA_COMPOSE_FILE"
printf ' - %s:/home/node/.clawdbot\n' "$CLAWDBOT_CONFIG_DIR" >>"$EXTRA_COMPOSE_FILE"
printf ' - %s:/home/node/.moltbot\n' "$CLAWDBOT_CONFIG_DIR" >>"$EXTRA_COMPOSE_FILE"
printf ' - %s:/home/node/clawd\n' "$CLAWDBOT_WORKSPACE_DIR" >>"$EXTRA_COMPOSE_FILE"
fi
@ -77,7 +77,7 @@ YAML
if [[ -n "$home_volume" ]]; then
printf ' - %s:/home/node\n' "$home_volume" >>"$EXTRA_COMPOSE_FILE"
printf ' - %s:/home/node/.clawdbot\n' "$CLAWDBOT_CONFIG_DIR" >>"$EXTRA_COMPOSE_FILE"
printf ' - %s:/home/node/.moltbot\n' "$CLAWDBOT_CONFIG_DIR" >>"$EXTRA_COMPOSE_FILE"
printf ' - %s:/home/node/clawd\n' "$CLAWDBOT_WORKSPACE_DIR" >>"$EXTRA_COMPOSE_FILE"
fi

View File

@ -360,7 +360,13 @@ export async function autoMigrateLegacyStateDir(params: {
try {
fs.renameSync(legacyDir, targetDir);
} catch (err) {
} catch (err: any) {
if (err.code === "EBUSY") {
warnings.push(
`Legacy state dir (${legacyDir}) could not be renamed (EBUSY). This is common in Docker with bind mounts. Skipping automatic migration.`,
);
return { migrated: false, skipped: true, changes: [], warnings };
}
warnings.push(`Failed to move legacy state dir (${legacyDir}${targetDir}): ${String(err)}`);
return { migrated: false, skipped: false, changes, warnings };
}
@ -430,11 +436,11 @@ export async function detectLegacyStateMigrations(params: {
: { store: {}, ok: true };
const legacyKeys = targetSessionParsed.ok
? listLegacySessionKeys({
store: targetSessionParsed.store,
agentId: targetAgentId,
mainKey: targetMainKey,
scope: targetScope,
})
store: targetSessionParsed.store,
agentId: targetAgentId,
mainKey: targetMainKey,
scope: targetScope,
})
: [];
const legacyAgentDir = path.join(stateDir, "agent");