Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ side of the mapping in the compose file — the right side is the internal port,
the one the process listens on.


Official multi-architecture Docker images (`linux/amd64` and `linux/arm64`) are published automatically to the GitHub Container Registry (GHCR) on pushes to `master` with changes in `src/`. An automated retention policy maintains strictly the last 3 versions:
Official multi-architecture Docker images (`linux/amd64` and `linux/arm64`) are published automatically to the GitHub Container Registry (GHCR) on pushes to `master` and version tags (`v*.*.*`). An automated retention policy maintains strictly the last 3 versions. For a complete deployment guide covering Docker run, Compose, Headroom, Caveman, Cloudflare Tunnel, and Tailscale, see [Docker Deployment Guide](docs/wiki/Docker-Deployment.md).

```bash
docker pull ghcr.io/pathbit/9rtksync:latest
Expand Down
21 changes: 21 additions & 0 deletions docker-compose.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,27 @@ services:
networks:
- 9rtksync-net

# --- Otimizacao de tokens (opcional) --------------------------------------
#
# Comprime payloads de contexto e reduz custos de tokens para agentes de IA (Claude
# Code, Cursor, Windsurf) intermediando chamadas antes do gateway:
# docker compose --profile headroom up -d
9rtk-headroom:
image: ghcr.io/headroomlabs-ai/headroom:latest
container_name: 9rtk-headroom
hostname: 9rtk-headroom
profiles: ["headroom"]
restart: unless-stopped
ports:
- "127.0.0.1:8787:8787"
environment:
- UPSTREAM_URL=http://9rtk-router:20128

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use Headroom's supported upstream settings

UPSTREAM_URL is not Headroom's setting for an OpenAI-compatible backend, so this service does not route requests to 9Router as the guide claims. Headroom documents OPENAI_TARGET_API_URL and ANTHROPIC_TARGET_API_URL for custom targets in its proxy configuration; without the appropriate target, OpenAI-compatible traffic uses the public provider default and a 9Router credential is rejected rather than reaching the gateway.

Useful? React with 👍 / 👎.

- PORT=8787
Comment on lines +176 to +178

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bind Headroom to the container interface

The published Headroom image defaults HEADROOM_HOST to 127.0.0.1 inside the container, so Docker's port forwarding to the container address cannot reach this listener and http://127.0.0.1:8787/v1 returns connection failures. Headroom's own Compose example explicitly sets HEADROOM_HOST=0.0.0.0 for this reason; add that setting while retaining the host-side loopback publication.

Useful? React with 👍 / 👎.

networks:
- 9rtksync-net
Comment on lines +179 to +180

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep Headroom off the management network

This attaches a third-party inference proxy to 9rtksync-net, even though the network contract below states that this management network is isolated so only the synchronizer reaches the gateway; the router also defaults to REQUIRE_LOGIN=false, so Headroom gains access to the unprotected administrative surface in addition to /v1. Since 9rtk-router is already attached to the purpose-built rtk-inference-net, Headroom should use that inference network instead of expanding the management trust boundary.

Useful? React with 👍 / 👎.

depends_on:
- 9rtk-router

volumes:
9rtk_tailnet:
9router_data:
Expand Down
285 changes: 285 additions & 0 deletions docs/wiki/Docker-Deployment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
# Docker & Docker Compose Deployment Guide

*(Versão em português disponível na segunda metade desta página.)*

Complete guide to deploying **9RTKSync** and **9Router** via Docker and Docker Compose, including token optimization with **Headroom** and **Caveman**, remote access via **Cloudflare Tunnel** and **Tailscale**, and automated container package retention.

---

## 1. Official Docker Packages & 3-Version Retention Policy

Official multi-architecture Docker images (`linux/amd64` and `linux/arm64`) are published to the GitHub Container Registry (GHCR):

```bash
# Pull latest stable release
docker pull ghcr.io/pathbit/9rtksync:latest
Comment on lines +14 to +15

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stop labeling the moving master image as stable

