# Story Kit – single-container deployment # # Usage: # # Set your API key and project path, then: # ANTHROPIC_API_KEY=sk-ant-... PROJECT_PATH=/path/to/your/repo \ # docker compose -f docker/docker-compose.yml up # # OrbStack users: just install OrbStack and use `docker compose` normally. # OrbStack's VirtioFS bind mount driver is significantly faster than # Docker Desktop's default (see spike findings). # # ── gVisor (runsc) host setup ──────────────────────────────────────────── # This compose file uses `runtime: runsc` (gVisor) for syscall-level # sandboxing. gVisor intercepts all container syscalls in userspace so # that even if a malicious workload escapes the container's process # namespace it cannot make raw syscalls to the host kernel. # # Prerequisites on the Docker host: # 1. Install gVisor: # curl -fsSL https://gvisor.dev/archive.key | sudo gpg --dearmor -o /usr/share/keyrings/gvisor-archive-keyring.gpg # echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/gvisor-archive-keyring.gpg] https://storage.googleapis.com/gvisor/releases release main" | sudo tee /etc/apt/sources.list.d/gvisor.list # sudo apt-get update && sudo apt-get install -y runsc # 2. Register runsc with Docker (/etc/docker/daemon.json): # { # "runtimes": { # "runsc": { "path": "/usr/bin/runsc" } # } # } # 3. Restart Docker: sudo systemctl restart docker # 4. Verify: docker run --runtime=runsc hello-world # # Note: On macOS (OrbStack / Docker Desktop) gVisor is Linux-only and # not supported. Remove `runtime: runsc` for local development on macOS. services: storkit: build: context: .. dockerfile: docker/Dockerfile # Run under gVisor for syscall-level sandboxing. # Requires runsc installed and registered in /etc/docker/daemon.json. # See host setup instructions in the header comment above. runtime: runsc container_name: storkit ports: # Bind to localhost only — not exposed on all interfaces. - "127.0.0.1:3001:3001" environment: # Optional: Anthropic API key. If unset, Claude Code falls back to # OAuth credentials from `claude login` (e.g. Max subscription). - ANTHROPIC_API_KEY=${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) - STORKIT_PORT=3001 # Optional: Matrix bot credentials (if using Matrix integration) - MATRIX_HOMESERVER=${MATRIX_HOMESERVER:-} - MATRIX_USER=${MATRIX_USER:-} - MATRIX_PASSWORD=${MATRIX_PASSWORD:-} # Optional: Slack webhook (if using Slack integration) - SLACK_BOT_TOKEN=${SLACK_BOT_TOKEN:-} - SLACK_APP_TOKEN=${SLACK_APP_TOKEN:-} volumes: # The target project repo – bind-mounted from host. # Changes made by agents inside the container are immediately # visible on the host (and vice versa). - ${PROJECT_PATH:?Set PROJECT_PATH}:/workspace # Cargo registry cache – persists downloaded crates across # container restarts so `cargo build` doesn't re-download. - cargo-registry:/usr/local/cargo/registry # Cargo git checkouts – persists git-based dependencies. - cargo-git:/usr/local/cargo/git # Claude Code state – persists session history, projects config, # and conversation transcripts so --resume works across restarts. - claude-state:/home/storkit/.claude # Storkit source tree for rebuild_and_restart. # The binary has CARGO_MANIFEST_DIR baked in at compile time # pointing to /app/server, so the source must be at /app. # This is COPY'd in the Dockerfile; mounting over it allows # live source updates without rebuilding the image. # Mount host source so rebuild_and_restart picks up live changes: - ./..:/app # Keep cargo build artifacts off the bind mount. # Bind-mount directory traversal is ~23x slower than Docker volumes # (confirmed in spike 329). Cargo stat-checks every file in target/ # on incremental builds — leaving it on the bind mount makes builds # catastrophically slow (~12s just to traverse the tree). - workspace-target:/workspace/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,exec - /home/storkit:size=512M,uid=999,gid=999,exec # Drop all Linux capabilities, then add back only what's needed. # SETUID/SETGID needed by Claude Code's PTY allocation (openpty). cap_drop: - ALL cap_add: - SETUID - SETGID # 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. # Adjust based on your machine. These are conservative defaults. deploy: resources: limits: cpus: "8" memory: 24G reservations: cpus: "2" memory: 4G # Health check – verify the MCP endpoint responds healthcheck: test: ["CMD", "curl", "-sf", "http://localhost:3001/health"] interval: 30s timeout: 5s retries: 3 start_period: 10s # Restart policy – restart on crash but not on manual stop restart: unless-stopped volumes: cargo-registry: cargo-git: claude-state: workspace-target: storkit-target: