Docker Compose
The fastest path from zero to a running Orsa instance. This guide walks through every step — clone, configure, start, verify.
Quick Start (5 minutes)
# 1. Clone the repo
git clone https://github.com/paragonhq/orsa.git
cd orsa
# 2. Copy environment file
cp .env.example .env.local
# 3. Start Supabase locally
supabase start
# 4. Start Redis + Browser Worker
docker compose up -d
# 5. Run database migrations
supabase db push
# 6. Seed the database (optional — loads NAICS codes, test data)
pnpm db:seed
# 7. Install dependencies and start dev servers
pnpm install
pnpm devYour services are now running:
- API: http://localhost:3001 (opens in a new tab)
- Web Dashboard: http://localhost:3000 (opens in a new tab)
- Docs: http://localhost:3002 (opens in a new tab) (if started)
- Browser Worker: http://localhost:3002 (opens in a new tab)
- Supabase Studio: http://localhost:54323 (opens in a new tab)
- Redis: localhost:6379
docker-compose.yml Walkthrough
The provided docker-compose.yml runs the infrastructure services that Orsa depends on. The application itself (API, Web) runs via pnpm dev for development or deploys to Vercel/your host for production.
version: '3.8'
services:
# ─── Redis ────────────────────────────────────────────────────
# Cache layer + rate limiting backend.
# In production, replace with Upstash Redis or a managed Redis instance.
redis:
image: redis:7-alpine
ports:
- '6379:6379'
volumes:
- redis-data:/data
command: redis-server --appendonly yes
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 10s
timeout: 5s
retries: 3
# ─── Browser Worker ───────────────────────────────────────────
# Playwright Chromium pool for scraping, screenshots, JS rendering.
# In production, deploy to Fly.io or a dedicated machine.
browser-worker:
build:
context: ./services/browser-worker
dockerfile: Dockerfile
ports:
- '3002:3002'
environment:
- PORT=3002
- NODE_ENV=development
- BROWSER_POOL_SIZE=3 # Concurrent browser instances
- MAX_CONCURRENT_PAGES=10 # Total open pages across all browsers
- PAGE_TIMEOUT=30000 # 30s per-page timeout
depends_on:
redis:
condition: service_healthy
volumes:
- ./services/browser-worker/src:/app/src # Hot reload in dev
# Required for Chromium:
shm_size: '512mb'
volumes:
redis-data:Key Configuration
| Variable | Default | Description |
|---|---|---|
BROWSER_POOL_SIZE | 3 | Number of concurrent Chromium instances. Each uses ~200-400 MB RAM. |
MAX_CONCURRENT_PAGES | 10 | Maximum open pages across all browser instances. |
PAGE_TIMEOUT | 30000 | Milliseconds before a page load is considered failed. |
shm_size | 512mb | Shared memory for Chromium. Must be ≥256 MB. |
Production Docker Compose
For production self-hosting without Kubernetes, use this extended compose file:
version: '3.8'
services:
# ─── API ──────────────────────────────────────────────────────
api:
build:
context: .
dockerfile: apps/api/Dockerfile
ports:
- '3001:3001'
env_file:
- .env.local
environment:
- NODE_ENV=production
- PORT=3001
depends_on:
redis:
condition: service_healthy
restart: unless-stopped
deploy:
resources:
limits:
memory: 1G
cpus: '1.0'
# ─── Web Dashboard ───────────────────────────────────────────
web:
build:
context: .
dockerfile: apps/web/Dockerfile
ports:
- '3000:3000'
env_file:
- .env.local
environment:
- NODE_ENV=production
- PORT=3000
restart: unless-stopped
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
# ─── Redis ────────────────────────────────────────────────────
redis:
image: redis:7-alpine
ports:
- '6379:6379'
volumes:
- redis-data:/data
command: redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 10s
timeout: 5s
retries: 3
restart: unless-stopped
deploy:
resources:
limits:
memory: 768M
cpus: '0.5'
# ─── Browser Worker ───────────────────────────────────────────
browser-worker:
build:
context: ./services/browser-worker
dockerfile: Dockerfile
ports:
- '3002:3002'
environment:
- PORT=3002
- NODE_ENV=production
- BROWSER_POOL_SIZE=5
- MAX_CONCURRENT_PAGES=20
- PAGE_TIMEOUT=30000
depends_on:
redis:
condition: service_healthy
shm_size: '1gb'
restart: unless-stopped
deploy:
resources:
limits:
memory: 4G
cpus: '2.0'
# ─── Reverse Proxy ───────────────────────────────────────────
caddy:
image: caddy:2-alpine
ports:
- '80:80'
- '443:443'
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy-data:/data
- caddy-config:/config
depends_on:
- api
- web
restart: unless-stopped
volumes:
redis-data:
caddy-data:
caddy-config:Create a Caddyfile in the project root:
api.yourdomain.com {
reverse_proxy api:3001
}
app.yourdomain.com {
reverse_proxy web:3000
}
browser.yourdomain.com {
reverse_proxy browser-worker:3002
}Step-by-Step Setup
1. Clone and Configure
git clone https://github.com/paragonhq/orsa.git
cd orsa
cp .env.example .env.localEdit .env.local with your actual values. See the Configuration page for every variable.
2. Start Supabase
For local development, use the Supabase CLI:
# Install Supabase CLI
brew install supabase/tap/supabase # macOS
# or: npx supabase@latest # any OS
# Start local Supabase stack (Postgres, Auth, Storage, Studio)
supabase startThis outputs connection details. Update .env.local:
NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321
NEXT_PUBLIC_SUPABASE_ANON_KEY=<output from supabase start>
SUPABASE_SERVICE_ROLE_KEY=<output from supabase start>For production, use Supabase Cloud (opens in a new tab) or self-hosted Supabase. See Providers → Supabase.
3. Run Migrations
# Apply all SQL migrations to the database
supabase db pushThis runs all files in supabase/migrations/ in order:
00001_initial_schema.sql— Core tables (brands, users, API keys, credits, crawl jobs, usage events, audit log)00002_rls_policies.sql— Row Level Security policies00003_seed_data.sql— NAICS codes and reference data00004_merchant_descriptors_expansion.sql— Merchant identification tables00005_webhooks.sql— Webhook delivery tables
4. Start Infrastructure Services
docker compose up -dVerify everything is running:
# Check containers
docker compose ps
# Test Redis
docker compose exec redis redis-cli ping
# → PONG
# Test Browser Worker
curl http://localhost:3002/health
# → {"status":"ok","browsers":3,"pages":0}5. Install Dependencies and Start
pnpm install
pnpm devThis starts all workspace packages in development mode via Turborepo:
apps/apion port 3001apps/webon port 3000
6. Seed the Database (Optional)
pnpm db:seedThis runs scripts/seed.ts which loads:
- NAICS classification codes
- Test brands for development
- Sample API keys
7. Verify the Installation
# Health check
curl http://localhost:3001/api/v1/health
# Test scraping (no auth required for health, auth required for data endpoints)
curl -H "Authorization: Bearer YOUR_API_KEY" \
"http://localhost:3001/api/v1/web/scrape/markdown?url=https://example.com"
# Test brand retrieval
curl -H "Authorization: Bearer YOUR_API_KEY" \
"http://localhost:3001/api/v1/brand/retrieve?domain=stripe.com"Generating TypeScript Types
After any migration, regenerate the database types:
pnpm generate:typesThis runs supabase gen types typescript --local > packages/db/src/types/database.ts and keeps your TypeScript types in sync with the database schema.
Stopping Services
# Stop all containers
docker compose down
# Stop and remove volumes (deletes Redis data)
docker compose down -v
# Stop Supabase
supabase stopNext Steps
- External Providers — Configure proxies, LLMs, Stripe, and more
- Configuration — Complete environment variable reference
- Troubleshooting — Common issues and fixes