The latest image is not the latest stable release: .github/workflows/release.yml applies the latest tag only when github.ref == 'refs/heads/master', while version-tag builds publish semver tags without advancing latest. Consequently, every qualifying master push can replace this image independently of a release, and users following the comment may deploy unreleased code; either recommend a versioned tag here or change the release workflow to promote releases to latest.

Useful? React with 👍 / 👎.

```

### Automated Package Retention (Last 3 Versions)

To ensure high availability, rollback safety, and clean storage, the GitHub Actions CI/CD pipeline enforces an automated package retention policy:
- **Always keeps the last 3 versions**: the active `latest` tag plus the 2 most recent tagged releases / commit SHAs.
- Manifest lists for multi-architecture builds (`amd64` + `arm64`) are resolved before pruning so platform layers are never orphaned.
- Runs automatically after every successful release build and on a weekly schedule.

> [!NOTE]
> **GHCR Visibility & Permissions**: GitHub Container Registry packages in organizations default to Private upon initial push. If `docker pull` returns `unauthorized`, an organization administrator must navigate to `github.com/orgs/pathbit/packages/container/9rtksync/settings` and set the package visibility to **Public**.

---

## 2. Quick Start with Standalone Docker

If you already have a 9Router instance running on your host machine or network:

```bash
docker run -d \
--name 9rtk-sync \
--restart unless-stopped \
Comment on lines +35 to +37

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Run the standalone synchronizer as the gateway UID

When this standalone command is corrected to mount the gateway's actual shared volume, it still runs the image as root because the Dockerfile declares no USER and the command omits --user. The synchronizer performs SQLite writes and creates state beside DB_PATH, so SQLite journals or directories can become root-owned and leave the UID-1000 gateway unable to write its own database; the repository's Compose service explicitly uses user: "1000:1000" to prevent this EACCES failure, and the standalone recipe needs the same setting.

Useful? React with 👍 / 👎.

-p 127.0.0.1:9091:9090 \
-v 9router_data:/app/data \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Mount the gateway's real data volume

When the existing gateway does not already use a Docker volume named exactly 9router_data—including the repository's standard Compose deployment, whose project-scoped volume has a different engine name—this option silently creates a new empty volume. Because synchronization reads /app/data/db/data.sqlite directly and ROUTER_URL is only used for diagnostics, the standalone container then remains DATABASE_NOT_READY; a remote gateway cannot work with this recipe at all unless its database is mounted or otherwise made locally accessible.

Useful? React with 👍 / 👎.

-v "${HOME}:/root/host:ro" \
-v 9rtksync_logs:/app/logs \
-e DB_PATH=/app/data/db/data.sqlite \
-e ROUTER_URL=http://host.docker.internal:20128 \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add the Linux host-gateway mapping

On a plain Linux Docker Engine host, host.docker.internal is not automatically installed in the container's hosts file, so this advertised standalone command cannot resolve the host-side 9Router. Docker documents that Docker Desktop supplies the name automatically, while docker run on Engine should add --add-host host.docker.internal=host-gateway (Docker run documentation); alternatively the guide should use a reachable LAN address.

Useful? React with 👍 / 👎.

-e DASHBOARD_USER=admin \
-e DASHBOARD_PASSWORD=your-secure-password \
ghcr.io/pathbit/9rtksync:latest
```

---

## 3. Production Deployment with Docker Compose

The standard production setup runs 9Router (gateway) and 9RTKSync (synchronizer) on an isolated internal management network (`9rtksync-net`), with optional shared inference networking (`rtk-inference-net`).

### Step 1: Initialize Configuration

```bash
cp .env.example .env
# Edit .env and configure INITIAL_PASSWORD, JWT_SECRET, and DASHBOARD_PASSWORD
```

### Step 2: Launch Stack

```bash
docker compose -f docker-compose.example.yml up -d

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Create the external network in the quick start

On a fresh host, this launch command fails before creating any containers because docker-compose.example.yml attaches 9rtk-router to rtk-inference-net, which is declared external: true. Docker requires external networks to exist before docker compose up, as stated in the official Compose networking documentation; the repository's make setup already creates it, so the production steps should use that target or run docker network create rtk-inference-net first.

