f3router docs

English · 简体中文

Install & deploy

← Back to README · Documentation index

Everything about getting f3router running: the one-liner, Docker Compose, local development, declarative config, production builds, the desktop app, where your data lives, and how to reset a password, read the logs, or uninstall.

Quick start (one-liner)

Docker required — sets up ~/f3router, generates an encryption key, pulls the image, and starts the container:

curl -fsSL https://raw.githubusercontent.com/f3tools/f3router/main/docs/install.sh | bash

Prefer to read before you pipe to bash? The script is here. Re-running it is safe: your .env (and encryption key) is preserved and the container updates to :latest. Override the defaults with F3ROUTER_DIR, PORT, or HOST_BIND env vars.

On Windows, the easiest path is the desktop .exe installer from Releases (below); the Docker steps work in WSL or any bash shell.

On Android, see the experimental Termux installation guide. It uses Node's built-in SQLite driver and does not require the Android NDK.

Open http://localhost:3001, add your provider keys on the Keys page, reorder the Fallback Chain to taste, and grab your unified API key from the Keys page header. That unified key is what you point your OpenAI SDK at.

Your install keeps itself updated from the signed catalog feed. The current full catalog is listed at f3router.co/models.

Docker Compose

Runs the API and dashboard together on port 3001 and persists SQLite in a named volume.

Prerequisites: Docker, Docker Compose, OpenSSL.

On macOS / Linux (Bash):

git clone https://github.com/f3tools/f3router.git
cd f3router

# Generate an encryption key for at-rest key storage
ENCRYPTION_KEY="$(openssl rand -hex 32)"
printf "ENCRYPTION_KEY=%s\nPORT=3001\n" "$ENCRYPTION_KEY" > .env

docker compose up -d

On Windows (PowerShell):

git clone https://github.com/f3tools/f3router.git
cd f3router

