refactor(claude): use built-in node user instead of custom claude user

Drop the addgroup/adduser layer entirely. node:20-alpine already ships
a node user at uid/gid 1000. Update chown and USER directives, and
update the claude-config volume mount path to /home/node/.claude.
This commit is contained in:
docker-claude 2026-04-14 22:50:59 +02:00
parent 0800e4a084
commit 1c489f8636
4 changed files with 12 additions and 16 deletions

View file

@ -10,8 +10,8 @@ This file provides context and guidance for working with this project.
Three containers managed by Docker Compose:
- **`claude`** — Claude Code CLI (`node:20-alpine`), non-root (UID 1000), isolated to an internal-only Docker network
- **`webui`** — Claude Code as a browser terminal via ttyd (`node:20-alpine`), non-root (UID 1000), same network isolation, basic auth required
- **`claude`** — Claude Code CLI (`node:20-alpine`), runs as the built-in `node` user (UID 1000), isolated to an internal-only Docker network
- **`webui`** — Claude Code as a browser terminal via ttyd (`node:20-alpine`), `node` user (UID 1000), same network isolation, basic auth required
- **`proxy`** — Squid forward proxy (`alpine:3.21`), `squid` user, bridges the internal network to the internet with an egress allowlist
Key Docker network property: `claude-internal` has `internal: true`, meaning Docker adds no default gateway. The `claude` and `webui` containers physically cannot reach the internet without going through the `proxy` container.

View file

@ -30,8 +30,8 @@ Runs [Claude Code](https://claude.ai/code) inside an isolated Docker environment
└──────────────────────────────────────────────────────────┘
```
- **`claude`** — Claude Code CLI (`node:20-alpine`), UID 1000, on `claude-internal` only
- **`webui`** — Claude Code in a browser terminal via ttyd (`node:20-alpine`), UID 1000, on `claude-internal` only, port 7681
- **`claude`** — Claude Code CLI (`node:20-alpine`), runs as the built-in `node` user (UID 1000), on `claude-internal` only
- **`webui`** — Claude Code in a browser terminal via ttyd (`node:20-alpine`), `node` user (UID 1000), on `claude-internal` only, port 7681
- **`proxy`** — Squid forward proxy (`alpine:3.21`), bridges `claude-internal` ↔ internet with egress allowlist
- **`claude-internal`** — `internal: true`; no default gateway, containers cannot reach the internet directly
- **`proxy-external`** — Standard bridge; proxy sidecar only
@ -161,7 +161,7 @@ Rebuild after changes:
| Control | claude / webui | proxy |
|---|---|---|
| Non-root user | UID 1000 (`claude`) | `squid` user |
| Non-root user | UID 1000 (`node`, built into base image) | `squid` user |
| `no-new-privileges` | yes | yes |
| All capabilities dropped | yes | yes |
| Direct internet access | no (`internal` network only) | allowlisted only |

View file

@ -11,21 +11,17 @@ RUN apk add --no-cache \
# Entrypoint used by the webui service (ttyd wrapping claude)
COPY --chmod=755 webui-entrypoint.sh /usr/local/bin/webui-entrypoint.sh
# Create non-root user (node:20-alpine reserves gid/uid 1000 for the node user)
RUN addgroup -g 1001 claude \
&& adduser -u 1001 -G claude -s /bin/bash -D claude
# Install Claude Code globally (runs as root for npm -g, then drops)
# Install Claude Code globally
RUN npm install -g @anthropic-ai/claude-code
# Workspace and Claude config dir — both owned by claude user.
# Workspace and Claude config dir — owned by the built-in node user (uid 1000).
# Pre-creating ~/.claude ensures the named volume is initialised with the
# correct ownership when first mounted (Docker copies image content into
# an empty named volume on first use).
RUN mkdir -p /workspace /home/claude/.claude \
&& chown -R claude:claude /workspace /home/claude/.claude
RUN mkdir -p /workspace /home/node/.claude \
&& chown -R node:node /workspace /home/node/.claude
USER claude
USER node
WORKDIR /workspace
# Proxy traffic through sidecar — override at runtime if needed

View file

@ -43,7 +43,7 @@ services:
# OAuth callback — required for browser-based login (claude login)
- "0.0.0.0:54545:54545"
volumes:
- claude-config:/home/claude/.claude
- claude-config:/home/node/.claude
# Workspace is injected by claude.sh via --volume flag at run time.
# Default: named Docker volume. Override: set WORKSPACE_DIR on the host.
security_opt:
@ -82,7 +82,7 @@ services:
# OAuth callback — required for browser-based login (claude login)
- "0.0.0.0:54545:54545"
volumes:
- claude-config:/home/claude/.claude
- claude-config:/home/node/.claude
- claude-web-workspace:/workspace
security_opt:
- no-new-privileges:true