mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 04:18:05 +00:00
docs(docker): P3 — hoist two-command quickstart, restructure docker/README, add parity CI (#1260)
* docs: hoist Docker zero-install quickstart above npm install path - Create docs/quickstart-snippet.md as canonical source for the two-command flow (curl + docker compose up -d), wrapped in <!-- quickstart-snippet-start/end --> markers - Hoist the snippet into README.md immediately below the deprecation banner, above all other install paths - Rename old npm-only "## Quick Start" to "## Install on Host (npm)" and move it below the Docker quickstart * docs(docker): restructure README with zero-install first and migration section - Reorder top-level sections: zero-install (canonical snippet with markers), choosing an image, power-user ccs docker, prebuilt image, connect your app to CLIProxy, migration, env vars, troubleshooting - Add deprecation banner at the top pointing at the migration section - Add ## Migration from ccs-dashboard:latest section with step-by-step instructions covering compose down, data preservation, named volume vs bind-mount path, and compose up with the new image - Keep P1's Choosing an image table and P5's Connect Your App to CLIProxy section intact, just repositioned * test(docs): parity check for quickstart snippet across README files Assert README.md and docker/README.md both contain the canonical quickstart block verbatim, anchored by marker comments. Exits non-zero and prints a diff on any drift. * ci(docs): wire quickstart-parity test on push and PR Runs tests/docs/quickstart-parity.sh on self-hosted runner whenever docs/quickstart-snippet.md, README.md, docker/README.md, or the test/workflow files themselves change. Fails fast on snippet drift.
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
name: Docs – Quickstart Snippet Parity
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "docs/quickstart-snippet.md"
|
||||
- "README.md"
|
||||
- "docker/README.md"
|
||||
- "tests/docs/quickstart-parity.sh"
|
||||
- ".github/workflows/docs-parity.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "docs/quickstart-snippet.md"
|
||||
- "README.md"
|
||||
- "docker/README.md"
|
||||
- "tests/docs/quickstart-parity.sh"
|
||||
- ".github/workflows/docs-parity.yml"
|
||||
|
||||
jobs:
|
||||
quickstart-parity:
|
||||
name: Assert quickstart snippet matches in README.md and docker/README.md
|
||||
runs-on: [self-hosted, linux, x64, cliproxy]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run quickstart parity check
|
||||
run: bash tests/docs/quickstart-parity.sh
|
||||
@@ -22,21 +22,23 @@ Anthropic-compatible APIs without config thrash.
|
||||
|
||||
> **[Docker]** `ghcr.io/kaitranntt/ccs-dashboard:latest` is deprecated. Use `ghcr.io/kaitranntt/ccs:latest` instead. See [#1251](https://github.com/kaitranntt/ccs/issues/1251) and [docker/README.md](docker/README.md#choosing-an-image) for migration details. To wire a sibling container to CLIProxy, see [Connect your app to CLIProxy](docker/README.md#connect-your-app-to-cliproxy).
|
||||
|
||||
## Why CCS
|
||||
<!-- quickstart-snippet-start -->
|
||||
## Quick Start (Docker)
|
||||
|
||||
CCS gives you one stable command surface while letting you switch between:
|
||||
With Docker installed:
|
||||
|
||||
- multiple runtimes such as Claude Code, Factory Droid, and Codex CLI
|
||||
- multiple Claude subscriptions and isolated account contexts
|
||||
- OAuth providers like Codex, Kiro, Claude, Qwen, Kimi, and more, with legacy
|
||||
Copilot compatibility for existing setups
|
||||
- API and local-model profiles like GLM, Kimi, OpenRouter, Ollama, llama.cpp,
|
||||
Novita, and Alibaba Coding Plan
|
||||
```bash
|
||||
curl -fsSL https://ccs.kaitran.ca/docker-compose.yaml -o docker-compose.yaml
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
The goal is simple: stop rewriting config files, stop breaking active sessions,
|
||||
and move between providers in seconds.
|
||||
Dashboard at http://localhost:3000 · CLIProxy at http://localhost:8317.
|
||||
|
||||
## Quick Start
|
||||
Need a corporate-proxy alternative? Download directly:
|
||||
`https://github.com/kaitranntt/ccs/blob/main/docker/compose.yaml`
|
||||
<!-- quickstart-snippet-end -->
|
||||
|
||||
## Install on Host (npm)
|
||||
|
||||
```bash
|
||||
npm install -g @kaitranntt/ccs
|
||||
@@ -53,6 +55,20 @@ ccs glm
|
||||
ccs ollama
|
||||
```
|
||||
|
||||
## Why CCS
|
||||
|
||||
CCS gives you one stable command surface while letting you switch between:
|
||||
|
||||
- multiple runtimes such as Claude Code, Factory Droid, and Codex CLI
|
||||
- multiple Claude subscriptions and isolated account contexts
|
||||
- OAuth providers like Codex, Kiro, Claude, Qwen, Kimi, and more, with legacy
|
||||
Copilot compatibility for existing setups
|
||||
- API and local-model profiles like GLM, Kimi, OpenRouter, Ollama, llama.cpp,
|
||||
Novita, and Alibaba Coding Plan
|
||||
|
||||
The goal is simple: stop rewriting config files, stop breaking active sessions,
|
||||
and move between providers in seconds.
|
||||
|
||||
## OpenAI-Compatible Routing
|
||||
|
||||
CCS can now bridge Claude Code into OpenAI-compatible providers through a local
|
||||
|
||||
+202
-127
@@ -11,8 +11,29 @@ Persistent config, restart on reboot.
|
||||
|
||||
</div>
|
||||
|
||||
> **[Deprecation]** `ghcr.io/kaitranntt/ccs-dashboard:latest` is deprecated.
|
||||
> Migrate to `ghcr.io/kaitranntt/ccs:latest`. See [Migration](#migration-from-ccs-dashboardlatest) below.
|
||||
|
||||
<br>
|
||||
|
||||
<!-- quickstart-snippet-start -->
|
||||
## Quick Start (Docker)
|
||||
|
||||
With Docker installed:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://ccs.kaitran.ca/docker-compose.yaml -o docker-compose.yaml
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Dashboard at http://localhost:3000 · CLIProxy at http://localhost:8317.
|
||||
|
||||
Need a corporate-proxy alternative? Download directly:
|
||||
`https://github.com/kaitranntt/ccs/blob/main/docker/compose.yaml`
|
||||
<!-- quickstart-snippet-end -->
|
||||
|
||||
---
|
||||
|
||||
## Choosing an image
|
||||
|
||||
| Tag | Use | Approx. size | Status |
|
||||
@@ -23,113 +44,11 @@ Persistent config, restart on reboot.
|
||||
|
||||
Both `ccs:latest` and `ccs:full` also publish pinned version tags (`ccs:<major>.<minor>.<patch>`, `ccs:<major>.<minor>`, `ccs:<major>`) for reproducible deployments. The `:full` variants carry the `full-` prefix: `ccs:full-<ver>`, `ccs:full-<minor>`, etc.
|
||||
|
||||
## Connect Your App to CLIProxy
|
||||
|
||||
The CCS container joins a Docker network named `ccs-net`. This network name is a **stable, public contract** — it will not change without a SemVer-major release.
|
||||
|
||||
### Network Contract
|
||||
|
||||
| Resource | Stable name | Notes |
|
||||
|---|---|---|
|
||||
| Network | `ccs-net` | Attach any sibling container to this network |
|
||||
| Service DNS | `ccs` | Resolves to the CCS container from inside `ccs-net` |
|
||||
| CLIProxy port | `8317` | OAuth proxy — use as `OPENAI_BASE_URL` / `CLIPROXY_URL` |
|
||||
| Dashboard port | `3000` | Web UI |
|
||||
| Env-friendly URL | `http://ccs:8317` | Drop into your app's env without port-mapping on the host |
|
||||
|
||||
### Pattern A — Same Compose File
|
||||
|
||||
Declare `ccs-net` as external in your own compose file and add your service to it:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
my-app:
|
||||
image: my-app:latest
|
||||
environment:
|
||||
CLIPROXY_URL: http://ccs:8317
|
||||
networks:
|
||||
- ccs-net
|
||||
|
||||
networks:
|
||||
ccs-net:
|
||||
external: true
|
||||
```
|
||||
|
||||
Start CCS first so the network exists:
|
||||
|
||||
```bash
|
||||
docker compose -f docker/compose.yaml up -d # or: ccs docker up
|
||||
docker compose -f my-app/compose.yaml up -d
|
||||
```
|
||||
|
||||
### Pattern B — `docker run`
|
||||
|
||||
Attach a container at runtime without modifying any compose file:
|
||||
|
||||
```bash
|
||||
docker run --rm \
|
||||
--network ccs-net \
|
||||
-e CLIPROXY_URL=http://ccs:8317 \
|
||||
my-app:latest
|
||||
```
|
||||
|
||||
### Troubleshooting Network Issues
|
||||
|
||||
**Service not resolvable from sibling container**
|
||||
|
||||
Verify both containers are on `ccs-net`:
|
||||
|
||||
```bash
|
||||
docker network inspect ccs-net
|
||||
```
|
||||
|
||||
The output should list both `ccs` and your app container under `Containers`.
|
||||
|
||||
**Network not found**
|
||||
|
||||
The `ccs-net` network is created when the CCS stack starts. Run:
|
||||
|
||||
```bash
|
||||
docker compose -f docker/compose.yaml up -d
|
||||
# or: ccs docker up
|
||||
```
|
||||
|
||||
**Conflict with an existing `ccs-net`**
|
||||
|
||||
If you already have a network named `ccs-net` from unrelated tooling, either rename yours or scope
|
||||
the CCS project via `COMPOSE_PROJECT_NAME`:
|
||||
|
||||
```bash
|
||||
COMPOSE_PROJECT_NAME=myproject docker compose -f docker/compose.yaml up -d
|
||||
# Network becomes: myproject_ccs-net
|
||||
```
|
||||
|
||||
Note: scoping changes the network name, so sibling compose files must use the same project name.
|
||||
|
||||
**Podman / rootless containers**
|
||||
|
||||
On rootless Podman, network names and DNS resolution may behave differently. Verify your Podman
|
||||
version supports `--network` with named networks (`podman network ls`) and that `aardvark-dns` or
|
||||
equivalent is installed for container-name resolution.
|
||||
|
||||
**Low MTU on Hetzner and other cloud providers**
|
||||
|
||||
Some cloud environments set a low MTU (e.g., 1450) on their overlay networks. If you see packet
|
||||
fragmentation or stalled requests, add a custom MTU to the network in `compose.yaml`:
|
||||
|
||||
```yaml
|
||||
networks:
|
||||
ccs-net:
|
||||
name: ccs-net
|
||||
driver_opts:
|
||||
com.docker.network.driver.mtu: "1450"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Preferred: `ccs docker`
|
||||
## Power-user: `ccs docker`
|
||||
|
||||
The CLI now ships a first-class Docker command suite for the integrated CCS + CLIProxy stack:
|
||||
The CLI ships a first-class Docker command suite for the integrated CCS + CLIProxy stack:
|
||||
|
||||
```bash
|
||||
ccs docker up
|
||||
@@ -259,6 +178,8 @@ Expected healthy output:
|
||||
- CLIProxy health: `cliproxy-port: ok, CLIProxy running`
|
||||
- Client count matches number of auth token files
|
||||
|
||||
---
|
||||
|
||||
## Prebuilt Image Quick Start
|
||||
|
||||
Pull the recommended minimal image (CCS + CLIProxy, no AI CLIs):
|
||||
@@ -289,12 +210,7 @@ docker run -d \
|
||||
|
||||
Release-tag images are published as `ghcr.io/kaitranntt/ccs:<version>` (minimal) and `ghcr.io/kaitranntt/ccs:full-<version>` (full).
|
||||
|
||||
### Legacy image (deprecated)
|
||||
|
||||
The `ghcr.io/kaitranntt/ccs-dashboard:latest` image continues building for 2 more releases but
|
||||
emits a deprecation warning on startup. Migrate to `ccs:latest` at your earliest convenience.
|
||||
|
||||
## Prebuilt Image Build Locally
|
||||
### Build Locally
|
||||
|
||||
```bash
|
||||
docker build -f docker/Dockerfile -t ccs-dashboard:latest .
|
||||
@@ -312,6 +228,182 @@ Open `http://localhost:3000` (Dashboard).
|
||||
|
||||
CCS also starts CLIProxy on `http://localhost:8317` (used by Dashboard features and OAuth providers).
|
||||
|
||||
---
|
||||
|
||||
## Connect Your App to CLIProxy
|
||||
|
||||
The CCS container joins a Docker network named `ccs-net`. This network name is a **stable, public contract** — it will not change without a SemVer-major release.
|
||||
|
||||
### Network Contract
|
||||
|
||||
| Resource | Stable name | Notes |
|
||||
|---|---|---|
|
||||
| Network | `ccs-net` | Attach any sibling container to this network |
|
||||
| Service DNS | `ccs` | Resolves to the CCS container from inside `ccs-net` |
|
||||
| CLIProxy port | `8317` | OAuth proxy — use as `OPENAI_BASE_URL` / `CLIPROXY_URL` |
|
||||
| Dashboard port | `3000` | Web UI |
|
||||
| Env-friendly URL | `http://ccs:8317` | Drop into your app's env without port-mapping on the host |
|
||||
|
||||
### Pattern A — Same Compose File
|
||||
|
||||
Declare `ccs-net` as external in your own compose file and add your service to it:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
my-app:
|
||||
image: my-app:latest
|
||||
environment:
|
||||
CLIPROXY_URL: http://ccs:8317
|
||||
networks:
|
||||
- ccs-net
|
||||
|
||||
networks:
|
||||
ccs-net:
|
||||
external: true
|
||||
```
|
||||
|
||||
Start CCS first so the network exists:
|
||||
|
||||
```bash
|
||||
docker compose -f docker/compose.yaml up -d # or: ccs docker up
|
||||
docker compose -f my-app/compose.yaml up -d
|
||||
```
|
||||
|
||||
### Pattern B — `docker run`
|
||||
|
||||
Attach a container at runtime without modifying any compose file:
|
||||
|
||||
```bash
|
||||
docker run --rm \
|
||||
--network ccs-net \
|
||||
-e CLIPROXY_URL=http://ccs:8317 \
|
||||
my-app:latest
|
||||
```
|
||||
|
||||
### Troubleshooting Network Issues
|
||||
|
||||
**Service not resolvable from sibling container**
|
||||
|
||||
Verify both containers are on `ccs-net`:
|
||||
|
||||
```bash
|
||||
docker network inspect ccs-net
|
||||
```
|
||||
|
||||
The output should list both `ccs` and your app container under `Containers`.
|
||||
|
||||
**Network not found**
|
||||
|
||||
The `ccs-net` network is created when the CCS stack starts. Run:
|
||||
|
||||
```bash
|
||||
docker compose -f docker/compose.yaml up -d
|
||||
# or: ccs docker up
|
||||
```
|
||||
|
||||
**Conflict with an existing `ccs-net`**
|
||||
|
||||
If you already have a network named `ccs-net` from unrelated tooling, either rename yours or scope
|
||||
the CCS project via `COMPOSE_PROJECT_NAME`:
|
||||
|
||||
```bash
|
||||
COMPOSE_PROJECT_NAME=myproject docker compose -f docker/compose.yaml up -d
|
||||
# Network becomes: myproject_ccs-net
|
||||
```
|
||||
|
||||
Note: scoping changes the network name, so sibling compose files must use the same project name.
|
||||
|
||||
**Podman / rootless containers**
|
||||
|
||||
On rootless Podman, network names and DNS resolution may behave differently. Verify your Podman
|
||||
version supports `--network` with named networks (`podman network ls`) and that `aardvark-dns` or
|
||||
equivalent is installed for container-name resolution.
|
||||
|
||||
**Low MTU on Hetzner and other cloud providers**
|
||||
|
||||
Some cloud environments set a low MTU (e.g., 1450) on their overlay networks. If you see packet
|
||||
fragmentation or stalled requests, add a custom MTU to the network in `compose.yaml`:
|
||||
|
||||
```yaml
|
||||
networks:
|
||||
ccs-net:
|
||||
name: ccs-net
|
||||
driver_opts:
|
||||
com.docker.network.driver.mtu: "1450"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration from `ccs-dashboard:latest`
|
||||
|
||||
`ghcr.io/kaitranntt/ccs-dashboard:latest` is deprecated and will stop publishing after 2 more
|
||||
releases. Migrate to `ghcr.io/kaitranntt/ccs:latest` now.
|
||||
|
||||
### Steps
|
||||
|
||||
1. **Stop the old stack.**
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
# or if running via docker run:
|
||||
docker stop ccs-dashboard && docker rm ccs-dashboard
|
||||
```
|
||||
|
||||
2. **Preserve your data.**
|
||||
|
||||
Existing `~/.ccs` data on the host is not affected by the container change. If you were using
|
||||
a named volume (`ccs_home`), it persists automatically. If you were bind-mounting your host
|
||||
`~/.ccs`, continue doing so — just update the compose file path below.
|
||||
|
||||
3. **Get the new compose file.**
|
||||
|
||||
```bash
|
||||
curl -fsSL https://ccs.kaitran.ca/docker-compose.yaml -o docker-compose.yaml
|
||||
```
|
||||
|
||||
Or download manually from:
|
||||
`https://github.com/kaitranntt/ccs/blob/main/docker/compose.yaml`
|
||||
|
||||
4. **If you were bind-mounting `~/.ccs`** (instead of using a named volume), edit the downloaded
|
||||
`docker-compose.yaml` and replace the `ccs_home` named volume with your bind mount:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- ~/.ccs:/root/.ccs
|
||||
```
|
||||
|
||||
Otherwise the default named volume (`ccs_home`) works out of the box. Let compose create it
|
||||
automatically, or create it manually first:
|
||||
|
||||
```bash
|
||||
docker volume create ccs_home
|
||||
```
|
||||
|
||||
5. **Start the new stack.**
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Dashboard at http://localhost:3000 · CLIProxy at http://localhost:8317.
|
||||
|
||||
6. **Verify.**
|
||||
|
||||
```bash
|
||||
curl -fsS http://localhost:8317/
|
||||
```
|
||||
|
||||
### What changes
|
||||
|
||||
| Old | New |
|
||||
|---|---|
|
||||
| `ghcr.io/kaitranntt/ccs-dashboard:latest` | `ghcr.io/kaitranntt/ccs:latest` |
|
||||
| > 600 MB image | < 350 MB image |
|
||||
| Monolithic all-in-one | CCS + CLIProxy (AI CLIs optional via `:full`) |
|
||||
| No stable network contract | `ccs-net` network, `ccs` service DNS |
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Common CCS environment variables (from the docs):
|
||||
@@ -355,23 +447,6 @@ docker start ccs-dashboard
|
||||
docker rm -f ccs-dashboard
|
||||
```
|
||||
|
||||
## Prebuilt Image 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
|
||||
```
|
||||
|
||||
For the integrated CCS + CLIProxy stack managed by the CLI, use `ccs docker up` instead.
|
||||
|
||||
## Persistence
|
||||
|
||||
- CCS stores data in `/home/node/.ccs` inside the container.
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<!-- quickstart-snippet-start -->
|
||||
## Quick Start (Docker)
|
||||
|
||||
With Docker installed:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://ccs.kaitran.ca/docker-compose.yaml -o docker-compose.yaml
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Dashboard at http://localhost:3000 · CLIProxy at http://localhost:8317.
|
||||
|
||||
Need a corporate-proxy alternative? Download directly:
|
||||
`https://github.com/kaitranntt/ccs/blob/main/docker/compose.yaml`
|
||||
<!-- quickstart-snippet-end -->
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
# quickstart-parity.sh
|
||||
# Assert that README.md and docker/README.md both contain the canonical quickstart
|
||||
# snippet verbatim (anchored by marker comments).
|
||||
# Usage: bash tests/docs/quickstart-parity.sh (from repo root)
|
||||
set -euo pipefail
|
||||
|
||||
SNIPPET=$(awk '/<!-- quickstart-snippet-start -->/,/<!-- quickstart-snippet-end -->/' docs/quickstart-snippet.md)
|
||||
|
||||
fail=0
|
||||
for f in README.md docker/README.md; do
|
||||
file_block=$(awk '/<!-- quickstart-snippet-start -->/,/<!-- quickstart-snippet-end -->/' "$f")
|
||||
if ! diff -q <(printf '%s' "$SNIPPET") <(printf '%s' "$file_block") >/dev/null 2>&1; then
|
||||
echo "[X] $f quickstart snippet drift detected" >&2
|
||||
echo "--- canonical (docs/quickstart-snippet.md) ---" >&2
|
||||
printf '%s\n' "$SNIPPET" >&2
|
||||
echo "--- found in $f ---" >&2
|
||||
printf '%s\n' "$file_block" >&2
|
||||
fail=1
|
||||
else
|
||||
echo "[OK] $f snippet matches canonical"
|
||||
fi
|
||||
done
|
||||
|
||||
exit "$fail"
|
||||
Reference in New Issue
Block a user