Docker Compose is the supported installation. Use Docker Engine + Compose on
Linux or Docker Desktop on macOS / Windows (Linux containers).
The qoder-data volume stores SQLite and account credentials. Source runs are
for development and do not support managed updates.
From the repository root, use:
macOS / Linux:
./scripts/start.shWindows PowerShell:
powershell -ExecutionPolicy Bypass -File .\scripts\start.ps1Both launchers create deploy/.env if needed, pull the published image, fall
back to a local build when necessary, and wait for /health.
To run Compose directly, create deploy/.env first (an empty file is enough
for defaults; do not overwrite an existing file):
docker compose --env-file deploy/.env -f deploy/docker-compose.yml up -dAdd --build to build from the checked-out source. Only 127.0.0.1:3010 is
published.
Save the administrator key printed once in the first-start logs:
docker compose --env-file deploy/.env -f deploy/docker-compose.yml logs qoder-api-proxyOpen http://127.0.0.1:3010, sign in with that key, and add an account.
Create a separate client key in API keys for applications; it only grants
access to /v1/* and can be restricted by provider and region.
Use Accounts to add an account with its supported login method:
| Provider | Region | Login / import |
|---|---|---|
| Qoder | Global / CN | Browser OAuth, PAT, qoder-native-v1 |
| WorkBuddy | Global / CN | Browser OAuth, workbuddy-oauth-v1 |
| Trae Work | CN | Browser OAuth, trae-oauth-v1 |
| Devin (experimental) | Global | Browser OAuth, devin-session-v1 |
Qoder CN, WorkBuddy, and Trae still need live-account acceptance; Devin is not claimed production-ready. Qoder uses one isolated Node worker per account. Other providers use Go in-process adapters. All durable credentials stay in SQLite.
WorkBuddy daily check-in and token keepalive are per-account opt-in and disabled by default. Enable them only if you want those automatic account operations.
Use http://127.0.0.1:3010/v1 on the host. For another container on the same
Docker network:
base_url = http://qoder-api-proxy:3010/v1
api_key = <client key created in the console>
Get a model ID from Access or GET /v1/models, then test a request:
export CLI2API_API_KEY='paste-your-client-key'
curl http://127.0.0.1:3010/v1/chat/completions \
-H "Authorization: Bearer $CLI2API_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "<model-id-from-v1-models>",
"messages": [{"role": "user", "content": "Reply with OK only"}],
"stream": false
}'PowerShell equivalent:
$env:CLI2API_API_KEY = "paste-your-client-key"
$Headers = @{ Authorization = "Bearer $env:CLI2API_API_KEY" }
$Body = @{
model = "<model-id-from-v1-models>"
messages = @(@{ role = "user"; content = "Reply with OK only" })
stream = $false
} | ConvertTo-Json -Depth 4
Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:3010/v1/chat/completions" -Headers $Headers -ContentType "application/json" -Body $BodyPin a request to a specific account with the X-Qoder-Account: acc_... header
(a historical header name that applies to every provider). Consecutive turns of
the same conversation prefer the same account from the first user message
(including image-only turns);
X-CLI2API-Session remains an optional override.
Account pinning is a routing preference, not an authorization boundary: a missing
account can fall back to the pool, and cooling pins may escape within the same
provider and region. Use client-key grants to enforce access limits.
Cross-provider routing for shared model IDs is enabled by default. Use a
provider-prefixed ID such as qoder/<model-id> to restrict the provider.
When cross-provider routing is disabled, a provider prefix is required.
Key grants, region constraints, model support, and account readiness still apply.
Most settings are in the console: System for the global proxy, Accounts
for per-account proxy and concurrency, and API keys for client access.
Global and Qoder proxies accept HTTP(S) only; WorkBuddy / Trae / Devin account
proxies also accept SOCKS5. Use direct or none to bypass proxy inheritance.
The administrator key is generated once and stored in SQLite. Rotate it in the console; environment variables cannot replace it.
Advanced environment variables and diagnostics
| Variable | Default | Purpose |
|---|---|---|
QODER_DATA_DIR |
/data |
SQLite database and durable account credentials |
QODER_RUNTIME_DIR |
/run/cli2api |
Ephemeral per-account runtime homes for providers that use child processes |
QODER_MAX_RETRY_ACCOUNTS |
4 |
Maximum accounts attempted for one request (1-64) |
QODER_SSE_DIAGNOSTIC_MODELS |
empty | Comma-separated Qoder model IDs for redacted SSE diagnostics in Runtime Logs; * enables all |
QODER_WORKER_BASE_PORT |
32100 |
Internal child-runtime port range |
QODER_PROXY_URL |
empty | Initial global outbound proxy: http(s)://, direct, or none; saved console settings take precedence |
QODERCLI_JS |
image default | Pinned Qoder Global CLI bundle |
QODERCNCLI_JS |
image default | Pinned Qoder CN CLI bundle |
UPDATE_GITHUB_TOKEN |
empty | Optional GitHub token for release checks |
UPDATE_AGENT_URL |
empty | Docker Desktop host updater URL, written by the installer |
UPDATE_AGENT_TOKEN |
empty | Docker Desktop updater token, written by the installer |
CLI2API_UPDATER_SOCKET_DIR |
platform-specific | Host directory mounted read-only for the Linux updater socket |
These are process settings; Compose passes only variables declared in its
environment section. For variables not listed there, add them through a local
deploy/docker-compose.override.yml and include that file with -f when running
Compose directly. An entry in deploy/.env alone is not enough.
For Qoder stream diagnostics, set QODER_SSE_DIAGNOSTIC_MODELS to a model ID
from /v1/models and recreate the container. Diagnostics record event metadata,
not prompts, responses, tool arguments, or credentials.
| Method | Path | Purpose |
|---|---|---|
GET |
/health |
Health probe; no API key required |
GET |
/v1/models |
Model catalog |
POST |
/v1/chat/completions |
OpenAI-compatible chat |
POST |
/v1/messages |
Anthropic-compatible messages |
POST |
/v1/responses |
OpenAI Responses-compatible API |
GET/POST/PATCH/DELETE |
/api/* |
Console management API |
Console /api/* requires the administrator key. /v1/* accepts that key or an
enabled client key. /health, static frontend resources, and /v1/* CORS
preflight OPTIONS do not require authentication.
Messages / Responses are stateless compatibility APIs, not full official API implementations. They do not store server-side conversations or execute upstream-specific tools. Qoder supports images where the model allows them; experimental Devin accepts base64 data-URL images. WorkBuddy / Trae do not support images. File inputs are rejected.
Start qoder-api-proxy once before installing the optional host updater.
| Host | Container platform | Updater asset |
|---|---|---|
| Linux x86-64 | linux/amd64 |
cli2api-updater_linux_amd64 |
| Linux ARM64 | linux/arm64 |
cli2api-updater_linux_arm64 |
| macOS Intel | Docker Desktop linux/amd64 |
cli2api-updater_darwin_amd64 |
| macOS Apple Silicon | Docker Desktop linux/arm64 |
cli2api-updater_darwin_arm64 |
| Windows x86-64 | Docker Desktop Linux containers | cli2api-updater_windows_amd64.exe |
| Windows ARM64 | Docker Desktop Linux containers | cli2api-updater_windows_arm64.exe |
Installers prefer checksum-verified release binaries and fall back to the running container's binary or a local Go build when needed.
macOS + Docker Desktop:
./deploy/install-updater.sh
docker compose --env-file deploy/.env -f deploy/docker-compose.yml up -d --force-recreate qoder-api-proxyLinux + systemd:
sudo ./deploy/install-updater.sh
docker compose --env-file deploy/.env -f deploy/docker-compose.yml up -d --force-recreate qoder-api-proxyWindows + Docker Desktop in Linux-container mode, from the logged-in Docker user's PowerShell:
powershell -ExecutionPolicy Bypass -File .\deploy\install-updater.ps1
docker compose --env-file deploy\.env -f deploy\docker-compose.yml up -d --force-recreate qoder-api-proxyThe application container never receives the Docker socket. Linux uses a private
Unix Socket. macOS runs a per-user LaunchAgent and Windows runs a current-user
Scheduled Task; both Docker Desktop platforms use an authenticated updater bound
to 127.0.0.1 and reached through host.docker.internal.
In System, download the latest stable update, then confirm installation. Confirmation pauses new API traffic, creates a SQLite snapshot, and replaces the container. Active connections may be interrupted; clients should retry. Development builds without a semantic version cannot use managed updates.
Updates jump directly to the latest stable release. The console shows skipped
versions and offers rollback to one of the three previous stable releases.
If an upgrade fails its health check or updater replacement, the updater restores
the previous image and pre-update database snapshot. The five most recent
snapshots are retained in /data/backups.
The flow is implemented, but live upgrade / rollback acceptance remains pending. Keep a separate database backup before upgrading.
Keep deploy/.env private: Docker Desktop mode stores an updater token there.
Do not remove qoder-data or run docker compose down -v unless you intend to
delete the accounts and credentials.