Harden Docker container security
Run as non-root user (fixes Claude Code refusing bypassPermissions as root, which caused all agent spawns to exit instantly with no session). Add read-only root filesystem, drop all capabilities, set no-new-privileges, bind port to localhost only, and require GIT_USER_NAME/GIT_USER_EMAIL env vars at startup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,10 +33,6 @@ RUN curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C /usr/local/bin
|
|||||||
# The CLI binary is `claude`.
|
# The CLI binary is `claude`.
|
||||||
RUN npm install -g @anthropic-ai/claude-code
|
RUN npm install -g @anthropic-ai/claude-code
|
||||||
|
|
||||||
# ── Biome (frontend linter) ─────────────────────────────────────────
|
|
||||||
# Installed project-locally via npm install, but having it global avoids
|
|
||||||
# needing node_modules for CI-style checks.
|
|
||||||
|
|
||||||
# ── Working directory ────────────────────────────────────────────────
|
# ── Working directory ────────────────────────────────────────────────
|
||||||
# /app holds the storkit source (copied in at build time for the binary).
|
# /app holds the storkit source (copied in at build time for the binary).
|
||||||
# /workspace is where the target project repo gets bind-mounted at runtime.
|
# /workspace is where the target project repo gets bind-mounted at runtime.
|
||||||
@@ -98,6 +94,22 @@ COPY --from=base /usr/local/bin/storkit /usr/local/bin/storkit
|
|||||||
# Alternative: mount the source as a volume.
|
# Alternative: mount the source as a volume.
|
||||||
COPY --from=base /app /app
|
COPY --from=base /app /app
|
||||||
|
|
||||||
|
# ── Non-root user ────────────────────────────────────────────────────
|
||||||
|
# Claude Code refuses --dangerously-skip-permissions (bypassPermissions)
|
||||||
|
# when running as root. Create a dedicated user so agents can launch.
|
||||||
|
RUN groupadd -r storkit \
|
||||||
|
&& useradd -r -g storkit -m -d /home/storkit storkit \
|
||||||
|
&& mkdir -p /home/storkit/.claude \
|
||||||
|
&& chown -R storkit:storkit /home/storkit \
|
||||||
|
&& chown -R storkit:storkit /usr/local/cargo /usr/local/rustup \
|
||||||
|
&& chown -R storkit:storkit /app
|
||||||
|
|
||||||
|
# ── Entrypoint ───────────────────────────────────────────────────────
|
||||||
|
# Validates required env vars (GIT_USER_NAME, GIT_USER_EMAIL) and
|
||||||
|
# configures git identity before starting the server.
|
||||||
|
COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
|
|
||||||
|
USER storkit
|
||||||
WORKDIR /workspace
|
WORKDIR /workspace
|
||||||
|
|
||||||
# ── Ports ────────────────────────────────────────────────────────────
|
# ── Ports ────────────────────────────────────────────────────────────
|
||||||
@@ -106,10 +118,8 @@ EXPOSE 3001
|
|||||||
|
|
||||||
# ── Volumes (defined in docker-compose.yml) ──────────────────────────
|
# ── Volumes (defined in docker-compose.yml) ──────────────────────────
|
||||||
# /workspace – bind mount: target project repo
|
# /workspace – bind mount: target project repo
|
||||||
# /root/.claude – named volume: Claude Code sessions/state
|
# /home/storkit/.claude – named volume: Claude Code sessions/state
|
||||||
# /usr/local/cargo/registry – named volume: cargo dependency cache
|
# /usr/local/cargo/registry – named volume: cargo dependency cache
|
||||||
|
|
||||||
# ── Entrypoint ───────────────────────────────────────────────────────
|
ENTRYPOINT ["entrypoint.sh"]
|
||||||
# Run storkit against the bind-mounted project at /workspace.
|
|
||||||
# The server picks up ANTHROPIC_API_KEY from the environment.
|
|
||||||
CMD ["storkit", "/workspace"]
|
CMD ["storkit", "/workspace"]
|
||||||
|
|||||||
@@ -16,11 +16,14 @@ services:
|
|||||||
dockerfile: docker/Dockerfile
|
dockerfile: docker/Dockerfile
|
||||||
container_name: storkit
|
container_name: storkit
|
||||||
ports:
|
ports:
|
||||||
# Web UI + MCP endpoint
|
# Bind to localhost only — not exposed on all interfaces.
|
||||||
- "3001:3001"
|
- "127.0.0.1:3001:3001"
|
||||||
environment:
|
environment:
|
||||||
# Required: Anthropic API key for Claude Code agents
|
# Required: Anthropic API key for Claude Code agents
|
||||||
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:?Set ANTHROPIC_API_KEY}
|
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:?Set ANTHROPIC_API_KEY}
|
||||||
|
# Required: git identity for agent commits
|
||||||
|
- GIT_USER_NAME=${GIT_USER_NAME:?Set GIT_USER_NAME}
|
||||||
|
- GIT_USER_EMAIL=${GIT_USER_EMAIL:?Set GIT_USER_EMAIL}
|
||||||
# Optional: override the server port (default 3001)
|
# Optional: override the server port (default 3001)
|
||||||
- STORKIT_PORT=3001
|
- STORKIT_PORT=3001
|
||||||
# Optional: Matrix bot credentials (if using Matrix integration)
|
# Optional: Matrix bot credentials (if using Matrix integration)
|
||||||
@@ -45,7 +48,7 @@ services:
|
|||||||
|
|
||||||
# Claude Code state – persists session history, projects config,
|
# Claude Code state – persists session history, projects config,
|
||||||
# and conversation transcripts so --resume works across restarts.
|
# and conversation transcripts so --resume works across restarts.
|
||||||
- claude-state:/root/.claude
|
- claude-state:/home/storkit/.claude
|
||||||
|
|
||||||
# Storkit source tree for rebuild_and_restart.
|
# Storkit source tree for rebuild_and_restart.
|
||||||
# The binary has CARGO_MANIFEST_DIR baked in at compile time
|
# The binary has CARGO_MANIFEST_DIR baked in at compile time
|
||||||
@@ -63,6 +66,23 @@ services:
|
|||||||
- workspace-target:/workspace/target
|
- workspace-target:/workspace/target
|
||||||
- storkit-target:/app/target
|
- storkit-target:/app/target
|
||||||
|
|
||||||
|
# ── Security hardening ──────────────────────────────────────────
|
||||||
|
# Read-only root filesystem. Only explicitly mounted volumes and
|
||||||
|
# tmpfs paths are writable.
|
||||||
|
read_only: true
|
||||||
|
tmpfs:
|
||||||
|
- /tmp:size=512M
|
||||||
|
- /home/storkit/.npm:size=256M
|
||||||
|
|
||||||
|
# Drop all Linux capabilities, then add back only what's needed.
|
||||||
|
cap_drop:
|
||||||
|
- ALL
|
||||||
|
|
||||||
|
# Prevent child processes from gaining new privileges via setuid,
|
||||||
|
# setgid, or other mechanisms.
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
|
||||||
# Resource limits – cap the whole system.
|
# Resource limits – cap the whole system.
|
||||||
# Adjust based on your machine. These are conservative defaults.
|
# Adjust based on your machine. These are conservative defaults.
|
||||||
deploy:
|
deploy:
|
||||||
|
|||||||
24
docker/entrypoint.sh
Executable file
24
docker/entrypoint.sh
Executable file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# ── Git identity ─────────────────────────────────────────────────────
|
||||||
|
# Agents commit code inside the container. Without a git identity,
|
||||||
|
# commits fail or use garbage defaults. Fail loudly at startup so the
|
||||||
|
# operator knows immediately.
|
||||||
|
if [ -z "$GIT_USER_NAME" ]; then
|
||||||
|
echo "FATAL: GIT_USER_NAME is not set. Export it in your environment or docker-compose.yml." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -z "$GIT_USER_EMAIL" ]; then
|
||||||
|
echo "FATAL: GIT_USER_EMAIL is not set. Export it in your environment or docker-compose.yml." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use GIT_AUTHOR/COMMITTER env vars instead of git config --global,
|
||||||
|
# so the root filesystem can stay read-only (no ~/.gitconfig write).
|
||||||
|
export GIT_AUTHOR_NAME="$GIT_USER_NAME"
|
||||||
|
export GIT_COMMITTER_NAME="$GIT_USER_NAME"
|
||||||
|
export GIT_AUTHOR_EMAIL="$GIT_USER_EMAIL"
|
||||||
|
export GIT_COMMITTER_EMAIL="$GIT_USER_EMAIL"
|
||||||
|
|
||||||
|
exec "$@"
|
||||||
Reference in New Issue
Block a user