9router in Docker Compose, esposto con Cloudflare Tunnel
9router in Docker Compose, protetto e persistente, esposto via Cloudflare Tunnel con fallback automatico tra le tue subscription AI.
Testato il: 2026-09-13 Ambiente: Ubuntu 24.04.4 LTS · Docker Engine 29.8.0 · Docker Compose v5.5.1 · cloudflared 2026.9.0 · 9router 0.5.75
Prerequisiti
- Un server Linux (qui Ubuntu 24.04) con Docker Engine e il plugin Docker Compose installati
- Un dominio i cui nameserver puntano a Cloudflare, per il tunnel nominato (il quick tunnel non lo richiede)
- Almeno una subscription AI da usare come upstream: Claude Pro, ChatGPT Business/Plus, OpenCode Go
- cloudflared installato sul server: lo si installa al passo 6 se manca
Lo scopo è questo: un solo endpoint OpenAI-compatible, http://localhost:20128/v1, che inoltra le richieste alle subscription che già paghi e, quando una va in quota, passa automaticamente alla successiva. Niente più sessioni di coding ferme a mezzanotte per un rate limit.
1. Preparare la directory e generare i segreti
Crea la directory di lavoro:
mkdir -p ~/9router && cd ~/9router9router legge i segreti dalle variabili d'ambiente. Genera tre valori casuali, uno per JWT_SECRET (firma dei cookie di sessione), uno per API_KEY_SECRET (firma delle API key generate) e uno per MACHINE_ID_SALT:
openssl rand -hex 32Esegui il comando tre volte e conserva i tre output: ti serviranno al passo successivo. Poi scegli una password forte per INITIAL_PASSWORD: non è un dettaglio, vedi il passo 4.
2. Creare .env e docker-compose.yml
Crea il file .env nella directory:
cat > .env <<'EOF'
JWT_SECRET=INSERISCI_Q1
API_KEY_SECRET=INSERISCI_Q2
MACHINE_ID_SALT=INSERISCI_Q3
INITIAL_PASSWORD=INSERISCI_UNA_PASSWORD_FORTE
DATA_DIR=/app/data
PORT=20128
NODE_ENV=production
EOFSostituisci i placeholder con i valori generati al passo 1. DATA_DIR=/app/data è il punto in cui il container scrive il database, che ora mappiamo su un volume. Le variabili di sicurezza AUTH_COOKIE_SECURE e REQUIRE_API_KEY le attiviamo al passo 7, quando il servizio diventa pubblico: in locale non servono e complicherebbero solo il primo accesso.
Poi il file Compose. Uso un bind mount invece del named volume del repo ufficiale: $HOME/.9router resta visibile sul host, il database db/data.sqlite lo puoi ispezionare e fare backup con un semplice cp.
services:
9router:
image: decolua/9router:0.5.75
container_name: 9router
restart: always
ports:
- "20128:20128"
volumes:
- "$HOME/.9router:/app/data"
env_file:
- .env
environment:
DATA_DIR: /app/data
PORT: "20128"
HOSTNAME: "0.0.0.0"
NODE_ENV: productionNota la versione dell'immagine, 0.5.75, fissata esplicitamente: non usare :latest su un servizio che esponi, così un aggiornamento è una decisione tua (docker compose pull && docker compose up -d), non un evento casuale al prossimo riavvio.
Verifica che il file sia valido:
docker compose config --quietNessun output e codice di uscita 0 significa che la sintassi è corretta.
3. Avviare lo stack e verificare l'endpoint in locale
docker compose up -dAtteso: il container parte e resta attivo. Controlla:
docker compose psAtteso: 9router in stato Up. Poi i log di avvio:
docker logs -f 9routerAtteso: una riga che indica il driver del database, ad esempio [DB] Driver: node:sqlite | file: /app/data/db/data.sqlite, e il server in ascolto sulla porta 20128. Premi Ctrl+C per uscire dal follow.
Conferma che l'endpoint risponde:
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:20128/v1/modelsAtteso: 200. Se vedi 000, il container non è raggiungibile: controlla docker compose ps e che la porta non sia già occupata da un altro processo (ss -ltnp | grep 20128).
4. Primo accesso alla dashboard e cambio password
Apri http://localhost:20128/dashboard e accedi con la password che hai messo in INITIAL_PASSWORD.
Qui c'è il punto su cui non transigo: cambia subito la password.
La prima cosa da fare dopo il login è Dashboard → Settings → Change Password, con la nuova password forte. Poi, se vuoi, riduci il rischio residuo disattivando i plugin MCP che non usi.
5. Creare l'API key e collegare i provider
L'endpoint /v1 si autentica con una API key che crei tu dal dashboard. Vai in Dashboard → Settings → API Keys, genera una chiave e copia il valore (ha il formato 9r_...): ti serve per qualsiasi tool che punti all'endpoint.
Ora collega le subscription. I percorsi nel dashboard sono questi:
- Claude Pro: Dashboard → Providers → Connect Claude Code → OAuth login. Modelli disponibili:
cc/claude-opus-4-7,cc/claude-sonnet-4-6 - ChatGPT (Codex): Dashboard → Providers → Connect Codex → OAuth login (callback sulla porta 1455). Modelli:
cx/gpt-5.5,cx/gpt-5.4 - OpenCode Go: Dashboard → Providers → Connect OpenCode Free, nessun login richiesto. Modello generico:
oc/<auto>
Il valore di 9router è il fallback a tier: subscription come primario, free come ultima spiaggia. Crea un combo in Dashboard → Combos → Create New:
Name: personal-stack
Models:
1. cc/claude-opus-4-7
2. cx/gpt-5.5
3. oc/<auto>Quando la quota di Claude si esaurisce, la richiesta passa a Codex; se anche quello va in limite, finisce su OpenCode. Nei tool, invece di un modello singolo, punti a personal-stack.
Verifica un provider prima di proseguire: Dashboard → Providers → il pulsante Test accanto alla connessione deve rispondere con esito positivo.
6. Quick tunnel per il primo test esterno
Il quick tunnel non richiede account né dominio: è il modo più rapido per vedere il servizio dall'esterno, ma l'URL cambia a ogni riavvio. Usalo solo come test.
cloudflared tunnel --url http://localhost:20128Atteso: cloudflared apre un URL https://<identificatore-casuale>.trycloudflare.com. Aprilo dal browser: devi vedere la dashboard, e da qui puoi già usare 9router da qualsiasi dispositivo. Per l'uso stabile però serve il tunnel nominato del passo successivo.
7. Tunnel nominato e attivazione delle protezioni pubbliche
Installazione di cloudflared
Installa cloudflared, se non l'hai già fatto (repo ufficiale Cloudflare):
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main" | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt-get update && sudo apt-get install cloudflaredVerifica la versione:
cloudflared --versionLogin e creazione del tunnel
Autentica il tunnel col tuo account Cloudflare:
cloudflared tunnel loginAtteso: si apre un browser, scegli il dominio da gestire e cloudflared genera il certificato cert.pem in ~/.cloudflared/. Crea il tunnel:
cloudflared tunnel create 9routerAtteso: un messaggio con l'UUID del tunnel e il percorso del file di credenziali, ~/.cloudflared/<UUID>.json. Copia l'UUID. Ora crea la configurazione:
cat > ~/.cloudflared/config.yml <<EOF
url: http://localhost:20128
tunnel: INSERISCI_L_UUID
credentials-file: /root/.cloudflared/INSERISCI_L_UUID.json
EOFSostituisci l'UUID in entrambi i punti (se l'utente non è root, il percorso del file di credenziali è ~/.cloudflared/INSERISCI_L_UUID.json). Associa il DNS:
cloudflared tunnel route dns 9router router.tuodominio.comAtteso: un record CNAME che punta a <UUID>.cfargotunnel.com.
Esposizione e protezioni pubbliche
A questo punto il servizio ha un indirizzo stabile, quindi è il momento di alzare le protezioni. Modifica .env:
cat >> .env <<'EOF'
REQUIRE_API_KEY=true
AUTH_COOKIE_SECURE=true
BASE_URL=https://router.tuodominio.com
EOFdocker compose up -dAtteso: il container viene ricreato con le nuove variabili. Avvia il tunnel:
cloudflared tunnel run 9routerAtteso: il tunnel si connette agli edge Cloudflare e resta in attesa. Per la persistenza dopo un riavvio del server, Cloudflare mette a disposizione l'installazione come servizio:
cloudflared service installche legge la configurazione da /etc/cloudflared/config.yml (sposta lì il file se usi questa strada).
Verifica
Dall'esterno, con l'API key creata al passo 5:
curl -s https://router.tuodominio.com/v1/models -H "Authorization: Bearer INSERISCI_LA_API_KEY"Atteso: 200 e il JSON con la lista dei modelli. Senza la chiave:
curl -s -o /dev/null -w "%{http_code}\n" https://router.tuodominio.com/v1/modelsAtteso: 401: con REQUIRE_API_KEY=true l'endpoint rifiuta le richieste anonime. Controlla anche che la dashboard sia servita in HTTPS:
curl -sI https://router.tuodominio.com/dashboard | head -1Atteso: HTTP/2 200. Infine, da un tool client (Claude Code, Codex o Cursor) punta l'endpoint a https://router.tuodominio.com/v1 con la tua API key e il modello personal-stack: la prima richiesta deve rispondere senza errori.
Limiti noti
- Il quick tunnel genera un URL nuovo a ogni avvio: per l'uso quotidiano serve il tunnel nominato, non sostituirlo.
REQUIRE_API_KEY=trueprotegge l'endpoint/v1, ma la dashboard resta protetta solo dalla password. Se vuoi uno strato in più, metti davanti al tunnel un accesso Cloudflare Access con il tuo account come unico utente.- La password di fallback
123456(CVE-2026-63732) è insita nel codice anche nelle versioni recenti seINITIAL_PASSWORDnon è valorizzata: la copertura è la tua password forte e l'aggiornamento dell'immagine, non altro. - I nomi dei modelli (es.
cc/claude-opus-4-7,cx/gpt-5.5) seguono il catalogo del momento: se un modello sparisce dal README del progetto, il combo va aggiornato dal dashboard. - L'immagine
decolua/9router:0.5.75è quella verificata per questa guida: prima di esporre un nuovo tag, controlla i changelog del progetto.