From 727927aae0839f53ef18e136c2a5a1be1bd07965 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 27 Apr 2026 04:34:35 -0700 Subject: [PATCH] fix(docker): repair named-volume state directory ownership Preserve contributor credit and land the narrowed Docker ownership fix after ProjectClownfish review/follow-up. --- CHANGELOG.md | 1 + Dockerfile | 5 +++++ src/dockerfile.test.ts | 20 ++++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4621e45b14d..21cf043dea4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,7 @@ Docs: https://docs.openclaw.ai - Plugins/registry: cache repeated installed-index manifest registry fallback rebuilds behind a bounded invalidating cache so cold provider-discovery paths avoid rereading unchanged manifests. Thanks @mcaxtr. - Plugins/web: reuse manifest records already loaded for bundled web provider candidate discovery when falling back to public artifact provider loading. Thanks @shakkernerd. - Mattermost: keep direct-message replies top-level by suppressing reply roots for DM delivery while preserving channel and group thread roots, and derive inbound chat kind from the trusted channel lookup instead of the websocket event channel type. Carries forward #60115, #55186, #72305, and #72659; refs #59758, #59981, #59791, and #57565. Thanks @vincentkoc, @jwchmodx, and @hnykda. +- Docker: pre-create `/home/node/.openclaw` with node ownership and private permissions so first-run Docker Compose named volumes no longer fail startup with EACCES. (#48072, #63959; fixes #61279) Thanks @timoxue and @jeanibarz. - Process/Windows: decode command stdout and stderr from raw bytes with console-codepage awareness, while preserving valid UTF-8 output and multibyte characters split across chunks. Fixes #50519. Thanks @iready, @kevinten10, @zhangyongjie1997, @knightplat-blip, @heiqishi666, and @slepybear. - Bonjour/Windows: hide the bundled mDNS advertiser's Windows ARP shell probe so Gateway startup no longer flashes command-prompt windows. Fixes #70238. Thanks @alexandre-leng, @PratikRai0101, @infinitypacific, and @tomerpeled. - Agents/bootstrap: dedupe hook-injected bootstrap context files by workspace-relative path and store normalized resolved paths so duplicate relative and absolute hook paths no longer depend on the process cwd. (#59344; fixes #59319; related #56721, #56725, and #57587) Thanks @koen666. diff --git a/Dockerfile b/Dockerfile index 8e32e6f8cce..8c1b60a024d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -253,6 +253,11 @@ RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,shar RUN ln -sf /app/openclaw.mjs /usr/local/bin/openclaw \ && chmod 755 /app/openclaw.mjs +# Pre-create the default state dir so first-run Docker named volumes mounted +# here inherit node ownership instead of starting as root-owned state. +RUN install -d -m 0700 -o node -g node /home/node/.openclaw && \ + stat -c '%U:%G %a' /home/node/.openclaw | grep -qx 'node:node 700' + ENV NODE_ENV=production # Security hardening: Run as non-root user diff --git a/src/dockerfile.test.ts b/src/dockerfile.test.ts index f21cbbe6323..ad60aefbcad 100644 --- a/src/dockerfile.test.ts +++ b/src/dockerfile.test.ts @@ -117,4 +117,24 @@ describe("Dockerfile", () => { 'corepack prepare "$(node -p "require(\'./package.json\').packageManager")" --activate', ); }); + + it("pre-creates the OpenClaw home before switching to the node user", async () => { + const dockerfile = await readFile(dockerfilePath, "utf8"); + const runtimeStageIndex = dockerfile.lastIndexOf("FROM base-runtime"); + const stateDirIndex = dockerfile.indexOf( + "RUN install -d -m 0700 -o node -g node /home/node/.openclaw && \\", + runtimeStageIndex, + ); + const userIndex = dockerfile.indexOf("USER node", runtimeStageIndex); + + expect(runtimeStageIndex).toBeGreaterThan(-1); + expect(stateDirIndex).toBeGreaterThan(-1); + expect(userIndex).toBeGreaterThan(-1); + expect(stateDirIndex).toBeGreaterThan(runtimeStageIndex); + expect(stateDirIndex).toBeLessThan(userIndex); + expect(dockerfile).not.toContain("mkdir -p /home/node/.openclaw"); + expect(dockerfile).toContain( + "stat -c '%U:%G %a' /home/node/.openclaw | grep -qx 'node:node 700'", + ); + }); });