Docs / Self-hosting

[ REFERENCE ]

Self-hosting

Oddysey is a Next.js app that runs on Cloudflare Workers through OpenNext, with D1 for persistence and a Cron Trigger for the sweep. Running your own instance takes about twenty minutes.

What you need

  • Node.js and npm.
  • A Cloudflare account for deployment (not needed for local development).
  • An OpenRouter key — without it no watch will ever draft anything.
  • Optionally a Finnhub key for live prices. The free tier allows 60 calls a minute.

Run it locally

setupBASH
git clone https://github.com/oddysey-ai/oddysey.git
cd oddysey
npm install
cp .env.example .env.local
npm run dev

Every variable in .env.example is optional in development, and the app degrades honestly rather than crashing: no market key means fictional prices, clearly labelled; no model key means the evaluation loop does nothing; no session secret means a random per-boot secret, so sessions reset when the dev server restarts.

Environment variables

VARIABLEREQUIREDWHAT IT DOES
SESSION_SECRETIn productionHMAC key for the session cookie. Rotating it signs everyone out and nothing more.
OPENROUTER_API_KEYFor draftingModel access for the agent loop. Without it, watches evaluate and never draft.
OPENROUTER_MODELNoOverrides the default deepseek/deepseek-v4-flash.
FINNHUB_API_KEYFor real pricesWithout it the deck shows fixture prices and says so everywhere.
CRON_SECRETFor the sweepGuards the scheduled endpoint. Unset means the sweep refuses to run — it never defaults open.
BROKER_ENCRYPTION_KEYNoAES-256-GCM key for the unmounted brokerage layer. Not needed unless you remount it.
generating the secretsBASH
openssl rand -hex 32   # SESSION_SECRET
openssl rand -hex 32   # CRON_SECRET

Deploy to Cloudflare

  1. 1

    Create the database and apply migrations

    d1BASH
    npx wrangler d1 create oddysey-deck
    # paste the returned database_id into wrangler.jsonc
    
    npx wrangler d1 migrations apply oddysey-deck --local
    npx wrangler d1 migrations apply oddysey-deck --remote

    Five migrations create the schema: watches, proposals and ledger events; the shared quote cache; the watch cooldown column; the brokerage tables; and deck tokens. Apply them both locally and remotely — they are the same files either way.

  2. 2

    Set the secrets

    worker secretsBASH
    npx wrangler secret put SESSION_SECRET
    npx wrangler secret put FINNHUB_API_KEY
    npx wrangler secret put OPENROUTER_API_KEY
    npx wrangler secret put CRON_SECRET

    These live in Cloudflare, never in the repo. .env.local is for development only.

  3. 3

    Check the cron wiring

    wrangler.jsoncJSONC
    "triggers": {
      "crons": ["*/15 * * * *"]
    },
    "services": [
      { "binding": "WORKER_SELF_REFERENCE", "service": "oddysey" }
    ]

    The scheduled handler re-enters the same Worker through the self-reference binding rather than making a round trip out to the public internet. Fifteen minutes is a deliberate compromise: often enough that a tripped watch is noticed promptly, rare enough to stay well inside the quote provider’s rate limit.

  4. 4

    Ship it

    deployBASH
    npm run deploy

    This builds with OpenNext and deploys the Worker. Use npm run preview to run the built Worker locally first — it catches Workers-runtime problems that next dev cannot.

The production gotcha worth knowing

Market-data providers rate-limit by IP, and a Cloudflare Worker’s egress address is shared with strangers.

A key that answers happily from your laptop can return 429 for five concurrent fetches in production. Oddysey handles this by fetching sequentially with 150ms spacing, retrying once on 429, and caching quotes in D1 for 120 seconds. Partial fetches accumulate in the shared cache, so a deployment that starts on placeholders heals to live prices within a few requests.

Known gap