Appearance
Deployment Guide
Deploy AvailEngine to production. This guide covers the full stack: FastAPI backend, React dashboard, and VitePress docs.
Architecture
┌─────────────────────────────────────────────┐
│ Internet │
│ api.availengine.com → FastAPI (port 8000) │
│ docs.availengine.com → VitePress static │
└─────────────────────────────────────────────┘
│
├── Supabase (Postgres + Auth)
├── Stripe (payments)
├── Redis (rate limiting)
├── Resend (email)
└── Brevo (transactional email)The FastAPI server also serves the React dashboard SPA at /. In production, you can either:
- Serve everything from one domain (API + dashboard at
api.availengine.com) - Split API (
api.availengine.com) and docs (docs.availengine.com)
Prerequisites
- Supabase project (free tier works for development)
- Stripe account (test mode during development)
- Redis instance (Upstash, Redis Cloud, or self-hosted)
- Resend API key (for email notifications)
- Domain name (for production)
Environment Variables
Copy backend/.env.example to backend/.env and fill in every variable:
bash
# ── Environment ──────────────────────────────────────────
ENVIRONMENT=production # production | development
APP_BASE_URL=https://api.availengine.com
API_BASE_URL=https://api.availengine.com/v1
CORS_ORIGINS=["https://app.yourapp.com"]
# ── Supabase ─────────────────────────────────────────────
SUPABASE_URL=https://xxxxx.supabase.co
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOi...
SUPABASE_ANON_KEY=eyJhbGciOi...
DATABASE_URL=postgresql://postgres:...@db.xxxxx.supabase.co:5432/postgres
# ── Stripe ───────────────────────────────────────────────
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PRICE_STARTER=price_...
STRIPE_PRICE_GROWTH=price_...
STRIPE_PRICE_SCALE=price_...
# ── Redis ────────────────────────────────────────────────
REDIS_URL=redis://default:password@host:port
# ── Email ────────────────────────────────────────────────
RESEND_API_KEY=re_...
FROM_EMAIL=bookings@availengine.com
BREVO_API_KEY=xkeysib-...
BREVO_SENDER_EMAIL=bookings@availengine.com
# ── Google Calendar (optional) ───────────────────────────
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
GOOGLE_REDIRECT_URI=https://api.availengine.com/integrations/google/callback
CALENDAR_ENCRYPTION_KEY=...Option 1: Docker (Recommended)
Dockerfile
dockerfile
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
# Install Node.js for dashboard build
RUN apt-get update && apt-get install -y curl && \
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get install -y nodejs && \
apt-get clean
# Python dependencies
COPY backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Build dashboard
COPY dashboard/ dashboard/
RUN cd dashboard && npm install && npm run build
# Copy backend
COPY backend/ backend/
COPY docs/ docs/
EXPOSE 8000
CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000"]docker-compose.yml
yaml
version: '3.8'
services:
api:
build: .
ports:
- "8000:8000"
env_file:
- backend/.env
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
redis:
image: redis:7-alpine
ports:
- "6379:6379"
restart: unless-stopped
volumes:
- redis_data:/data
volumes:
redis_data:Deploy
bash
docker-compose up -dThe API is live at http://localhost:8000. The dashboard is served at /. Swagger docs at /docs.
Option 2: Render
- Create a Web Service on Render
- Connect your GitHub repo
- Build Command:
cd dashboard && npm install && npm run build && cd .. - Start Command:
uvicorn backend.main:app --host 0.0.0.0 --port $PORT - Add all environment variables from the list above
- Add a Redis service (Render has managed Redis)
- Deploy
Option 3: Railway
- Create a new project on Railway
- Add your repo
- Add a Redis plugin
- Add environment variables
- Railway auto-detects the Python project and builds
Option 4: DigitalOcean App Platform
- Create an App on DigitalOcean
- Connect your repo
- Set build command:
cd dashboard && npm install && npm run build - Set run command:
uvicorn backend.main:app --host 0.0.0.0 --port $PORT - Add a managed Redis database
- Add environment variables
Docs Deployment
The VitePress docs are static HTML. Deploy to any static host:
Vercel
bash
cd docs
npm install
npx vitepress build
# Deploy docs/.vitepress/dist to VercelSet docs.availengine.com as the custom domain.
Netlify
- Connect repo to Netlify
- Base directory:
docs - Build command:
npm install && npx vitepress build - Publish directory:
docs/.vitepress/dist
Domain & SSL
DNS Records
api.availengine.com A <server-ip>
docs.availengine.com CNAME <vercel/netlify-url>SSL
- Render/Railway/DigitalOcean: SSL is auto-provisioned
- Self-hosted: Use Caddy or nginx + certbot
- Vercel/Netlify: SSL is automatic
Caddy (Self-Hosted)
api.availengine.com {
reverse_proxy localhost:8000
}Caddy auto-provisions and renews Let's Encrypt certificates.
Stripe Webhook Setup
After deploying, configure Stripe webhooks:
- Go to Stripe Dashboard > Webhooks
- Endpoint URL:
https://api.availengine.com/webhooks/stripe - Select events (see Stripe Setup for the full list)
- Copy the signing secret → set as
STRIPE_WEBHOOK_SECRETenv var
Verify Webhooks Work
bash
curl https://api.availengine.com/health
# → {"status": "ok"}
curl https://api.availengine.com/status
# → {"status": "ok", "version": "2.0.0", "components": {"database": "healthy", "stripe": "healthy"}}Post-Deployment Checklist
- [ ] All environment variables set (no defaults in production)
- [ ]
ENVIRONMENT=productionset - [ ] Stripe webhooks configured and verified
- [ ] Stripe products/prices created (Starter, Growth, Scale)
- [ ] CORS origins set to your frontend domain(s)
- [ ] API key prefix
avail_live_verified in the database - [ ] Rate limiting tested (default: 60 RPM per key)
- [ ] Idempotency keys working (send duplicate POST with same key)
- [ ] Billing enforcement active (test with past-due simulation)
- [ ] Usage tracking writing to
api_usagetable - [ ] SSL certificate active and auto-renewing
- [ ] DNS propagated (verify with
curl -I https://api.availengine.com/health) - [ ] Supabase RLS policies verified (test with a scoped API key)
- [ ] Email delivery working (test booking confirmation email)
- [ ] Google Calendar OAuth configured (if using)
- [ ] Docs site deployed and accessible
- [ ] Dashboard SPA loads at
/
Scaling
Vertical Scaling
Increase server resources. A 2 GB RAM / 2 vCPU instance handles ~500 concurrent bookings comfortably.
Horizontal Scaling
Run multiple API instances behind a load balancer. Ensure:
- Redis is external (not per-instance) — rate limiting and idempotency use it
- Stripe webhooks go to a single endpoint (or use a queue)
- Supabase handles connection pooling automatically
Database
- Supabase free tier: 500 MB database, good for development and early production
- Supabase Pro ($25/month): 8 GB, daily backups, point-in-time recovery
- Connection pooling: PgBouncer is built into Supabase — use port 6543 for pooled connections
Redis
- Upstash free tier: 256 MB, good for rate limiting
- Redis Cloud free tier: 30 MB
- For 10k+ RPM, 256 MB is sufficient
Monitoring
Health Endpoints
GET /health— simple liveness checkGET /status— component health (DB, Stripe)
Logging
Structured JSON logs are written to stdout. In production, ship them to your logging platform:
- Render: Logs appear in the dashboard
- Railway: Built-in log viewer
- Datadog/New Relic: Use their Python agent
- Self-hosted: Pipe to
journaldor a log file
Alerting
Set up alerts for:
/statusreturningdegraded(DB or Stripe down)- 5xx error rate spike
- Stripe webhook failures (check Stripe Dashboard > Webhooks > Events)
- Redis connection failures (rate limiting stops working)
Troubleshooting
500 Errors
- Check logs:
docker-compose logs api - Verify all env vars are set
- Check Supabase connection:
curl https://xxxxx.supabase.co/rest/v1/withapikeyheader - Check Redis connection:
redis-cli -u $REDIS_URL ping
Stripe Webhooks Failing
- Go to Stripe Dashboard > Webhooks > Events
- Check the response code and body
- Verify
STRIPE_WEBHOOK_SECRETmatches the dashboard signing secret - Verify the endpoint URL is reachable from the internet
CORS Errors
- Check
CORS_ORIGINSenv var — it's a JSON array of allowed origins - Must include the protocol:
["https://app.yourapp.com"] - Wildcards (
*) don't work with credentials
Rate Limiting Not Working
- Verify
REDIS_URLis set and Redis is reachable - Check that
rate_limit_rpmis set on API keys (default: 60) - The
Retry-Afterheader in 429 responses tells you how long to wait