From b38641002fadc8732c81aa9c7bd01bee826095a5 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Sun, 18 Jan 2026 08:10:44 -0500 Subject: [PATCH] fix(docker): address security and reproducibility issues Fixes from maintainer review: .dockerignore: - Add .env* files to prevent secret leakage (CRITICAL) - Add tests/, docs/, IDE files to reduce build context - Add organized comments for maintainability Dockerfile: - Pin bun version (ARG BUN_VERSION=1.2.2) for reproducible builds - Add build artifact validation step - Add npm cache clean to reduce image size - Add section comments for readability docker-compose.yml: - Add grok_home volume for grok-cli persistence - Add start_period to healthcheck for slow starts - Add resource limits (1G RAM, 2 CPUs) with reservations - Add documentation comments entrypoint.sh: - Improve chown error handling with warning message - Add usage help when no command provided README.md: - Add Resource Limits section with examples - Add Graceful Shutdown documentation - Add Troubleshooting section (permissions, ports, restart loops) - Add Security Notes section - Update persistence docs to include grok-cli Co-authored-by: opastorello --- .dockerignore | 30 ++++++++++++++ docker/Dockerfile | 21 +++++++++- docker/README.md | 84 ++++++++++++++++++++++++++++++++++++++- docker/docker-compose.yml | 15 +++++++ docker/entrypoint.sh | 15 ++++++- 5 files changed, 160 insertions(+), 5 deletions(-) diff --git a/.dockerignore b/.dockerignore index c388977b..0415d714 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,17 +1,47 @@ +# Git and GitHub .git .github .husky +# Dependencies node_modules ui/node_modules +# Build outputs (rebuilt in container) dist ui/dist ui/.vite +# Test and coverage coverage ui/coverage +tests +**/__tests__ +# Environment files (SECURITY: prevent secret leakage) +.env +.env.* +.env.local +.env.production +.env.development + +# Documentation (not needed for runtime) +docs +*.md +!README.md +!LICENSE + +# Logs and OS files *.log .DS_Store +Thumbs.db +# IDE and editor +.vscode +.idea +*.swp +*.swo + +# Misc +*.tgz +*.tar.gz diff --git a/docker/Dockerfile b/docker/Dockerfile index 8f09b6d6..b1f82c1d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,9 +1,14 @@ # syntax=docker/dockerfile:1 +# ============================================================================= +# Build stage: compile TypeScript and build UI +# ============================================================================= FROM node:20-bookworm-slim AS build SHELL ["/bin/bash", "-lc"] +# Pin bun version for reproducible builds +ARG BUN_VERSION=1.2.2 ENV BUN_INSTALL=/usr/local/bun ENV PATH="$BUN_INSTALL/bin:$PATH" @@ -11,7 +16,8 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends curl ca-certificates unzip \ && rm -rf /var/lib/apt/lists/* -RUN curl -fsSL https://bun.sh/install | bash +# Install specific bun version +RUN curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" WORKDIR /app @@ -26,11 +32,18 @@ COPY . . RUN bun run build:all +# Validate build artifacts exist +RUN test -d dist && test -d lib && echo "[OK] Build artifacts validated" +# ============================================================================= +# Runtime stage: minimal production image +# ============================================================================= FROM node:20-bookworm-slim AS runtime SHELL ["/bin/bash", "-lc"] +# Pin bun version for reproducible builds +ARG BUN_VERSION=1.2.2 ENV BUN_INSTALL=/usr/local/bun ENV PATH="$BUN_INSTALL/bin:/home/node/.opencode/bin:$PATH" @@ -38,7 +51,8 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends curl ca-certificates unzip \ && rm -rf /var/lib/apt/lists/* -RUN curl -fsSL https://bun.sh/install | bash +# Install specific bun version +RUN curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" WORKDIR /app @@ -55,8 +69,11 @@ COPY --from=build /app/scripts ./scripts COPY --from=build /app/README.md ./README.md COPY --from=build /app/LICENSE ./LICENSE +# Install AI CLI tools (using latest - pin versions in production if needed) +# These are optional tools for docker exec usage RUN npm install -g @google/gemini-cli @vibe-kit/grok-cli @anthropic-ai/claude-code \ && npm install -g @kaitranntt/ccs --force \ + && npm cache clean --force \ && su -s /bin/bash node -c 'curl -fsSL https://opencode.ai/install | bash -s -- --no-modify-path' \ && ln -sf /app/dist/ccs.js /usr/local/bin/ccs diff --git a/docker/README.md b/docker/README.md index 1a0f46cd..d0204282 100644 --- a/docker/README.md +++ b/docker/README.md @@ -93,7 +93,83 @@ docker-compose -f docker/docker-compose.yml down - CCS stores data in `/home/node/.ccs` inside the container. - The examples use a named volume (`ccs_home`) to persist that data. -- Compose also persists `/home/node/.claude` and `/home/node/.opencode` via named volumes. +- Compose also persists `/home/node/.claude`, `/home/node/.opencode`, and `/home/node/.grok-cli` via named volumes. + +## Resource Limits + +For production deployments, limit container resources: + +```bash +docker run -d \ + --name ccs-dashboard \ + --restart unless-stopped \ + --memory=1g \ + --cpus=2 \ + -p 3000:3000 \ + -p 8317:8317 \ + -v ccs_home:/home/node/.ccs \ + ccs-dashboard:latest +``` + +Docker Compose includes default limits (1GB RAM, 2 CPUs). Adjust in `docker-compose.yml` under `deploy.resources`. + +## Graceful Shutdown + +CCS handles `SIGTERM` gracefully. When stopping the container: + +```bash +docker stop ccs-dashboard # Sends SIGTERM, waits 10s, then SIGKILL +docker stop -t 30 ccs-dashboard # Wait 30s for graceful shutdown +``` + +The `init: true` in docker-compose.yml ensures proper signal forwarding. + +## Troubleshooting + +### Permission Errors (EACCES) + +If you see permission errors on startup: + +```bash +# Check volume permissions +docker exec ccs-dashboard ls -la /home/node/.ccs + +# Fix by recreating volumes +docker-compose down -v +docker-compose up -d +``` + +### Port Already in Use + +```bash +# Check what's using the port +lsof -i :3000 +lsof -i :8317 + +# Use different ports +docker run -p 4000:3000 -p 9317:8317 ... + +# Or with compose +CCS_DASHBOARD_PORT=4000 CCS_CLIPROXY_PORT=9317 docker-compose up -d +``` + +### Container Keeps Restarting + +```bash +# Check logs for errors +docker logs ccs-dashboard --tail 50 + +# Check container health +docker inspect ccs-dashboard --format='{{.State.Health.Status}}' +``` + +### Debug Mode + +Enable verbose logging: + +```bash +docker run -e CCS_DEBUG=1 ... +``` ## Examples: Claude + Gemini inside Docker @@ -121,3 +197,9 @@ If you need to configure credentials, do it according to each CLI's docs: docker exec -it ccs-dashboard claude --help docker exec -it ccs-dashboard gemini --help ``` + +## Security Notes + +- **Secrets**: For sensitive values like `CCS_PROXY_AUTH_TOKEN`, consider using Docker secrets or a `.env` file (not committed to git). +- **Network**: The container exposes ports 3000 and 8317. In production, use a reverse proxy (nginx, traefik) with TLS. +- **Updates**: Regularly rebuild the image to get security patches: `docker-compose build --pull` diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 12f6eee7..1cd63701 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,3 +1,5 @@ +# CCS Dashboard Docker Compose +# See docker/README.md for documentation services: ccs-dashboard: image: ccs-dashboard:latest @@ -26,13 +28,26 @@ services: - ccs_home:/home/node/.ccs - claude_home:/home/node/.claude - opencode_home:/home/node/.opencode + - grok_home:/home/node/.grok-cli + # Healthcheck uses internal ports (3000/8317) which are fixed healthcheck: test: ["CMD-SHELL", "curl -fsS --max-time 2 http://localhost:3000/ >/dev/null && curl -sS --max-time 2 http://127.0.0.1:8317/ >/dev/null"] interval: 10s timeout: 3s retries: 12 + start_period: 30s + # Resource limits (adjust based on workload) + deploy: + resources: + limits: + memory: 1G + cpus: '2' + reservations: + memory: 256M + cpus: '0.5' volumes: ccs_home: claude_home: opencode_home: + grok_home: diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index d1357da6..1cca373d 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -5,19 +5,30 @@ ccs_home_dir="${CCS_HOME_DIR:-/home/node/.ccs}" mkdir -p "$ccs_home_dir" +# Fix volume permissions if running as root if [ "$(id -u)" = "0" ]; then - chown -R node:node "$ccs_home_dir" || true + if ! chown -R node:node "$ccs_home_dir" 2>/dev/null; then + echo "[!] Warning: Could not change ownership of $ccs_home_dir (read-only volume?)" >&2 + fi fi +# Show usage if no command provided if [ "$#" -eq 0 ]; then echo "[X] No command provided" >&2 + echo "" >&2 + echo "Usage: docker run ccs-dashboard " >&2 + echo "" >&2 + echo "Examples:" >&2 + echo " docker run ccs-dashboard node dist/ccs.js config" >&2 + echo " docker run ccs-dashboard ccs --help" >&2 + echo "" >&2 exit 1 fi +# Drop privileges from root to node user if [ "$(id -u)" = "0" ]; then cmd="$(printf '%q ' "$@")" exec su -s /bin/bash node -c "exec ${cmd}" fi exec "$@" -