Tera Router

Docker Compose

Deploy the Tera Router API server and dashboard as a single container with Docker Compose.

The release image at ghcr.io/montara-project/tera-router bundles the API server and the built dashboard in one container — the server serves the dashboard on the same port, so the UI and the gateway share a single origin. The SQLite database lives on a named volume and survives image upgrades.

Prerequisites

  • Docker with the Compose plugin.
  • A machine reachable from your browser (directly or behind a reverse proxy).
  • A release tag to pin, e.g. v0.4.0 — avoid latest so upgrades are reproducible.

1. Create docker-compose.yml

services:
  tera-router-llm:
    image: ghcr.io/montara-project/tera-router:v0.4.0
    container_name: tera-router-llm
    environment:
      - APP_SECRET=${APP_SECRET:?APP_SECRET wajib diisi di Dokploy env}
      - ADMIN_EMAIL=${ADMIN_EMAIL}
      - ADMIN_PASSWORD=${ADMIN_PASSWORD}

      - VITE_APP_NAME=${VITE_APP_NAME}
      - VITE_API_URL=${VITE_API_URL}

      - BETTER_AUTH_URL=${BETTER_AUTH_URL}
      - BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
    command:
      - '--machine-id=${MACHINE_ID:-1}'
      - '--debug=${DEBUG:-false}'
      - '--env=${APP_ENV:-production}'
      - '--port=${APP_PORT:-8080}'

      - '--app-name=${APP_NAME:-tera-router-server}'
      - '--cors-allowed-origins=${CORS_ALLOWED_ORIGINS:-*}'

      - '--rate-limit-exempt-ips=${RATE_LIMIT_EXEMPT_IPS}'
      - '--trusted-proxies=${TRUSTED_PROXIES}'

      - '--database-path=${DATABASE_PATH:-/app/data/terarouter.db}'
      - '--migrate-on-boot=${MIGRATE_ON_BOOT:-true}'
      - '--seed-on-boot=${SEED_ON_BOOT:-true}'
      - '--sentry-dsn=${SENTRY_DSN:-}'
    restart: unless-stopped
    healthcheck:
      test: ['CMD-SHELL', 'wget -qO- http://127.0.0.1:8080/health || exit 1']
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s
    volumes:
      - tera-data:/app/data

volumes:
  tera-data:

2. Create .env

Place it next to docker-compose.yml and fill in the values:

# Machine
MACHINE_ID=1

# App
DEBUG=false
ENV=production
PORT=8080
APP_NAME=tera-router-server
APP_SECRET=generate_from_openssl_rand_base32

VITE_APP_NAME=Tera Router
VITE_API_URL=https://yourdomain.com

BETTER_AUTH_URL=https://yourdomain.com
BETTER_AUTH_SECRET=generate_from_openssl_rand_base32

CORS_ALLOWED_ORIGINS=*
RATE_LIMIT_EXEMPT_IPS=your_private_network_ip
TRUSTED_PROXIES=your_private_network_ip

# SQLite database file (created on first run)
DATABASE_PATH=/app/data/terarouter.db

# Seeded admin account (used by `make seed` / `make seed/dev`)
ADMIN_EMAIL=[email protected]
ADMIN_PASSWORD=your_password

# Apply pending SQL migrations on server boot (true/false)
MIGRATE_ON_BOOT=true

# Run baseline seeders on server boot after migrating (true/false).
# With ENV=development, this also mints the sample API key.
SEED_ON_BOOT=true

# Sentry DSN for error tracking
SENTRY_DSN=

APP_SECRET is the house key

APP_SECRET signs session tokens and derives the key that encrypts stored provider credentials. Compose refuses to start without it. Use at least 32 random characters (openssl rand -base64 48); the server warns at boot about a shorter one. Changing it later makes every stored credential unreadable — set it once and keep it safe.

3. Start the stack

docker compose up -d
docker compose logs -f tera-router-llm

On boot the container:

  1. Runs pending SQLite migrations (MIGRATE_ON_BOOT).
  2. Seeds baseline data (SEED_ON_BOOT) — roles, the admin user from ADMIN_EMAIL/ADMIN_PASSWORD, and the Default plan.
  3. Opens the gateway (/v1/*), the dashboard API, and the dashboard UI on the same port (APP_PORT, default 8080).

Compose reports healthy once the built-in healthcheck passes (wget http://127.0.0.1:8080/health inside the container). Then open https://yourdomain.com (or http://<host>:8080 directly) and sign in with the seeded admin account.

Production notes

  • Trusted proxies — --trusted-proxies=your_private_network_ip must match the subnet your reverse proxy or load balancer actually sits in, so the server reads real client IPs for rate limiting and logs. Adjust the value before deploying.
  • IP blocking — Settings → Security → Blocked IPs refuses addresses or CIDR ranges with 403, and an address with 10 failed sign-ins in 15 minutes is blocked for 15 minutes. Both key on the client IP, so set trusted proxies first: otherwise every visitor shares the proxy's address and one attacker's failed sign-ins block everyone.
  • Rate-limit exemptions — RATE_LIMIT_EXEMPT_IPS takes a comma-separated list of IPs that bypass the dashboard rate limit (e.g. an internal monitoring host).
  • Persistence — everything stateful is the SQLite file at /app/data/terarouter.db on the tera-data volume. Include it in your backup routine.
  • Upgrades — bump the image tag, then docker compose up -d. Pending migrations apply on boot while MIGRATE_ON_BOOT=true; pinning the tag keeps the dashboard bundle and server binary in sync.

Next steps

  • Configuration — full reference of the environment variables used above.
  • Remote access — the image bundles cloudflared for the one-click Cloudflare tunnel; Tailscale runs on the Docker host.
  • Dashboard tour — get to know each page.
  • Gateway API — your first request through the router.

On this page