mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 04:18:05 +00:00
docs(docker): document ccs-net contract for sibling containers (#1259)
Add "Connect your app to CLIProxy" section to docker/README.md with: - Public contract table (network=ccs-net, service DNS=ccs, ports 8317/3000) - Pattern A: same compose file with external network reference - Pattern B: docker run --network ccs-net - Troubleshooting subsection (DNS, missing network, conflict, Podman, MTU) Add one-line link in README.md pointing to the new section. Add CHANGELOG entry under Unreleased noting the contract as SemVer-major stable. Add CONTRIBUTING.md note that changing services.ccs or networks.ccs-net requires major bump.
This commit is contained in:
@@ -59,3 +59,18 @@ jobs:
|
||||
|
||||
echo "[OK] Both ports healthy"
|
||||
docker compose -f /tmp/ccs-compose.yaml down -v
|
||||
|
||||
- name: Verify ccs-net network contract (sibling DNS resolution)
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && inputs.do_up == true }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Checkout repo so tests/docker/network-contract.sh is available
|
||||
# The script needs to run from a directory that contains docker/compose.yaml.
|
||||
# We re-use the compose file from the repo rather than /tmp to avoid path drift.
|
||||
REPO_ROOT="$(mktemp -d)"
|
||||
git clone --depth=1 --no-tags \
|
||||
"https://github.com/kaitranntt/ccs.git" "$REPO_ROOT"
|
||||
|
||||
cd "$REPO_ROOT"
|
||||
bash tests/docker/network-contract.sh
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
* **docker:** Stable Docker network contract — external network `ccs-net` with service DNS `ccs` resolving to the CCS container. CLIProxy reachable at `http://ccs:8317`; dashboard at `http://ccs:3000`. Sibling containers can attach via `--network ccs-net` or by declaring `ccs-net` as an external network in their own compose file. Changing the network name or service name is a **SemVer-major breaking change**. See [docker/README.md](docker/README.md#connect-your-app-to-cliproxy) for usage patterns and troubleshooting. Verified by `tests/docker/network-contract.sh`.
|
||||
|
||||
### Deprecated
|
||||
|
||||
* **docker:** `ghcr.io/kaitranntt/ccs-dashboard:latest` Docker image is deprecated — migrate to `ghcr.io/kaitranntt/ccs:latest` (minimal, CCS + CLIProxy) or `ghcr.io/kaitranntt/ccs:full` (with claude-code, gemini-cli, grok-cli, opencode). The legacy image continues publishing for 2 more releases and emits a startup warning. See [#1251](https://github.com/kaitranntt/ccs/issues/1251).
|
||||
|
||||
@@ -217,6 +217,18 @@ If you cannot run the full suite, that is still fine for early or docs-only PRs.
|
||||
- Update `ui/src/` and any affected tests.
|
||||
- Run UI validation from `ui/`.
|
||||
|
||||
### If you change the Docker network or service name
|
||||
|
||||
The `ccs-net` network name and the `ccs` service name in `docker/compose.yaml` are a **public contract**. Users attach their own containers to `ccs-net` and resolve CLIProxy at `http://ccs:8317`.
|
||||
|
||||
Changing either of these values is a **SemVer-major breaking change**. Before modifying `services.ccs.name` or `networks.ccs-net.name` in `docker/compose.yaml`:
|
||||
|
||||
- Open an issue to discuss the migration path.
|
||||
- Update `docker/README.md`, `CHANGELOG.md`, and any docs that reference the stable names.
|
||||
- Bump the major version (via a `feat!:` or `fix!:` commit with a `BREAKING CHANGE:` footer).
|
||||
|
||||
If you are unsure whether your change affects the contract, check `tests/docker/network-contract.sh` — the test will fail if the network or service DNS resolution breaks.
|
||||
|
||||
### If you change config, providers, or architecture
|
||||
|
||||
- Update the relevant docs in `docs/`.
|
||||
|
||||
@@ -20,7 +20,7 @@ Anthropic-compatible APIs without config thrash.
|
||||
|
||||
</div>
|
||||
|
||||
> **[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.
|
||||
> **[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
|
||||
|
||||
|
||||
@@ -23,6 +23,110 @@ 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`
|
||||
|
||||
The CLI now ships a first-class Docker command suite for the integrated CCS + CLIProxy stack:
|
||||
|
||||
@@ -524,9 +524,8 @@ export async function preflightCheck(provider: CLIProxyProvider): Promise<Prefli
|
||||
return { proceed: true, accountId: defaultAccount?.id || '' };
|
||||
}
|
||||
|
||||
const { pauseAccountForQuotaCooldown, restoreExpiredQuotaPauses } = await import(
|
||||
'../accounts/account-safety'
|
||||
);
|
||||
const { pauseAccountForQuotaCooldown, restoreExpiredQuotaPauses } =
|
||||
await import('../accounts/account-safety');
|
||||
restoreExpiredQuotaPauses();
|
||||
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
|
||||
@@ -112,9 +112,8 @@ export async function runImageAnalysisCheck(results: HealthCheck): Promise<void>
|
||||
* Fix image analysis configuration issues
|
||||
*/
|
||||
export async function fixImageAnalysisConfig(): Promise<boolean> {
|
||||
const { updateConfig, loadOrCreateUnifiedConfig } = await import(
|
||||
'../../config/config-loader-facade'
|
||||
);
|
||||
const { updateConfig, loadOrCreateUnifiedConfig } =
|
||||
await import('../../config/config-loader-facade');
|
||||
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
let fixed = false;
|
||||
|
||||
Executable
+105
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env bash
|
||||
# tests/docker/network-contract.sh
|
||||
#
|
||||
# Verifies the stable ccs-net Docker network contract:
|
||||
# - Network name: ccs-net
|
||||
# - Service DNS: ccs
|
||||
# - CLIProxy: http://ccs:8317
|
||||
# - Dashboard: http://ccs:3000
|
||||
#
|
||||
# Requires: Docker with compose plugin, internet access to pull curlimages/curl
|
||||
# Usage: bash tests/docker/network-contract.sh
|
||||
# (called from repo root so docker/compose.yaml path resolves)
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
COMPOSE_FILE="docker/compose.yaml"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
log() { printf '[i] %s\n' "$*"; }
|
||||
ok() { printf '[OK] %s\n' "$*"; }
|
||||
err() { printf '[X] %s\n' "$*" >&2; }
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Bring stack up; register teardown on any exit
|
||||
# ---------------------------------------------------------------------------
|
||||
log "Bringing CCS stack up: $COMPOSE_FILE"
|
||||
docker compose -f "$COMPOSE_FILE" up -d
|
||||
|
||||
cleanup() {
|
||||
log "Tearing down stack..."
|
||||
docker compose -f "$COMPOSE_FILE" down -v 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Wait for healthcheck (max 90s)
|
||||
# ---------------------------------------------------------------------------
|
||||
log "Waiting for healthcheck to pass (max 90s)..."
|
||||
WAIT_MAX=45 # 45 x 2s = 90s
|
||||
HEALTHY=0
|
||||
for _i in $(seq 1 "$WAIT_MAX"); do
|
||||
STATUS=$(
|
||||
docker compose -f "$COMPOSE_FILE" ps --format json 2>/dev/null \
|
||||
| python3 -c "
|
||||
import sys, json
|
||||
data = sys.stdin.read().strip()
|
||||
if not data:
|
||||
print('unknown')
|
||||
raise SystemExit(0)
|
||||
rows = json.loads('[' + ','.join(data.splitlines()) + ']')
|
||||
for r in rows:
|
||||
if 'ccs' in r.get('Service', ''):
|
||||
print(r.get('Health', 'unknown'))
|
||||
raise SystemExit(0)
|
||||
print('unknown')
|
||||
" 2>/dev/null || echo "unknown"
|
||||
)
|
||||
if [ "$STATUS" = "healthy" ]; then
|
||||
HEALTHY=1
|
||||
break
|
||||
fi
|
||||
log "Health: $STATUS (attempt ${_i}/${WAIT_MAX})"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ "$HEALTHY" -ne 1 ]; then
|
||||
err "Container did not become healthy within 90s"
|
||||
exit 1
|
||||
fi
|
||||
ok "Container is healthy"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Verify ccs-net network exists on the host
|
||||
# ---------------------------------------------------------------------------
|
||||
log "Inspecting ccs-net network..."
|
||||
docker network inspect ccs-net >/dev/null
|
||||
ok "ccs-net network exists"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Verify DNS resolution from a sibling container on ccs-net
|
||||
# ---------------------------------------------------------------------------
|
||||
log "Testing http://ccs:8317 from sibling container..."
|
||||
docker run --rm \
|
||||
--network ccs-net \
|
||||
curlimages/curl:latest \
|
||||
-fsS --max-time 10 \
|
||||
http://ccs:8317/ \
|
||||
>/dev/null
|
||||
ok "CLIProxy reachable at http://ccs:8317"
|
||||
|
||||
log "Testing http://ccs:3000 from sibling container..."
|
||||
docker run --rm \
|
||||
--network ccs-net \
|
||||
curlimages/curl:latest \
|
||||
-fsS --max-time 10 \
|
||||
http://ccs:3000/ \
|
||||
>/dev/null
|
||||
ok "Dashboard reachable at http://ccs:3000"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Done
|
||||
# ---------------------------------------------------------------------------
|
||||
ok "network contract verified"
|
||||
Reference in New Issue
Block a user