Useful? React with 👍 / 👎.

```

### Service Map and Port Allocation

| Service | Container Name | Internal Port | Host Published Port | Purpose |
| :--- | :--- | :--- | :--- | :--- |
| **9Router** | `9rtk-router` | `20128` | `127.0.0.1:8081` | Gateway API & Admin |
| **9RTKSync** | `9rtk-sync` | `9090` | `127.0.0.1:9091` | Guardian Dashboard & Healthz |
| **Headroom** *(optional)* | `9rtk-headroom` | `8787` | `127.0.0.1:8787` | Token Compression Proxy |

Dashboard access: **http://localhost:9091**

---

## 4. Token & Cost Optimization: Headroom & Caveman

AI coding agents (such as **Claude Code**, **Cursor**, **Windsurf**, and **Roo Code**) consume vast amounts of tokens when sending large files, terminal logs, git diffs, and conversational explanations. Using **Headroom** and **Caveman** together drastically cuts token consumption and prevents rate limits.

```
┌────────────────────────────────┐
│ Coding Agent / Developer │
│ (Claude Code / Cursor) │
└───────────────┬────────────────┘
│
▼
┌────────────────────────────────┐
│ Caveman (Plugin/Skill) │ <-- Minimizes OUTPUT tokens
│ (Enforces concise responses) │
└───────────────┬────────────────┘
│
▼
┌────────────────────────────────┐
│ Headroom (Proxy: 8787) │ <-- Compresses INPUT tokens
│ (Trims logs, AST & context) │
└───────────────┬────────────────┘
│
▼
┌────────────────────────────────┐
│ 9Router (Gateway: 8081) │ <-- Routing & Fallback Combos
└───────────────▲────────────────┘
│
┌───────────────┴────────────────┐
│ 9RTKSync (Guardian: 9091) │ <-- OAuth renewal & rate-limit healing
└────────────────────────────────┘
```

### Headroom: Input Token Compression Proxy

Headroom acts as a middleware proxy sitting between your coding agent and 9Router:
- Analyzes context payloads, logs, git diffs, and structured JSON.
- Compresses input tokens by 20% to 80% without losing critical code semantics.
- To start with Docker Compose:

```bash
docker compose -f docker-compose.example.yml --profile headroom up -d
```

Configure your coding tool to point to Headroom (`http://127.0.0.1:8787/v1`) with 9Router credentials. Headroom automatically proxies optimized requests to `http://9rtk-router:20128`.

### Caveman: Output Token Reduction Skill

While Headroom compresses what the LLM *reads*, Caveman minimizes what the LLM *writes*:
- Enforces an ultra-terse, compact agent communication style ("concise mode").
- Strips conversational filler, preamble, and pleasantries while maintaining strict accuracy for code edits and terminal actions.
- Configured in Claude Code (`settings.local.json` prompt instructions) or agent system instructions.

---

## 5. Remote Access: Cloudflare Tunnel & Tailscale

By default, ports are bound to `127.0.0.1` for loopback security. When you need remote access from laptops, mobile devices, or team members, enable Cloudflare Tunnel or Tailscale.

> [!WARNING]
> **Security Prerequisite**: Before enabling any public tunnel or remote exposure, you **must** set:
> 1. `REQUIRE_LOGIN=true` (with a strong custom password)
> 2. `REQUIRE_API_KEY=true` (for coding tool authentication)
Comment on lines +140 to +141

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Wire the remote-auth flags into Compose

In the documented Compose flow, changing these values in .env does not enable either protection: docker-compose.example.yml hardcodes REQUIRE_LOGIN=false and REQUIRE_API_KEY=false instead of interpolating the corresponding .env entries. A user who follows this warning and then starts the tunel profile still exposes the gateway's admin and inference endpoints without authentication, so the Compose service must consume these variables or the guide must explicitly require editing the Compose file.

Useful? React with 👍 / 👎.

> Exposing the gateway without these settings allows anyone with the URL to consume your LLM balances.

### Option A: Cloudflare Quick Tunnel (Ephemeral URL)

