Self-Hosted Deployment
Build, migrate, and run VitNode on your own server or Docker container with a reverse proxy.
VitNode compiles into a standard Node.js server and static assets. The only required dependency is a PostgreSQL database (Redis is optional for caching and multi-instance scaling).
Quick start
Run these 4 commands on your deployment server:
bun install --frozen-lockfile
bun run build
bun run db:migrate
bun startAlways run db:migrate before start
Production processes do not automatically run migrations at boot. Running
db:migrate ensures the database schema matches your compiled application
before traffic is served.
Required Environment Variables
Configure these in your production .env file:
# Database (PostgreSQL 15+)
POSTGRES_URL=postgresql://user:password@localhost:5432/vitnode_db
# The origin visitors reach. Email links, SSO callbacks and password-reset
# links are built from it.
VITNODE_WEB_URL=https://example.com
# Required if anything schedules cron. A long random string.
CRON_SECRET=change_me_to_a_long_random_secret_string
# Optional: Shared Cache & Realtime
REDIS_URL=redis://localhost:6379
REDIS_PASSWORD=change_me_redis_passwordWhere the API lives
A Single App answers /api/* itself, so leave VITNODE_API_URL unset.
The server takes the API origin off the request being rendered and the browser
falls back to the origin the page was served from, so the same build is correct
on every hostname you point at it.
A split deployment runs the web app and apps/api as two processes, and the
web app then has to be told where the API is - its own origin has no /api/* to
answer, so an unset value turns every server-side call into a 404, starting with
the session read on the first render. Set it in the web app's environment, and
set it before the build: it is compiled into the browser bundle too.
VITNODE_API_URL=http://localhost:8000A value set here wins over the request origin, which is why a Single App must
not carry one - http://localhost:3000 left over from development would point
production at the visitor's own machine.
Reverse Proxy (Nginx Configuration)
Proxy traffic to VitNode while forwarding client headers and supporting WebSocket upgrades:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
# WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Forwarded identity
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Running with PM2 (Process Manager)
Keep VitNode running continuously with automatic restarts on crash:
# Install PM2
bun add -g pm2
# Start both web and API processes
pm2 start "bun start" --name "vitnode-web"
pm2 save
pm2 startup