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 "$@" +