Useful for temporary demonstrations and remote testing without domain setup:

```bash
docker compose -f docker-compose.example.yml --profile tunel up -d
```

View the generated `https://...trycloudflare.com` URL in container logs:

```bash
docker logs 9rtk-tunel
```

Alternatively, click the **Tunnel** button directly in the 9Router Web UI (API Endpoint screen).

### Option B: Cloudflare Named Tunnel (Persistent Domain)

For permanent custom domains (`router.yourdomain.com`) with Cloudflare Zero Trust:

1. Create a tunnel in the Cloudflare Zero Trust dashboard.
2. Add your tunnel token to `.env`:
```bash
TUNNEL_TOKEN=eyJhIjoi...
```
3. Start the tunnel profile:
```bash
docker compose -f docker-compose.example.yml --profile tunel up -d
```

### Option C: Tailscale (Private Zero-Trust Tailnet — Recommended)

The safest method: your gateway joins your private tailnet and is never exposed to the public internet.

1. Generate an ephemeral or reusable auth key at `https://login.tailscale.com/admin/settings/keys`.
2. Add `TS_AUTHKEY` to `.env`:
```bash
TS_AUTHKEY=tskey-auth-...
```
3. Start the tailnet container:
```bash
docker compose -f docker-compose.example.yml --profile tailnet up -d
```
4. Access via MagicDNS at `http://9rtk:20128` or Tailscale IP `100.x.y.z:20128`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Provide the advertised Tailscale serve config

Starting the documented tailnet profile does not make port 20128 reachable: the Compose service sets TS_SERVE_CONFIG=/config/serve.json, but mounts only /var/lib/tailscale, and a repository-wide search finds no serve.json to mount. Tailscale requires that JSON file to define the services being proxied and says it must be mounted into the container (Tailscale Docker parameters); merely joining the sidecar to the Docker network does not expose 9rtk-router, so this access instruction fails until the config and mount are added.

Useful? React with 👍 / 👎.


You can also use the native **Tailscale** button in the 9Router dashboard or install Tailscale directly on the host machine:

```bash
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
tailscale ip -4
```

---

## 6. Container Healthchecks & Monitoring

Verify the status of all services:

```bash
docker compose -f docker-compose.example.yml ps
```

The synchronizer exposes `/healthz` on port 9090 (host 9091):
- `OK` (HTTP 200): Synchronizer active and gateway reachable.
- `DATABASE_NOT_READY` (HTTP 503): Database path not found.
- `ROUTER_SERVICE_UNREACHABLE` (HTTP 503): Gateway not answering.

---

*(Versão em Português)*

# Guia de Implantação via Docker e Docker Compose

Guia completo para implantar o **9RTKSync** e o **9Router** via Docker e Docker Compose, com otimização de tokens via **Headroom** e **Caveman**, acesso remoto seguro via **Cloudflare Tunnel** e **Tailscale**, e retenção automatizada de imagens no GitHub Packages.

## 1. Pacotes Oficiais e Política de Retenção de 3 Versões

As imagens oficiais multi-arquitetura (`linux/amd64` e `linux/arm64`) são geradas e publicadas no GitHub Container Registry (GHCR):

```bash
docker pull ghcr.io/pathbit/9rtksync:latest
```

### Retenção Automática das 3 Últimas Versões

O pipeline do GitHub Actions garante que:
- **Sempre mantemos as 3 versões mais recentes**: a tag ativa `latest` mais as 2 versões/tags marcadas anteriores.
- Os manifestos multi-plataforma são resolvidos antes da limpeza para evitar a quebra de camadas.
- A limpeza ocorre ao término de cada build de release e em agendamento semanal.

> [!NOTE]
> **Visibilidade no GHCR**: Por padrão de segurança do GitHub, pacotes criados sob organizações nascem com visibilidade Privada. Se o comando `docker pull` responder com `unauthorized`, o administrador da organização no GitHub deve acessar as configurações do pacote (`github.com/orgs/pathbit/packages/container/9rtksync/settings`) e alterar a visibilidade para **Public**.

## 2. Implantação Rápida com Docker Run

