Skip to content

Docker: fix fragile setup, adopt proven patterns from HolyClaude #9

@igouss

Description

@igouss

Context

Compared our Docker setup against HolyClaude, a battle-tested containerized Claude Code environment. Several of their patterns solve real bugs in our current setup.

Must Fix

1. Node version mismatch

Dockerfile uses node:24, Dockerfile.sandbox uses node:22-bookworm-slim. Standardize on node:24.

2. UID/GID remapping in sandbox entrypoint

The sandbox hardcodes USER gsd (UID 1000). Anyone whose host UID isn't 1000 gets permission errors on the mounted /workspace. Add an entrypoint script that remaps UID/GID via env vars (PUID/PGID), matching HolyClaude's approach.

3. Pre-create critical files before bind-mount

Docker creates missing bind-mount targets as directories. If a user mounts a config file path that doesn't exist yet, it becomes a directory and breaks things. The entrypoint should pre-create known file targets (e.g. config JSON) before they're needed.

4. Disconnected multi-stage Dockerfile

Stage 1 (builder) and Stage 2 (runtime) in the root Dockerfile share nothing — no COPY --from=builder. They're two independent images pretending to be a multi-stage build. Either split into two Dockerfiles or make stage 2 actually consume builder artifacts.

Pure Wins

5. Sentinel-based bootstrap (run first-boot setup once)

Add a sentinel file check in the entrypoint. First boot runs setup (copy default configs, init git identity, etc). Subsequent boots skip it, preserving user customizations.

SENTINEL="$HOME/.gsd/.bootstrapped"
if [ ! -f "$SENTINEL" ]; then
    /usr/local/bin/bootstrap.sh
    touch "$SENTINEL"
fi

6. Two compose files (minimal + full)

Split docker-compose.yml into:

  • docker-compose.yaml — zero-config, just works
  • docker-compose.full.yaml — every option documented inline

7. Proper entrypoint with signal handling

Replace bare ENTRYPOINT ["gsd"] with an entrypoint script that handles UID remapping, bootstrap, and execs into the main process with proper signal forwarding.

Out of Scope

  • s6-overlay (overkill unless we add sidecar services)
  • Multi-arch CI/CD (separate effort)
  • Notification hooks (nice-to-have, not a Docker fix)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions