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— avoidlatestso 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-llmOn boot the container:
- Runs pending SQLite migrations (
MIGRATE_ON_BOOT). - Seeds baseline data (
SEED_ON_BOOT) — roles, the admin user fromADMIN_EMAIL/ADMIN_PASSWORD, and the Default plan. - Opens the gateway (
/v1/*), the dashboard API, and the dashboard UI on the same port (APP_PORT, default8080).
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_ipmust 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_IPStakes 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.dbon thetera-datavolume. Include it in your backup routine. - Upgrades — bump the image tag, then
docker compose up -d. Pending migrations apply on boot whileMIGRATE_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
cloudflaredfor 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.