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:
Kai (Tam Nhu) Tran
2026-05-16 12:56:09 -04:00
committed by GitHub
parent 28f08cbb50
commit b50c2db3ce
5 changed files with 300 additions and 138 deletions
+31
View File
@@ -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
+27 -11
View File
@@ -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
View File
@@ -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.
+15
View File
@@ -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 -->
+25
View File
@@ -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"