Advanced
Self-hosting
OVA is open-source and can be self-hosted on your own infrastructure. This gives you complete control over your data and allows customization.
Prerequisites
- Node.js 20.9+ (22 recommended)
- Docker Engine on the same host, for the per-session desktops
- A Solana wallet address for receiving deposits (mainnet only; devnet uses a derived test treasury)
- A Surplus Intelligence buyer key and/or an OpenRouter key (or direct Anthropic / OpenAI / Google / Moonshot keys)
Quick start
1. Clone the repository
git clone <this-repository>
cd ova2. Configure environment
cp .env.example .env
openssl rand -base64 48 # paste into JWT_SECRETEvery variable is listed and commented in .env.example. The ones that matter:
JWT_SECRET— Signs sessions. Production refuses to start without it.NEXT_PUBLIC_APP_URL— Your public origin, e.g.https://agent.example.comSURPLUS_API_KEY— Primary inference through the Surplus Intelligence market (see Inference market)OPENROUTER_API_KEY— Fallback router when the market errors or has no offerDEPOSIT_WALLET_ADDRESSandSOLANA_RPC_URL— Needed on mainnet. Only the public address is used; the app never holds a private key.
3. Build the sandbox image
npm run sandbox:buildThe app creates the solana-agent-network bridge on first launch.
4. Start the application
npm ci
npm run build # NEXT_PUBLIC_* values are inlined here
npm start # serves on port 3847Or run it in a container with docker compose up -d --build app. The compose file reads .env, mounts the Docker socket and uses host networking so the desktop proxy can reach sandbox ports on 127.0.0.1.
Environment variables
| Variable | Description | Required |
|---|---|---|
JWT_SECRET | Session signing secret | Yes |
NEXT_PUBLIC_APP_URL | Public origin of the site | Yes |
SURPLUS_API_KEY | Primary: Surplus Intelligence market | Yes* |
OPENROUTER_API_KEY | Fallback router for all models | Yes* |
ANTHROPIC_API_KEY | Direct Anthropic | No |
OPENAI_API_KEY | Direct OpenAI | No |
GOOGLE_API_KEY | Direct Google | No |
MOONSHOT_API_KEY | Direct Moonshot | No |
DOMAIN | Public hostname for Caddy / https://DOMAIN | VPS |
NETWORK | devnet, mainnet or testnet (alias for the cluster vars) | VPS |
SOLANA_CLUSTER | mainnet, devnet or testnet (devnet) | No |
SOLANA_RPC_URL | Dedicated RPC for deposit checks | Mainnet |
DEPOSIT_WALLET_ADDRESS | Address that receives deposits | Mainnet |
PAYOUT_WALLET_SECRET_KEY | Hot wallet that pays withdrawals | To enable withdrawals |
WITHDRAW_ADMIN_SECRET | Bearer token to approve held payouts | If you set a threshold |
PAYMENT_TOKEN_MINT | SPL token mint instead of SOL | No |
SANDBOX_PROVIDER | docker (default), local or auto | No |
MAX_SPEND_PER_DAY | Per-wallet daily cap in lamports | No |
SESSION_MAX_STEPS | Max agent steps per task (80) | No |
* Provide SURPLUS_API_KEY, OPENROUTER_API_KEY or a direct provider key. Routes are tried in order: Surplus, then a direct key, then OpenRouter. Without any key the app and desktop still run, and chat replies that no model is configured. A legacy LLM_API_KEY + LLM_API_BASE_URL still works as a custom OpenAI-compatible endpoint.
Production deployment
On one Debian 13 VPS, use the repo deploy/ pack: point an A record at the server, then sudo DOMAIN=agent.example.com NETWORK=mainnet bash deploy/bootstrap.sh. It installs Node, Docker, Caddy (automatic HTTPS and WebSockets for /desktop/), systemd, ufw (22/80/443) and SQLite backups. Copy secrets into .env afterwards. Steps and the recommended Hetzner size are in deploy/README.md.
- Put TLS in front and forward Host, X-Forwarded-Host and X-Forwarded-Proto. Sign-in binds to the request origin.
- Allow WebSocket upgrades on
/desktop/for the live desktop stream. - Back up the SQLite file at
DATABASE_PATH. It holds balances and the deposit ledger. - Keep sandboxes away from the metadata service, private ranges and the host:
sudo scripts/sandbox-firewall.shRun it once the first session has created solana-agent-network, and again after Docker restarts. Sandboxes keep internet access; the live desktop proxy keeps working.