This guide provides step-by-step instructions for deploying StellarStream to various platforms and using Docker.
- Stellar Smart Contract Deployment
- Backend Deployment (Render)
- Frontend Deployment (Vercel)
- Post-Deploy Verification
- Docker Deployment
- Troubleshooting
Before deploying the backend, you must deploy the Soroban smart contract to the Stellar Testnet.
- Soroban CLI installed.
- A Stellar account with testnet XLM.
- Generate a new keypair if you don't have one:
soroban config identity generate deployer
- Fund it via Friendbot:
curl "https://friendbot.stellar.org/?addr=$(soroban config identity address deployer)"
- Navigate to the root directory.
- Run the deployment script (replace with your secret key):
SECRET_KEY="YOUR_SECRET_KEY" ./scripts/deploy.sh - Note the Contract ID output (also saved in
contracts/contract_id.txt). You will need this for the backend configuration.
The backend is a Node.js Express app (TypeScript, compiled to JS) that uses a SQLite database. This guide walks through deploying it on Render as a Web Service.
⚠️ Frontend package.json: Before deploying, ensurefrontend/package.jsonexists and is not empty. If it's missing or 0 bytes, restore it from git withgit checkout frontend/package.json.
- A Render account
- Your GitHub repository connected to Render
- A deployed Stellar smart contract (see Section 1)
- From the Render Dashboard, click New + → Web Service.
- Connect your GitHub repository and select the repo.
- Configure the service:
- Name:
stellar-stream-backend(or your preferred name) - Root Directory:
backend - Runtime:
Node - Build Command:
npm run build - Start Command:
npm start - Plan: Free (or choose a paid plan for better performance)
- Name:
SQLite stores data in a single file. Render's ephemeral filesystem is reset on every deploy, so you must mount a persistent disk to preserve the database.
- In your Render Web Service dashboard, go to Disks.
- Click Add Disk.
- Configure:
- Name:
streams-data - Mount Path:
/data - Size: 1 GB (sufficient for thousands of streams)
- Name:
- Click Save.
Add the following environment variables in your Render Web Service dashboard under Environment.
| Variable | Required | Example Value | Description |
|---|---|---|---|
PORT |
No | 3001 |
Internal port (Render sets this automatically) |
CONTRACT_ID |
Yes | C... |
Soroban contract ID from Section 1 |
SERVER_PRIVATE_KEY |
Yes | S... |
Stellar secret key for the server account |
JWT_SECRET |
Yes | openssl rand -hex 32 |
Secret used to sign JWT tokens |
ADMIN_API_KEY |
Yes | openssl rand -hex 32 |
Admin API key (min 32 chars) |
DB_PATH |
Yes | /data/streams.db |
Path to SQLite file on the persistent disk |
ALLOWED_ASSETS |
No | USDC,XLM |
Comma-separated list of allowed asset codes |
ALLOWED_ORIGINS |
Yes | https://your-app.vercel.app |
Frontend URL(s) for CORS (comma-separated) |
RPC_URL |
No | https://soroban-testnet.stellar.org:443 |
Stellar RPC endpoint |
NETWORK_PASSPHRASE |
No | Test SDF Network ; September 2015 |
Stellar network passphrase |
HORIZON_URL |
No | https://horizon-testnet.stellar.org |
Stellar Horizon endpoint |
WEBHOOK_DESTINATION_URL |
No | https://your-app.com/webhooks |
URL for webhook delivery (optional) |
WEBHOOK_SIGNING_SECRET |
No | (generate a random string) | HMAC secret for webhook payload signing |
INDEXER_POLL_INTERVAL_MS |
No | 10000 |
How often (ms) to poll Stellar for events |
RECONCILIATION_INTERVAL_MS |
No | 60000 |
How often (ms) to reconcile local state with chain |
Generate secrets with:
openssl rand -hex 32Important:
DB_PATHmust point to the persistent disk mount path (/data/streams.db). If you use the default (data/streams.dbrelative to the app directory), data will be lost on every deploy.
In your Render Web Service settings, set:
- Health Check Path:
/api/health
Render will poll this endpoint every 5 seconds. A 200 OK response with {"status":"ok"} means the service is healthy.
- Click Create Web Service. Render will clone your repo, install deps, run the build, and start the server.
- Watch the Logs tab for any errors. A successful start looks like:
Server started on port 3001 Indexer started, polling every 10000ms
- WAL (Write-Ahead Logging) mode is already enabled in
db.ts. This significantly improves concurrent read/write performance. - WAL mode creates two companion files alongside your database:
streams.db-walandstreams.db-shm. These live on the persistent disk alongsidestreams.db. - Do not delete the
-waland-shmfiles while the app is running — doing so can corrupt the database.
The frontend is a React app built with Vite + Tailwind CSS. This guide walks through deploying it on Vercel.
- A Vercel account (log in with GitHub)
- Your backend deployed and accessible at a public URL (see Section 2)
- From the Vercel Dashboard, click Add New → Project.
- Import your GitHub repository.
- Configure the project:
- Root Directory:
frontend - Framework Preset:
Vite(Vercel auto-detects this) - Build Command:
npm run build - Output Directory:
dist - Node.js Version: 20.x (match the backend version)
- Root Directory:
Add the following environment variables in the Vercel project settings under Environment Variables.
| Variable | Required | Example Value | Description |
|---|---|---|---|
VITE_API_URL |
Yes | https://your-backend.onrender.com/api |
URL of your deployed backend API |
VITE_CONTRACT_ID |
No | C... |
Soroban contract ID (if frontend interacts directly with chain) |
VITE_RPC_URL |
No | https://soroban-testnet.stellar.org:443 |
Stellar RPC endpoint |
VITE_NETWORK_PASSPHRASE |
No | Test SDF Network ; September 2015 |
Stellar network passphrase |
VITE_API_URLis the most important variable. It must point to your Render backend URL with the/apisuffix. Example:https://stellar-stream-backend.onrender.com/api.
- Click Deploy. Vercel will build and deploy the frontend automatically.
- Once complete, Vercel provides a URL like
https://stellar-stream.vercel.app. - Go to your Render backend's environment variables and update
ALLOWED_ORIGINSto include the Vercel URL.
The app uses client-side routing (React Router). Vite's build output is a single-page app — Vercel handles SPA fallback automatically. No additional vercel.json or redirect rules are needed.
After deploying both the backend and frontend, run these checks to confirm everything is working.
curl https://your-backend.onrender.com/api/healthExpected response:
{
"service": "stellar-stream-backend",
"status": "ok",
"timestamp": "2026-07-29T12:00:00.000Z"
}The health endpoint returns a 200 OK with {"status":"ok"}. If you get a timeout or 5xx, the service may still be starting (see Cold Start Delays).
Test that the API returns stream data (will be empty on first deploy):
curl https://your-backend.onrender.com/api/streamsExpected response:
{
"data": [],
"total": 0,
"page": 1,
"limit": 20
}Verify the stats endpoint, which reads from SQLite:
curl https://your-backend.onrender.com/api/statsExpected response:
{
"data": {
"total": 0,
"active": 0,
"paused": 0,
"completed": 0,
"canceled": 0,
"scheduled": 0,
"onChainStreamCount": 0,
"localStreamCount": 0
}
}A valid JSON response confirms the database is initialized and queryable.
curl -I https://your-app.vercel.appExpected response: A 200 OK or 304 Not Modified status with a content-type: text/html header.
Also open the URL in a browser and verify:
- The page loads without console errors
- The backend API URL is reachable (check Network tab for API calls)
- Wallet connection flow works (if using Freighter)
# Set your actual URLs
BACKEND_URL="https://your-backend.onrender.com"
FRONTEND_URL="https://your-app.vercel.app"
echo "--- Backend Health ---"
curl -s $BACKEND_URL/api/health | jq .
echo "--- API Stream List ---"
curl -s $BACKEND_URL/api/streams | jq .
echo "--- Frontend ---"
curl -sI $FRONTEND_URL | head -5
echo "--- CORS Check ---"
curl -s -H "Origin: $FRONTEND_URL" -H "Access-Control-Request-Method: GET" \
-X OPTIONS $BACKEND_URL/api/streams -w "%{http_code}" -o /dev/null
# Expected: 204Note: The
jqcommand is optional — pipe topython3 -m json.toolifjqis unavailable.
For a quick production-like setup using Docker Compose.
- Copy
backend/.env.exampletobackend/.envand fill in the required values. - Run the following command from the root directory:
docker-compose up -d --build
Create a docker-compose.prod.yml if you need specific production overrides (e.g., removing dev-only tools):
version: "3.9"
services:
backend:
build:
context: ./backend
dockerfile: dockerfile
command: ["npm", "start"] # Assuming 'start' runs compiled JS
frontend:
build:
context: ./frontend
dockerfile: dockerfile
command: ["npm", "run", "preview", "--", "--host"]Ensure the CONTRACT_ID environment variable is correctly set in your deployment platform. The indexer will not start without it.
Check the webhook_dead_letters table in the database. Ensure WEBHOOK_DESTINATION_URL is accessible from the backend server. Refer to the Runbook for re-queueing instructions.
Ensure the backend ALLOWED_ORIGINS environment variable includes your frontend domain. If you see opaque CORS errors, also check that no protocol mismatch exists (e.g., http vs https).
This can happen if multiple processes try to write to the SQLite file. In production, ensure only one instance of the backend is running at a time. WAL mode (already enabled in db.ts) significantly reduces locking but does not eliminate it with multiple concurrent writers.
Render's free tier spins down a web service after 15 minutes of inactivity. The first request after a spin-down can take 30–60 seconds to respond while the service starts up.
Symptoms:
- The health check or first API call hangs or times out
- Logs show the service starting fresh
Mitigations:
- Accepted: The delay is normal for free tier. Just wait and retry.
- Paid: Upgrade to a paid Render plan to enable "Prevent Cold Starts" (keeps the service always awake).
- Monitoring: Set up a cron job (e.g., GitHub Actions or cron-job.org) to ping
/api/healthevery 10 minutes.
# Ping health every 10 minutes to prevent spin-down (cron job)
curl -s https://your-backend.onrender.com/api/health > /dev/nullSQLite's WAL mode creates two companion files alongside the database:
streams.db-wal— write-ahead logstreams.db-shm— shared memory file
These files are automatically managed by SQLite. They live on the persistent disk alongside the main .db file.
Common issues:
- Missing
-walor-shmfiles after redeploy: If you destroyed and re-created the persistent disk, the files are recreated automatically. As long asstreams.dbis intact, your data is safe. - Accidental deletion of WAL files while app is running: This can corrupt the database. Always stop the service before manipulating database files.
- Backup strategy: Periodically copy the entire directory (including WAL files) for backups:
To restore, stop the service, replace the
# Backup the database directory (run from a maintenance window) cp -r /data /data-backup/datacontents, and restart.
| Symptom | Likely Cause | Fix |
|---|---|---|
Backend starts but /api/streams returns empty always |
DB_PATH points to ephemeral storage; data lost on restart |
Set DB_PATH to the persistent disk mount path (e.g., /data/streams.db) |
Indexer not starting in logs |
CONTRACT_ID not set or invalid |
Verify CONTRACT_ID matches the deployed contract |
| Frontend loads but API calls fail with 404 | VITE_API_URL points to the wrong URL |
Ensure it includes the full backend URL with /api suffix |
| CORS errors in browser console | ALLOWED_ORIGINS missing or doesn't include the frontend URL |
Set ALLOWED_ORIGINS to your Vercel URL |
ERR_MODULE_NOT_FOUND or better-sqlite3 errors |
Native module mismatch — Node.js version differs between dev and Render | Ensure the Node.js version in Render settings matches your dev environment (20.x) |
| Webhook deliveries stuck | WEBHOOK_DESTINATION_URL unreachable from Render's network |
Verify the destination is publicly accessible and not behind a firewall |
| Database corruption after crash | SQLite wasn't properly closed | WAL mode is crash-safe in most cases. Use PRAGMA integrity_check to verify |
| Backend crashes on startup with segfault | better-sqlite3 compiled for wrong architecture |
Rebuild native modules by setting npm rebuild better-sqlite3 in the build command |
- Go to your Render Web Service dashboard.
- Click Logs in the sidebar.
- Look for startup messages:
- ✅
Server started on port 3001— app is running - ✅
Database initialized— SQLite connected - ✅
Indexer started— Stellar indexer is polling - ❌ Any error stack traces — indicates misconfiguration
- ✅
After changing environment variables in Render, you must manually trigger a deploy:
- In your Render Web Service dashboard, go to Manual Deploy.
- Click Deploy latest commit (or Clear build cache & deploy if you suspect caching issues).
- Wait for the build and deploy to complete (check Logs tab).
On Vercel, environment variable changes trigger an automatic redeploy. If not, use Redeploy from the Vercel dashboard.