Documentation Index
Fetch the complete documentation index at: https://docs.steward.fi/llms.txt
Use this file to discover all available pages before exploring further.
Self-Hosting Steward
Steward is designed to be self-hosted. This guide covers deploying Steward on your own infrastructure.
Requirements
- Runtime: Bun v1.0+
- Database: PostgreSQL 15+ (or Neon for serverless)
- Node.js: 18+ (for build tooling)
- OS: Linux (Ubuntu 22.04+ recommended), macOS, or Windows WSL2
Quick Start
git clone https://github.com/Steward-Fi/steward.git
cd steward
cp .env.example .env
# Edit .env with your values (see Environment Variables below)
docker compose up -d
See Docker Deployment for the full docker-compose.yml and network isolation.git clone https://github.com/Steward-Fi/steward.git
cd steward
bun install
cp .env.example .env
# Edit .env, then:
cd packages/db && bun run drizzle-kit push
cd ../api && bun run dev
For local development without PostgreSQL:git clone https://github.com/Steward-Fi/steward.git
cd steward
bun install
STEWARD_EMBEDDED=true STEWARD_MASTER_PASSWORD=dev-password bun run packages/api/src/index.ts
Embedded mode uses PGLite (an in-process Postgres) with data stored on disk. No third-party database needed.Embedded mode is for local development only. Data is stored in a local directory and is not suitable for production. Use PostgreSQL for any deployment that handles real keys.
Environment Variables
Core (Required)
# Master encryption password — derives the AES-256-GCM key for all wallet/secret encryption
# CRITICAL: If lost, all encrypted keys are permanently inaccessible
STEWARD_MASTER_PASSWORD=your-strong-master-password-32-chars-min
# PostgreSQL connection string (not needed in embedded mode)
DATABASE_URL=postgres://user:pass@localhost:5432/steward
API Server
STEWARD_PORT=3200 # API port (default: 3200)
STEWARD_BIND_HOST=0.0.0.0 # Bind address
STEWARD_JWT_SECRET=separate-jwt-signing-key # Defaults to master password if not set
STEWARD_PLATFORM_KEY=your-platform-admin-key # For platform-level management
STEWARD_EMBEDDED=false # Set true for PGLite mode
Blockchain
RPC_URL=https://mainnet.base.org # Default EVM chain RPC
CHAIN_ID=8453 # Default chain ID
SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
Authentication
# Passkeys (WebAuthn)
PASSKEY_RP_ID=myapp.com # Relying party ID (your domain)
PASSKEY_ORIGIN=https://myapp.com # Expected origin for ceremonies
PASSKEY_RP_NAME="My App" # Display name in passkey dialogs
# Email magic links
RESEND_API_KEY=re_xxxxxxxxxxxx # Resend.com API key
EMAIL_FROM=auth@myapp.com # Verified sender address
APP_URL=https://myapp.com # Used for magic link URLs
# OAuth providers (optional, enable any combination)
GOOGLE_CLIENT_ID=xxxx.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-xxxx
DISCORD_CLIENT_ID=1234567890
DISCORD_CLIENT_SECRET=xxxx
TWITTER_CLIENT_ID=xxxx
TWITTER_CLIENT_SECRET=xxxx
Database Setup
Run the Drizzle migrations to set up the database schema:
cd packages/db
bun run drizzle-kit push
# Or generate and run migrations
bun run drizzle-kit generate
bun run drizzle-kit migrate
Running the API
# Development
cd packages/api
bun run dev
# Production
bun run start
Verify it’s running:
curl http://localhost:3200/health
# {"ok":true,"timestamp":"2026-03-26T12:00:00Z"}
Creating Your First Tenant
curl -X POST http://localhost:3200/tenants \
-H "Content-Type: application/json" \
-d '{
"id": "my-org",
"name": "My Organization",
"apiKeyHash": "your-api-key"
}'
If you pass a non-hash string as apiKeyHash, Steward will SHA-256 hash it for you. The raw API key is returned once on creation.
Production Deployment
Systemd Service
[Unit]
Description=Steward API
After=network.target postgresql.service
[Service]
Type=simple
User=steward
WorkingDirectory=/opt/steward
ExecStart=/usr/local/bin/bun run packages/api/src/index.ts
Restart=always
RestartSec=5
Environment=NODE_ENV=production
EnvironmentFile=/opt/steward/.env
[Install]
WantedBy=multi-user.target
Reverse Proxy (Nginx)
server {
listen 443 ssl http2;
server_name api.myapp.com;
ssl_certificate /etc/letsencrypt/live/api.myapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.myapp.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3200;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Production Checklist