$Bytes = New-Object Byte[] 32
[Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($Bytes)
$ENCRYPTION_KEY = -join ($Bytes | ForEach-Object { "{0:x2}" -f $_ })
"ENCRYPTION_KEY=$ENCRYPTION_KEY`nPORT=3001" | Out-File -Encoding utf8 .env
docker compose up -d

Reaching it from another machine? By default the container is published only on 127.0.0.1, so http://<server-ip>:3001 won't load from another device (the page just hangs). To expose it on your LAN — e.g. a Raspberry Pi at http://192.168.1.x:3001 — start it with HOST_BIND=0.0.0.0:

HOST_BIND=0.0.0.0 docker compose up -d

Only do this on a trusted network: the proxy is single-user and guarded only by the unified API key.

Providers unreachable from the container, but fine from the host? A container has its own network stack, so two things that work on your machine do not carry over:

  • A proxy on 127.0.0.1 is not your machine. Inside the container, loopback is the container itself. If you reach providers through a proxy client on the host (Clash, v2rayN, sing-box, a corporate proxy), point f3router at the host instead: PROXY_URL=socks5h://host.docker.internal:7890. The bundled docker-compose.yml maps host.docker.internal to the host gateway, so this works on plain Linux Docker as well as Docker Desktop. The proxy also has to accept connections from outside loopback (in Clash, allow-lan: true).
  • An IPv6-only host needs IPv6 enabled in Docker. The default bridge network is IPv4-only, so on a host with no IPv4 route the container cannot reach anything, DNS included. Enable it in /etc/docker/daemon.json with "ipv6": true, "ip6tables": true and a "fixed-cidr-v6" range, then restart Docker.

To see which of these you are hitting, ask the container directly:

docker compose exec f3router node -e "fetch('https://generativelanguage.googleapis.com/').then(r=>console.log('ok',r.status)).catch(e=>console.log('fail',e.cause?.code||e.message))"

Local development

Prerequisites: Node.js 20+, npm.

On macOS / Linux (Bash):

git clone https://github.com/f3tools/f3router.git
cd f3router
npm install
ENCRYPTION_KEY="$(node -e 'console.log(require("crypto").randomBytes(32).toString("hex"))')"
printf "ENCRYPTION_KEY=%s\nPORT=3001\n" "$ENCRYPTION_KEY" > .env
npm run dev

On Windows (PowerShell):

git clone https://github.com/f3tools/f3router.git
cd f3router
npm install
$ENCRYPTION_KEY = node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
"ENCRYPTION_KEY=$ENCRYPTION_KEY`nPORT=3001" | Out-File -Encoding utf8 .env
npm run dev

ENCRYPTION_KEY is required for startup. When NODE_ENV is not production and it is unset, the server auto-generates a development key and saves it to a .encryption-key file (0600) next to the SQLite database, not inside it. Older installs that kept the key in the database are migrated to this file on first boot. Do not rely on that fallback with real provider keys; set ENCRYPTION_KEY.

Rotating the encryption key

Stored secrets are AES-256-GCM, so simply changing ENCRYPTION_KEY does not re-encrypt anything — it makes every provider key, per-key proxy override, client-profile credential and saved Fetch Relay token fail to decrypt at once. Re-encrypt them first, with the server stopped:

# 1. Generate the new key
NEW_KEY="$(node -e 'console.log(require("crypto").randomBytes(32).toString("hex"))')"

# 2. Preview (reads only, writes nothing)
cd server
ENCRYPTION_KEY=<old key> npm run rotate-encryption-key -- \
  --new-key "$NEW_KEY" --dry-run

# 3. Rotate for real
ENCRYPTION_KEY=<old key> npm run rotate-encryption-key -- --new-key "$NEW_KEY"

--old-key <hex> overrides the key read from ENCRYPTION_KEY, and --db <path> points at a database somewhere other than server/data. Nothing is written unless every value decrypts, so a wrong old key aborts with a non-zero exit and an untouched database.

Then put the new key in .env (ENCRYPTION_KEY=$NEW_KEY) and restart. Under Docker, run the command inside the container against the mounted database and restart the container with the updated .env. Take a copy of the database file before rotating.

Request analytics are retained for 90 days or 100000 request rows by default, whichever limit prunes first. Set REQUEST_ANALYTICS_RETENTION_DAYS=0 or REQUEST_ANALYTICS_MAX_ROWS=0 in .env to disable either retention limit.

Open http://localhost:5173 (the Vite dev UI), add your provider keys on the Keys page, reorder the Fallback Chain to taste, and grab your unified API key from the Keys page header. That unified key is what you point your OpenAI SDK at.

Reaching the dev UI from another device on your LAN? Use npm run dev:lan — it passes --host through to Vite, which then prints a Network: http://<your-ip>:5173 URL you can open from a phone or another machine. (Plain npm run dev -- --host does not work here: the root dev script is a concurrently wrapper, so the flag never reaches Vite.) API calls go through Vite's dev proxy, so no extra server config is needed.

For a production build without Docker:

npm run build
node server/dist/index.js     # server + dashboard both served on :3001

Declarative startup config

For repeatable Docker/server installs, f3router can apply a JSON config on every boot. Set FREEAPI_CONFIG_PATH=/path/to/f3router.config.json or put the same JSON in FREEAPI_CONFIG_JSON. The config is idempotent: existing keys, custom providers, model edits, fallback rows, and routing settings are updated instead of duplicated.

{
  "admin": { "email": "ops@example.com", "password": "change-me-min-8-chars" },
  "license": "premium license key from f3router.co",
  "keys": [
    { "platform": "groq", "key": "gsk_...", "label": "main" },
    { "platform": "google", "key": "AIza...", "enabled": true }
  ],
  "customProviders": [
    {
      "baseUrl": "http://host.docker.internal:11434/v1",
      "label": "Ollama",
      "models": [
        { "model": "llama3.1:8b", "displayName": "Local Llama", "supportsTools": true }
      ]
    }
  ],
  "models": [
    {
      "platform": "groq",
      "modelId": "llama-3.3-70b-versatile",
      "displayName": "Llama 3.3 70B",
      "supportsTools": true,
      "fallbackEnabled": true
    }
  ],
  "routing": { "strategy": "balanced" }
}

Two optional top-level fields cover first-run provisioning:

  • admin creates the first dashboard account, but only while no account exists — a claimed install ignores the entry with a warning, so the config can never take it over. Because the config is applied before the HTTP listener starts, an admin entry also closes the unauthenticated first-run setup window entirely: no setup code is minted and POST /api/auth/setup answers 409 from the first request on.
  • license is a Premium key from f3router.co. It is validated against the license service before being stored — the same contract as the dashboard's Premium page — then a live-tier catalog sync starts right away. Activation runs in the background so an unreachable license service never delays boot; a stored key identical to the configured one short-circuits before any network call, and a changed key re-activates (rotation).

The config carries plaintext secrets (provider keys, the admin password, the license key), so protect the file or env value exactly like ENCRYPTION_KEY.

If two custom endpoints serve the same model id, add "endpoint" to a models or fallback entry to say which one you mean — the endpoint's URL, or the short handle the dashboard shows next to it. Without it, an entry that matches more than one endpoint is rejected rather than applied to an arbitrary one:

{
  "models": [
    { "platform": "custom", "modelId": "deepseek-v3.1", "endpoint": "https://relay-b.example.com/v1", "enabled": false }
  ]
}

Docker image & operations

f3router publishes a single production image that contains the Express server and the built React dashboard:

docker pull ghcr.io/f3tools/f3router:latest   # or pin a release, e.g. :v1.2.3

The image is multi-arch (linux/amd64 + linux/arm64, so it runs on a Raspberry Pi). Published tags: latest (the newest release), main (the default branch), v*.*.* (git release tags), and sha-<commit>.

The included docker-compose.yml is the recommended install path:

docker compose up -d
docker compose logs -f f3router

Updating: docker compose pull && docker compose up -d (your volume and .env carry over). To update from the dashboard instead, set WATCHTOWER_TOKEN in .env (e.g. openssl rand -hex 32) and start with the opt-in profile:

docker compose --profile autoupdate up -d

Settings → Check for updates then shows Update now when a newer release exists: a Watchtower sidecar pulls the image and recreates the f3router container, and the dashboard reloads when it is back. Watchtower runs with only its token-protected update endpoint, never updates on a schedule, publishes no port and only touches the f3router container — but it mounts the Docker socket, which is root-equivalent on the host, so the profile stays off unless you start it.

By default the container's port is bound to 127.0.0.1 (localhost only). To reach the dashboard/API from another machine on your network, publish it on all interfaces with HOST_BIND=0.0.0.0 docker compose up -d — only on a trusted LAN, since the proxy is single-user.

Plain HTTP over a LAN address works as-is: the security headers that only apply to HTTPS (upgrade-insecure-requests, Cross-Origin-Opener-Policy, Origin-Agent-Cluster) are emitted only when the request actually arrived over TLS — or over loopback, which browsers already treat as a secure context. Behind an HTTPS reverse proxy they come back on automatically, as long as the proxy forwards X-Forwarded-Proto. CSP_UPGRADE_INSECURE_REQUESTS=true|false overrides the upgrade directive if your setup needs it.

SQLite data is stored in the f3router-data volume at /app/server/data. Keep the same .env ENCRYPTION_KEY and volume when upgrading, because provider keys are encrypted at rest. If your host only persists a specific directory, set FREEAPI_DB_PATH=/that/path/f3router.db.

On hosts with ephemeral disks, configure an encrypted backup target:

FREEAPI_DB_BACKUP_PATH=/app/server/data/f3router.db.backup
# or:
FREEAPI_DB_BACKUP_URL=https://example.com/f3router.db.backup
FREEAPI_DB_BACKUP_TOKEN=optional-bearer-token
FREEAPI_DB_BACKUP_KEY=64-char-hex-backup-key
FREEAPI_DB_BACKUP_INTERVAL_MS=300000

When the database file is missing at startup, f3router restores the backup before migrations run. While the server is running it uploads a fresh encrypted backup periodically. If FREEAPI_DB_BACKUP_KEY is omitted, the app uses ENCRYPTION_KEY for the backup envelope too.

More Docker operations and examples live in docker/README.md.

Desktop app

A native menu-bar app lives in desktop/: the entire router + dashboard running locally from your tray, with a glass popover showing live request stats.

f3router desktop app

Download from Releases — the macOS .dmg and the Windows .exe installer are built and attached to every release by the desktop-release workflow. Or build it from this repo in a few minutes:

Mac downloads: choose arm64 for Apple Silicon or x64 for Intel. Both require macOS 12 Monterey or later and include DMG and ZIP downloads.

Updating: the installed app updates itself on macOS, on Windows (the Setup installer) and from the Linux AppImage. ⋯ → Settings → Check for updates (or the tray's Check for Updates…) downloads the new release and offers Restart to update; a full database backup, listed as Pre-update on the Backups page, is written before it installs. With Automatic update check on, new releases download in the background and install on the next restart or quit; the app never restarts on its own. The Linux .deb/.rpm/.tar.xz and the portable Windows .zip update by downloading the new release.

Note for Windows users building from source: Building the desktop app requires compiling native SQLite modules for Electron. You must have Visual Studio Build Tools installed (specifically the "Desktop development with C++" workload) and Python installed before running npm install.

npm install
npm install --prefix desktop  # install desktop dependencies
npm run desktop:dist          # macOS  → desktop/dist-electron/f3router-…-arm64.dmg
npm run desktop:dist:mac:x64  # Intel Mac → desktop/dist-electron/f3router-…-x64.dmg
npm run desktop:dist:win      # Windows → "desktop/dist-electron/f3router-Setup-….exe"

Locally built apps are unsigned, so Windows SmartScreen may warn on first run ("More info" → "Run anyway"); the macOS build launches without Gatekeeper prompts. Full instructions in desktop/README.md.

Credentials and where your data lives

The desktop app has no username or password to set up. Unlike the server (which gates its dashboard behind an email + password account), the desktop build signs the dashboard in automatically with a hidden local account, so you're never prompted for credentials and never need one.

The only credential you need is your unified API key — the f3router-… token your OpenAI/Anthropic client points at. Get it from:

  • the tray popover — click the tray icon, then Copy Key, or
  • the dashboard Keys page header (tray → Open Dashboard).

You do not need to open or edit freeapi.db by hand.

Your settings and data live in one folder per OS (copy it to migrate to another machine or into a container):

OS Location
Windows %APPDATA%\f3router\ (e.g. C:\Users\<you>\AppData\Roaming\f3router\)
macOS ~/Library/Application Support/f3router/
Linux ~/.config/f3router/

That folder holds freeapi.db (all keys, models, settings, encrypted at rest), config.json (window/theme/port/LAN preferences) and logs/freeapi.log (what the app would print to a terminal if it had one). Copy the first two to move an install. For the server (non-desktop) deployment, the equivalent state is the .env file and the SQLite DB at server/data/freeapi.db (or wherever FREEAPI_DB_PATH points).

FAQ: passwords, logs, uninstall

I forgot my dashboard password

Desktop app — there is no password. The dashboard signs itself in with a hidden local account, so there is nothing to remember and nothing to reset. If you cannot see the dashboard, open it from the tray icon → Open Dashboard.

Server installs (Docker, one-liner, npm run dev) do have an email + password account, and there is no email delivery to send a reset link to. The flow is a one-time code printed to the server log:

  1. On the login page, click Forgot password?, then Send reset code.

  2. Read the code from the server log (see below). It looks like this:

      Password-reset code: HWNHPU5QGZ
      Enter this code on the reset form to set a new password.
    
  3. Type it into the reset form together with your new password.

The code is valid for 15 minutes, and requesting a new one invalidates the previous code. Requests are rate-limited, so ask for one code and wait for it rather than clicking repeatedly.

Where are the logs?

Install method Where the log goes
Docker Compose docker compose logs -f f3router
Plain Docker docker logs -f <container> (docker ps lists the name)
One-liner install cd ~/f3router && docker compose logs -f f3router
npm run dev / node server/dist/index.js the terminal the server is running in
Desktop app <data dir>/logs/freeapi.log — tray icon → right-click → Open Logs Folder

The desktop app has no terminal attached, so it also tees everything it prints to a file: freeapi.log in the logs folder inside the data directory listed above — for example ~/Library/Application Support/f3router/logs/freeapi.log on macOS. It keeps the current file plus one rotated freeapi.log.1, 1 MB each. Open it in any text editor; the reset code above appears there too.

How do I uninstall?

Removing the app never removes your data directory — deleting that is a separate, deliberate step, which is also what makes it safe to reinstall over the top.

Desktop app

  1. Quit from the tray menu (Quit f3router). If you turned on Start at login in the popover, switch it off first so no stale login item is left.
  2. Remove the application:
    • macOS — drag f3router.app from /Applications to the Trash.
    • Windows — Settings → Apps → Installed apps → f3router → Uninstall.
    • Linux — delete the AppImage, sudo apt remove f3router for the .deb, or sudo dnf remove f3router for the .rpm.
  3. Delete the data directory to remove your keys, settings and logs for good:
    • Windows — %APPDATA%\f3router\
    • macOS — ~/Library/Application Support/f3router/
    • Linux — ~/.config/f3router/

Docker

docker compose down -v            # -v also drops the f3router-data volume
docker image rm ghcr.io/f3tools/f3router:latest
rm -rf ~/f3router               # the one-liner's directory: .env + compose file

Leave off -v if you want to keep the database for a later reinstall.

From source — delete the checkout. The state that matters is .env and server/data/freeapi.db (or wherever FREEAPI_DB_PATH points), so back those up first if you plan to come back.