Deployment
one server, one disk.
OUTRIDER is a long-running process that writes to its data folder. Host it on one small server with a persistent disk, keep the dashboard on localhost, and put a reverse proxy with HTTPS in front if you want to reach it from a browser.
01What the desk needs from a host
The commands on this page are written for the shell of macOS and Linux (bash or zsh). On Windows, run them in WSL (Windows Subsystem for Linux, Ubuntu) or Git Bash: commands such as cp, openssl, rm -rf and multi-line curl with \ do not work as written in PowerShell or cmd. The node and npm commands work in any of them.
- One always-on process. A VPS or a home server. Serverless platforms do not fit: the desk loops forever, keeps WebSockets open and stores its console in SQLite on disk.
- A persistent disk for
data/: state, journal, recordings, wallet lists and the console database. - Node.js 22+ and outbound internet. Run
node src/cli.mjs doctoron the server: some APIs answer differently from data-centre addresses. - One instance per data folder. Two desks on the same
data/would write over each other.
02Install on the server
- Copy the folderUpload the unzipped package, for example to
/opt/outrider, and createconfig.json(Configuration). - Install, build and check
Production runs plaincd /opt/outrider npm ci npm run build # compiles the operator console to dist/ npm test node src/cli.mjs doctornode; there is no TypeScript loader at run time. Runnpm ciwithout--omit=dev(and withoutNODE_ENV=productionin the shell), because the build needs the TypeScript compiler from the dev tools. - Set the console secret keyRequired on a server. Generate it once and store it in your process manager's environment, never in git:
If you skip it, the desk writes one toopenssl rand -hex 32data/.admin-secret-key. Either way, lose the key and the secrets stored in the console cannot be read.
03Keys in .env
Copy .env.example to .env next to package.json and fill in what you use. The desk loads it on start; a variable already set in the environment wins. .env is in .gitignore and .dockerignore: never commit it.
cp .env.example .env
openssl rand -hex 32 # paste as ADMIN_SECRET_KEY
04Docker
The package includes a Dockerfile and docker-compose.yml. The image is built in two stages: the first installs the dependencies (compiling better-sqlite3 when no prebuilt binary fits the platform), runs npm run build and drops the dev tools; the second runs plain node src/cli.mjs run. You do not run the build yourself. The port is published on the host's loopback only.
- Keys
cp .env.example .env # set ADMIN_SECRET_KEY and ADMIN_SETUP_TOKEN at least - Your settings (optional) Create
config.jsonfromconfig.example.jsonand uncomment the./config.jsonline undervolumesindocker-compose.yml. Without it the container uses the defaults:.dockerignorekeeps your localconfig.jsonout of the image. - Start
docker compose up -d --build # open http://127.0.0.1:8790/admin (through an SSH tunnel on a server) and create the owner with ADMIN_SETUP_TOKEN docker compose logs -f outrider # without ADMIN_SETUP_TOKEN: the one-time setup code (printed on every start until the owner exists) docker compose down # stops cleanly: the desk saves its state first
Data lives in the named volume outrider-data, created with the right owner, so it works on any host. Back it up with docker compose cp outrider:/app/data ./backup. Inside the container the dashboard listens on 0.0.0.0 (DASH_HOST); that is allowed only because the operator console guards it. Memory: check the container's use on your host (docker stats); the research refresh runs in a separate process and needs more memory than the desk itself while it runs.
05PM2
npm i -g pm2
npm run build && pm2 start ecosystem.config.cjs
pm2 logs outrider # the setup code on the first start, if ADMIN_SETUP_TOKEN is not set
pm2 save && pm2 startup # start again after a reboot
The included ecosystem.config.cjs runs src/cli.mjs run with plain node, gives the desk 30 seconds to save on stop, restarts it if it exits, and restarts it above 2 GB of memory (max_memory_restart). Secrets come from .env or the environment, never from that file.
06Run it as a service (systemd)
An example systemd unit to adapt (the user, paths and variables are yours; not part of the package). Run npm run build in the folder before you start it:
[Unit]
Description=OUTRIDER desk
After=network-online.target
[Service]
User=outrider
WorkingDirectory=/opt/outrider
Environment=ADMIN_SECRET_KEY=<64 hex characters>
Environment=ADMIN_SETUP_TOKEN=<48 hex characters, remove once the owner exists>
Environment=TELEGRAM_BOT_TOKEN=<token>
Environment=TELEGRAM_CHAT_ID=<chat id>
ExecStart=/usr/bin/npm start
Restart=on-failure
KillSignal=SIGINT
TimeoutStopSec=30
[Install]
WantedBy=multi-user.target
On stop the desk finishes its cycle, saves state and closes sockets. Create the owner with the ADMIN_SETUP_TOKEN value, then remove that line and reload the unit. Without the token, read the first log for the one-time setup code (journalctl -u outrider).
07Create the owner before you open it
Run directly or under PM2, the dashboard and console listen on 127.0.0.1 only. In Docker they listen on 0.0.0.0 inside the container, and docker-compose.yml publishes the port on the host's 127.0.0.1 only. Either way, reach them through an SSH tunnel and create the owner account first:
ssh -L 8790:127.0.0.1:8790 <user>@<server>
# then open http://127.0.0.1:8790/admin on your own computer
Until the owner exists, the desk answers only requests whose Host is a loopback address (in Docker: every dashboard request is refused until the owner exists), so a browser coming through a public domain gets 403 create the owner at /admin first on the dashboard. /admin itself stays reachable, and creating the owner needs ADMIN_SETUP_TOKEN from the server environment, or the one-time setup code printed in the server log when no token is set. Creating the owner through the tunnel is the simplest path. Consider requiring two-factor for owners and managers right after.
08Reverse proxy with HTTPS
If you want the dashboard on a domain, put a reverse proxy on the same server. Three things matter:
- Allowed hosts. The desk rejects requests whose Host is not a loopback address with
403 host not allowed(protection against DNS rebinding). List your public domain(s) inDASH_ALLOWED_HOSTS, comma-separated, for exampleDASH_ALLOWED_HOSTS=desk.example.com. The proxy passes the original Host header. - HTTPS. Send
X-Forwarded-Proto: httpsso the session cookie is markedSecure. - Streaming. The page uses Server-Sent Events on
/api/stream; turn off response buffering. - Client address. Set
TRUST_PROXY_HOPS=1(one proxy on the same server) so the console's sign-in limits count each visitor by the address your proxy saw, not by the proxy's own address. The console then reads the right-mostX-Forwarded-Forentry, the one your proxy wrote; anything a browser writes further left is ignored. Leave it empty when nothing sits in front of the console, and never set it higher than the number of proxies you run.
Add the variable to the service (above) and restart:
Environment=DASH_ALLOWED_HOSTS=desk.example.com
Environment=TRUST_PROXY_HOPS=1
Example nginx configuration to adapt (your domain, your certificate paths):
server {
listen 443 ssl;
server_name desk.example.com;
# ssl_certificate … (for example from certbot)
location / {
proxy_pass http://127.0.0.1:8790;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_buffering off;
proxy_read_timeout 1h;
}
}
Example Caddy configuration to adapt:
desk.example.com {
reverse_proxy 127.0.0.1:8790 {
header_up X-Forwarded-Proto https
flush_interval -1
}
}
Caddy passes the original Host header by default. Check your proxy after setting it up: before the owner exists, a request with your public Host gets 403 on the dashboard and reaches /admin/; a Host that is not listed in DASH_ALLOWED_HOSTS gets 403 host not allowed. The automated test suite covers the Host check. The troubleshooting page shows how to check the cookie flags behind your proxy.
09Backups
| What | Why |
|---|---|
data/journal.jsonl, data/state.json | Your paper track record and the desk's state. The journal is hash-chained; a restored copy still verifies. |
data/admin.sqlite* | Console users, settings, history and audit log (SQLite with -wal and -shm files). |
ADMIN_SECRET_KEY or data/.admin-secret-key | Without it, secrets stored in the console cannot be decrypted. Keep it separate from the data backup. |
config.json | Your settings; may contain your RPC key. |
data/pump-events/, gecko-trades/, wallet files | Recordings and wallet books. Large; back up if you want to replay later. |
The simplest safe copy: stop the service, copy data/ and config.json, start it again. What the secret key protects, and what to do if it is lost: Secrets and backups.
10A public read-only demo
node scripts/export-demo.mjs demo-site # sample paper book only
node scripts/export-demo.mjs demo-site --real data # plus your real liquidation map, wallets, leaderboard and calls
node scripts/export-demo.mjs demo-site --frame-origin https://your-landing.example # allow your landing page to embed it
It writes a static site (index.html, the static/ stylesheet and modules, demo-shim.js, api-static/*.json, SNAPSHOT.txt and a vercel.json with security headers) that shows the real dashboard page fed from snapshots. The header shows Snapshot · <date> and the page never polls or opens a stream. The paper book is sample data with the banner; the kill switch answers that it only works on your own desk. By default it shows the memecoin core; add --modules perps,polymarket,jev to include optional modules. Host the folder on any static host (the security headers in vercel.json must be copied to your host's own header settings if it is not Vercel).
The generated vercel.json lets the demo be framed by its own site and by https://preview.codecanyon.net (frame-ancestors). To show it in an iframe on your landing page too, pass that page's origin with --frame-origin (an https:// origin).
With --real, the copied files contain real wallet addresses and positions from public chains; publish only what you are comfortable showing.
11Updating
- Read the changelogCheck Changelog for migration notes (from 1.0.0: Upgrading).
- Stop and back upStop the service and copy
data/andconfig.json. - Replace the codeCopy the new version over the old folder, keeping
data/andconfig.json. - Install, build and check
npm ci && npm run build npm test node src/cli.mjs doctor node src/cli.mjs verify - StartStart the service and open the dashboard. PM2:
pm2 reload outrider. Docker:docker compose up -d --build(the image builds the console itself).