Containers: Dockerfile must pre-create /state and config dirs with claude ownership #29

Closed
opened 2026-04-17 21:42:40 +00:00 by claude-desktop · 0 comments
Collaborator

User story

As an operator bringing up a fresh agent container, I want the
Dockerfile to ship mount points (/state, /home/claude/.config,
/home/claude/.config/claude-code) that are already owned by the
non-root claude user, so docker run -v <named-volume>:/state …
produces a writable volume on first mount and the credentials bind
target's parent is traversable.

Follow-up to #27. When piloting container mode on reviewer, the very
first git clone inside the container failed with:

fatal: could not create leading directories of
'/state/repos/charles/claude-hooks': Permission denied

Root cause: Docker creates named volumes with root:root 0755 unless
the image's declared mount point already has different ownership.
/state wasn't declared in the image at all, so the volume came up
owned by root and unwritable by uid 1000 (the claude user). The
credentials bind target /home/claude/.config/claude-code/ had the
same problem — Docker auto-created the intermediate directories owned
by root.

Fixed locally to keep the pilot moving; this issue lands the same
change on main so future image builds carry it.

Acceptance criteria

Dockerfile

  • Before USER claude, pre-create /state,
    /home/claude/.config, and /home/claude/.config/claude-code
    owned by claude:claude with mode 0755. install -d -o claude -g claude -m 0755 … is the clean one-liner (four invocations in
    one RUN, one per path).
  • Image size stays under the 500 MB target (expected impact: a few
    hundred bytes).

Tests

  • .forgejo/workflows/qa.yml (the daemonless dockerfile job)
    gains a static check: grep for the install -d -o claude
    block. Fails the build if missing or regressed.
  • No runtime check is added to PR CI (still daemonless); the
    matching runtime assertion belongs in #26.

Docs

  • Brief comment in the Dockerfile next to the new RUN explaining
    why (volume/bind ownership on first mount).

Out of scope

  • Any change to justfile or src/container.ts — both already agree
    on the correct paths (fixed in #27).
  • Release-runner runtime smoke tests that would catch this at
    publish time (tracked in #26).

References

  • Parent tracking issue: #17
  • Integration follow-up: #27 (the two earlier bugs that made the
    pilot possible at all)
  • Related: #26 (release runner + runtime smoke tests)

Dependencies

  • Blocked by: none open
  • Blocks: first fresh docker buildx that a release or new operator
    pulls — without this the image still ships broken volume ownership
  • Branch off: main
  • Full graph: #17
## User story As an **operator** bringing up a fresh agent container, I want the Dockerfile to ship mount points (`/state`, `/home/claude/.config`, `/home/claude/.config/claude-code`) that are already owned by the non-root `claude` user, so `docker run -v <named-volume>:/state …` produces a writable volume on first mount and the credentials bind target's parent is traversable. Follow-up to #27. When piloting container mode on `reviewer`, the very first `git clone` inside the container failed with: fatal: could not create leading directories of '/state/repos/charles/claude-hooks': Permission denied Root cause: Docker creates named volumes with `root:root 0755` unless the image's declared mount point already has different ownership. `/state` wasn't declared in the image at all, so the volume came up owned by root and unwritable by `uid 1000` (the `claude` user). The credentials bind target `/home/claude/.config/claude-code/` had the same problem — Docker auto-created the intermediate directories owned by root. Fixed locally to keep the pilot moving; this issue lands the same change on `main` so future image builds carry it. ## Acceptance criteria ### Dockerfile - [ ] Before `USER claude`, pre-create `/state`, `/home/claude/.config`, and `/home/claude/.config/claude-code` owned by `claude:claude` with mode 0755. `install -d -o claude -g claude -m 0755 …` is the clean one-liner (four invocations in one `RUN`, one per path). - [ ] Image size stays under the 500 MB target (expected impact: a few hundred bytes). ### Tests - [ ] `.forgejo/workflows/qa.yml` (the daemonless `dockerfile` job) gains a static check: `grep` for the `install -d -o claude` block. Fails the build if missing or regressed. - [ ] No runtime check is added to PR CI (still daemonless); the matching runtime assertion belongs in #26. ### Docs - [ ] Brief comment in the Dockerfile next to the new `RUN` explaining why (volume/bind ownership on first mount). ## Out of scope - Any change to `justfile` or `src/container.ts` — both already agree on the correct paths (fixed in #27). - Release-runner runtime smoke tests that would catch this at publish time (tracked in #26). ## References - Parent tracking issue: #17 - Integration follow-up: #27 (the two earlier bugs that made the pilot possible at all) - Related: #26 (release runner + runtime smoke tests) ## Dependencies - **Blocked by:** none open - **Blocks:** first fresh `docker buildx` that a release or new operator pulls — without this the image still ships broken volume ownership - **Branch off:** `main` - **Full graph:** #17
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks#29
No description provided.