Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/outray-tunnel/outray/llms.txt

Use this file to discover all available pages before exploring further.

This guide walks you through deploying OutRay from source. By the end you will have the web dashboard, tunnel server, and cron service running and the CLI pointed at your instance.

Prerequisites

  • Node.js 20+ and npm
  • PostgreSQL 14+ instance
  • Redis 6+ instance
  • TimescaleDB (Tiger Data) instance — must be a separate database from PostgreSQL
  • A domain name (e.g., tunnel.example.com) with DNS pointing to your server
Keep your PostgreSQL and Redis instances off the public internet. Bind them to localhost or a private network and restrict access with authentication and firewall rules. Anyone who can reach Redis can read and write active tunnel state.

1

Clone the repository

Clone the OutRay monorepo and enter the project directory:
git clone https://github.com/akinloluwami/outray.git
cd outray
2

Install dependencies

Install dependencies for all apps from the repository root:
npm install
Then install production dependencies in each app you plan to run:
cd apps/web && npm install
3

Configure environment variables

Copy the example environment files and fill in your values:
cp apps/web/.env.example apps/web/.env
cp apps/tunnel/.env.example apps/tunnel/.env
cp apps/cron/.env.example apps/cron/.env
cp apps/internal-check/.env.example apps/internal-check/.env

Web dashboard (apps/web/.env)

# The public URL of your web dashboard
APP_URL=https://dashboard.example.com

# PostgreSQL connection string (user data, organizations, tunnels)
DATABASE_URL=postgresql://user:password@localhost:5432/outray

# Auth secret — generate with: openssl rand -hex 32
BETTER_AUTH_SECRET=your_secret_key_here
BETTER_AUTH_URL=https://dashboard.example.com

# OAuth providers (optional — configure at least one)
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret

# Transactional email (Zepto Mail)
ZEPTO_API_KEY=your_zepto_api_key

# Redis
REDIS_URL=redis://localhost:6379

# TimescaleDB (Tiger Data) for analytics
TIMESCALE_URL=postgresql://user:password@timescale-host:5432/outray_analytics

# Shared secret between the web app and tunnel server
INTERNAL_API_SECRET=generate_a_secure_random_string_here

Tunnel server (apps/tunnel/.env)

# Port the tunnel server listens on
PORT=3547

# Your tunnel subdomain base (e.g., tunnels will be <name>.tunnel.example.com)
BASE_DOMAIN=tunnel.example.com

# Redis
REDIS_URL=redis://localhost:6379

# How long a tunnel stays alive without a heartbeat (seconds)
REDIS_TUNNEL_TTL_SECONDS=120

# How often the CLI sends heartbeats (milliseconds)
REDIS_HEARTBEAT_INTERVAL_MS=20000

# Request proxy timeout (milliseconds)
REQUEST_TIMEOUT_MS=60000

# TimescaleDB for recording request/protocol events
TIMESCALE_URL=postgresql://user:password@timescale-host:5432/outray_analytics

# Port ranges assigned to TCP and UDP tunnels
TCP_PORT_RANGE_MIN=20000
TCP_PORT_RANGE_MAX=30000
UDP_PORT_RANGE_MIN=30001
UDP_PORT_RANGE_MAX=40000

# URL of the web API (used for tunnel registration)
WEB_API_URL=https://dashboard.example.com/api

Cron jobs (apps/cron/.env)

REDIS_URL=redis://localhost:6379
TIMESCALE_URL=postgresql://user:password@timescale-host:5432/outray_analytics
DATABASE_URL=postgresql://user:password@localhost:5432/outray

Internal check service (apps/internal-check/.env)

DATABASE_URL=postgresql://user:password@localhost:5432/outray
PORT=3001
INTERNAL_API_SECRET must be the same value in both apps/web/.env and passed to the tunnel server. It authenticates requests between the two services.
4

Run database migrations

Apply the Drizzle schema to your PostgreSQL database:
cd apps/web
npx drizzle-kit push
Then initialize the TimescaleDB hypertables and continuous aggregates. Run the setup script against your TimescaleDB instance:
psql "$TIMESCALE_URL" -f deploy/setup_tigerdata.sql
This script is idempotent — it is safe to run multiple times. It creates the tunnel_events, protocol_events, active_tunnel_snapshots, and request_captures hypertables, along with indexes, continuous aggregates, and a scheduled cleanup job.
5

Build the applications

Build each app before starting it in production:
cd apps/web && npm run build
6

Start the services

Start each service with your process manager. The example below uses PM2:
# Tunnel server
pm2 start apps/tunnel/dist/server.js --name outray-tunnel

# Internal check service (required for custom domain TLS verification)
pm2 start apps/internal-check/dist/index.js --name outray-internal-check

# Cron jobs
pm2 start apps/cron/dist/index.js --name outray-cron

# Save the process list so it survives reboots
pm2 save
pm2 startup
For the web dashboard, deploy apps/web as a standard Vite/React build. You can serve it from any static hosting provider or behind a reverse proxy like Caddy or nginx.
The internal check service must be reachable at http://localhost:3001/internal/domain-check by your reverse proxy. This endpoint is used by Caddy on-demand TLS to verify that a custom domain belongs to an OutRay user before issuing a certificate.
To verify the tunnel server is running:
pm2 list
You should see outray-tunnel with status online.
7

Configure the CLI to use your server

Users of your self-hosted instance need to point the OutRay CLI at your server URL instead of the default hosted service.Option 1 — Environment variable:
export OUTRAY_SERVER_URL=wss://tunnel.example.com
outray http 3000
Option 2 — server_url in outray/config.toml:
[global]
server_url = "wss://tunnel.example.com"
Option 3 — --dev flag (for local development):
outray http 3000 --dev
The --dev flag points the CLI at ws://localhost:3547, useful when running all OutRay services locally.

Verifying the deployment

After starting all services, run through this checklist:
  • Open your web dashboard URL in a browser and confirm the login page loads.
  • On the server, run pm2 list and confirm outray-tunnel, outray-internal-check, and outray-cron all show online.
  • From a local machine, set OUTRAY_SERVER_URL to your tunnel server URL and run outray http 3000. Confirm you receive a public tunnel URL.
  • Make a request to the tunnel URL and verify it appears in the dashboard’s analytics view.

Build docs developers (and LLMs) love