Para executar o sincronizador apontando para um 9Router já existente:

```bash
docker run -d \
--name 9rtk-sync \
--restart unless-stopped \
-p 127.0.0.1:9091:9090 \
-v 9router_data:/app/data \
-v "${HOME}:/root/host:ro" \
-v 9rtksync_logs:/app/logs \
-e DB_PATH=/app/data/db/data.sqlite \
-e ROUTER_URL=http://host.docker.internal:20128 \
-e DASHBOARD_USER=admin \
-e DASHBOARD_PASSWORD=sua-senha-segura \
ghcr.io/pathbit/9rtksync:latest
```

## 3. Implantação Completa com Docker Compose

```bash
# 1. Prepare as variáveis de ambiente
cp .env.example .env

# 2. Inicie a stack
docker compose -f docker-compose.example.yml up -d
```

Acesse o painel web em: **http://localhost:9091**

## 4. Otimização de Tokens: Headroom e Caveman

A combinação de **Headroom** e **Caveman** atua em duas frentes complementares:
1. **Headroom (Entrada/Input):** Middleware proxy que intercepta context windows, logs de compilação e comandos git diff, compactando a carga antes do envio ao 9Router. Ative via perfil:
```bash
docker compose -f docker-compose.example.yml --profile headroom up -d
```
Aponte seus clientes para `http://127.0.0.1:8787/v1`.
2. **Caveman (Saída/Output):** Instrução de estilo conciso para agentes (Claude Code / Cursor) que elimina rodeios e respostas prolixas, gerando respostas diretas e economizando 30% a 60% de tokens de saída.

## 5. Acesso Remoto: Cloudflare Tunnel e Tailscale

Regra essencial de segurança: antes de expor qualquer rota, ative `REQUIRE_LOGIN=true` e `REQUIRE_API_KEY=true`.

- **Cloudflare Tunnel Rápido:** `docker compose -f docker-compose.example.yml --profile tunel up -d` (ou clique no botão `Tunnel` no painel do 9Router).
- **Cloudflare Tunnel Nomeado:** Preencha `TUNNEL_TOKEN` no `.env` e suba o perfil `tunel`.
- **Tailscale (VPN Privada):** Preencha `TS_AUTHKEY` no `.env` e execute `docker compose -f docker-compose.example.yml --profile tailnet up -d` (ou utilize o botão `Tailscale` do 9Router).
1 change: 1 addition & 0 deletions docs/wiki/Home.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ republishes these pages automatically. Editing a page directly here will be over
| Page | What it covers |
| :--- | :--- |
| [Installation](Installation) | Docker Compose and local virtual environment |
| [Docker Deployment](Docker-Deployment) | Docker run, Compose, Headroom, Caveman, Tunnel, Tailscale |
| [Configuration](Configuration) | Every environment variable — the full headless contract |
| [Dashboard](Dashboard) | The server-rendered panel, language switcher, cron logs |
| [Authentication](Authentication) | Credentials, headless mode, break-glass recovery |
Expand Down
1 change: 1 addition & 0 deletions docs/wiki/Installation.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Installation

Two supported paths: Docker (recommended) and a local Python virtual environment.
For a complete guide covering Docker run, Compose, Headroom, Caveman, Tunnel, and Tailscale, see [Docker Deployment](Docker-Deployment).

---

Expand Down
1 change: 1 addition & 0 deletions docs/wiki/_Sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- [Home](Home)
- [Installation](Installation)
- [Docker Deployment](Docker-Deployment)
- [Configuration](Configuration)
- [Dashboard](Dashboard)
- [Authentication](Authentication)
Expand Down
2 changes: 1 addition & 1 deletion tools/valida_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
# CLI deste produto.
"--profile", "--env-file", "--remove-orphans", "--wait",
"--wait-timeout", "--force-recreate", "--no-autoupdate", "--url",
"--token", "-d", "-f",
"--token", "-d", "-f", "--name", "--restart",
}

# Rotas do GATEWAY, nao do painel. A pagina de saida de rede cita a API do
Expand Down
Loading