From a14c7f3f6ba0d694dda622a59c9f878f648976b4 Mon Sep 17 00:00:00 2001 From: opastorello Date: Sat, 17 Jan 2026 00:19:53 -0300 Subject: [PATCH 1/3] feat(docker): add Docker/Compose setup for CCS dashboard --- .dockerignore | 17 ++++++ README.md | 2 + docker/Dockerfile | 68 +++++++++++++++++++++ docker/README.md | 123 ++++++++++++++++++++++++++++++++++++++ docker/docker-compose.yml | 38 ++++++++++++ docker/entrypoint.sh | 23 +++++++ 6 files changed, 271 insertions(+) create mode 100644 .dockerignore create mode 100644 docker/Dockerfile create mode 100644 docker/README.md create mode 100644 docker/docker-compose.yml create mode 100644 docker/entrypoint.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..c388977b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +.git +.github +.husky + +node_modules +ui/node_modules + +dist +ui/dist +ui/.vite + +coverage +ui/coverage + +*.log +.DS_Store + diff --git a/README.md b/README.md index 92adff4a..a0382846 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,8 @@ ccs config # Opens http://localhost:3000 ``` +Want to run the dashboard in Docker? See `docker/README.md`. + ### 3. Configure Your Accounts The dashboard provides visual management for all account types: diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..8f09b6d6 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,68 @@ +# syntax=docker/dockerfile:1 + +FROM node:20-bookworm-slim AS build + +SHELL ["/bin/bash", "-lc"] + +ENV BUN_INSTALL=/usr/local/bun +ENV PATH="$BUN_INSTALL/bin:$PATH" + +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 + +WORKDIR /app + +# Dependency install layer (avoid running install scripts inside the image build) +COPY package.json bun.lock bunfig.toml ./ +COPY ui/package.json ui/bun.lock ./ui/ + +RUN bun install --frozen-lockfile --ignore-scripts \ + && (cd ui && bun install --frozen-lockfile --ignore-scripts) + +COPY . . + +RUN bun run build:all + + +FROM node:20-bookworm-slim AS runtime + +SHELL ["/bin/bash", "-lc"] + +ENV BUN_INSTALL=/usr/local/bun +ENV PATH="$BUN_INSTALL/bin:/home/node/.opencode/bin:$PATH" + +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 + +WORKDIR /app + +COPY package.json bun.lock ./ +RUN bun install --frozen-lockfile --production --ignore-scripts + +COPY docker/entrypoint.sh /usr/local/bin/ccs-entrypoint +RUN chmod +x /usr/local/bin/ccs-entrypoint + +COPY --from=build /app/dist ./dist +COPY --from=build /app/lib ./lib +COPY --from=build /app/config ./config +COPY --from=build /app/scripts ./scripts +COPY --from=build /app/README.md ./README.md +COPY --from=build /app/LICENSE ./LICENSE + +RUN npm install -g @google/gemini-cli @vibe-kit/grok-cli @anthropic-ai/claude-code \ + && npm install -g @kaitranntt/ccs --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 + +ENV CCS_PORT=3000 +EXPOSE 3000 8317 + +ENTRYPOINT ["/usr/local/bin/ccs-entrypoint"] + +CMD ["bash", "-c", "node dist/ccs.js config --port ${CCS_PORT}"] diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..1a0f46cd --- /dev/null +++ b/docker/README.md @@ -0,0 +1,123 @@ +
+ +# CCS Dashboard - Docker + +![CCS Logo](../assets/ccs-logo-medium.png) + +### Run the CCS Config Dashboard in Docker. +Persistent config, restart on reboot. + +**[Back to README](../README.md)** + +
+ +
+ +## Quick Start (Docker Run) + +```bash +docker build -f docker/Dockerfile -t ccs-dashboard:latest . +docker run -d \ + --name ccs-dashboard \ + --restart unless-stopped \ + -p 3000:3000 \ + -p 8317:8317 \ + -e CCS_PORT=3000 \ + -v ccs_home:/home/node/.ccs \ + ccs-dashboard:latest +``` + +Open `http://localhost:3000` (Dashboard). + +CCS also starts CLIProxy on `http://localhost:8317` (used by Dashboard features and OAuth providers). + +## Environment Variables + +Common CCS environment variables (from the docs): + +- Docs: [Environment variables](https://docs.ccs.kaitran.ca/getting-started/configuration#environment-variables) + +- `CCS_CONFIG`: override config file path +- `CCS_UNIFIED_CONFIG=1`: force unified YAML config loader +- `CCS_MIGRATE=1`: trigger config migration +- `CCS_SKIP_MIGRATION=1`: skip migrations +- `CCS_DEBUG=1`: enable verbose logs +- `NO_COLOR=1`: disable ANSI colors +- `CCS_SKIP_PREFLIGHT=1`: skip API key validation checks +- `CCS_WEBSEARCH_SKIP=1`: skip WebSearch hook integration +- Proxy: `CCS_PROXY_HOST`, `CCS_PROXY_PORT`, `CCS_PROXY_PROTOCOL`, `CCS_PROXY_AUTH_TOKEN`, `CCS_PROXY_TIMEOUT`, `CCS_PROXY_FALLBACK_ENABLED`, `CCS_ALLOW_SELF_SIGNED` + +Example (passing env vars to the running container): + +```bash +docker run -d \ + --name ccs-dashboard \ + --restart unless-stopped \ + -p 3000:3000 \ + -p 8317:8317 \ + -e CCS_PORT=3000 \ + -e CCS_DEBUG=1 \ + -e NO_COLOR=1 \ + -e CCS_PROXY_HOST="proxy.example.com" \ + -e CCS_PROXY_PORT=443 \ + -e CCS_PROXY_PROTOCOL="https" \ + -v ccs_home:/home/node/.ccs \ + ccs-dashboard:latest +``` + +## Useful Commands + +```bash +docker logs -f ccs-dashboard +docker stop ccs-dashboard +docker start ccs-dashboard +docker rm -f ccs-dashboard +``` + +## Docker Compose (Optional) + +Using the included `docker/docker-compose.yml`: + +```bash +docker-compose -f docker/docker-compose.yml up --build -d +docker-compose -f docker/docker-compose.yml logs -f +``` + +Stop: + +```bash +docker-compose -f docker/docker-compose.yml down +``` + +## Persistence + +- 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. + +## Examples: Claude + Gemini inside Docker + +Open a shell inside the running container: + +```bash +docker exec -it ccs-dashboard bash +``` + +Claude (non-interactive / print mode): + +```bash +docker exec -it ccs-dashboard claude -p "Hello from Docker" +``` + +Gemini (one-shot prompt): + +```bash +docker exec -it ccs-dashboard gemini "Hello from Docker" +``` + +If you need to configure credentials, do it according to each CLI's docs: + +```bash +docker exec -it ccs-dashboard claude --help +docker exec -it ccs-dashboard gemini --help +``` diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 00000000..12f6eee7 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,38 @@ +services: + ccs-dashboard: + image: ccs-dashboard:latest + build: + context: .. + dockerfile: docker/Dockerfile + restart: unless-stopped + init: true + ports: + - "${CCS_DASHBOARD_PORT:-3000}:3000" + - "${CCS_CLIPROXY_PORT:-8317}:8317" + environment: + CCS_PORT: 3000 + CCS_DEBUG: "${CCS_DEBUG:-}" + NO_COLOR: "${NO_COLOR:-}" + CCS_SKIP_PREFLIGHT: "${CCS_SKIP_PREFLIGHT:-}" + CCS_WEBSEARCH_SKIP: "${CCS_WEBSEARCH_SKIP:-}" + CCS_PROXY_HOST: "${CCS_PROXY_HOST:-}" + CCS_PROXY_PORT: "${CCS_PROXY_PORT:-}" + CCS_PROXY_PROTOCOL: "${CCS_PROXY_PROTOCOL:-}" + CCS_PROXY_AUTH_TOKEN: "${CCS_PROXY_AUTH_TOKEN:-}" + CCS_PROXY_TIMEOUT: "${CCS_PROXY_TIMEOUT:-}" + CCS_PROXY_FALLBACK_ENABLED: "${CCS_PROXY_FALLBACK_ENABLED:-}" + CCS_ALLOW_SELF_SIGNED: "${CCS_ALLOW_SELF_SIGNED:-}" + volumes: + - ccs_home:/home/node/.ccs + - claude_home:/home/node/.claude + - opencode_home:/home/node/.opencode + 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 + +volumes: + ccs_home: + claude_home: + opencode_home: diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 00000000..d1357da6 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +ccs_home_dir="${CCS_HOME_DIR:-/home/node/.ccs}" + +mkdir -p "$ccs_home_dir" + +if [ "$(id -u)" = "0" ]; then + chown -R node:node "$ccs_home_dir" || true +fi + +if [ "$#" -eq 0 ]; then + echo "[X] No command provided" >&2 + exit 1 +fi + +if [ "$(id -u)" = "0" ]; then + cmd="$(printf '%q ' "$@")" + exec su -s /bin/bash node -c "exec ${cmd}" +fi + +exec "$@" + From b38641002fadc8732c81aa9c7bd01bee826095a5 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Sun, 18 Jan 2026 08:10:44 -0500 Subject: [PATCH 2/3] 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 "$@" - From 1dee71897e89cc20bc1e78a57e29176ddacdb321 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Sun, 18 Jan 2026 08:11:54 -0500 Subject: [PATCH 3/3] fix(docker): use bun 1.2.21 --- docker/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index b1f82c1d..09b0be39 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -8,7 +8,7 @@ FROM node:20-bookworm-slim AS build SHELL ["/bin/bash", "-lc"] # Pin bun version for reproducible builds -ARG BUN_VERSION=1.2.2 +ARG BUN_VERSION=1.2.21 ENV BUN_INSTALL=/usr/local/bun ENV PATH="$BUN_INSTALL/bin:$PATH" @@ -43,7 +43,7 @@ FROM node:20-bookworm-slim AS runtime SHELL ["/bin/bash", "-lc"] # Pin bun version for reproducible builds -ARG BUN_VERSION=1.2.2 +ARG BUN_VERSION=1.2.21 ENV BUN_INSTALL=/usr/local/bun ENV PATH="$BUN_INSTALL/bin:/home/node/.opencode/bin:$PATH"