Deployment¶
Backend¶
Environment Variables¶
# Production
DATABASE_URL=postgresql://user:pass@host:5432/saas_courier
SECRET_KEY=your-production-secret
ENVIRONMENT=production
# Optional
RESEND_API_KEY=your-resend-api-key
AERODATABOX_API_KEY=your-rapidapi-key
Running¶
# Build
uv run pip install -e .
# Run
uv run gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000
Frontend¶
Build¶
Environment¶
Documentation (Cloudflare Workers)¶
Architecture¶
The documentation site uses Cloudflare Workers with KV storage for serving static files.
┌─────────────┐ ┌──────────────────┐ ┌─────────────┐
│ GitHub │────▶│ GitHub Actions │────▶│ Cloudflare │
│ Push │ │ mkdocs build │ │ Workers │
└─────────────┘ │ wrangler deploy│ │ + KV Store│
└──────────────────┘ └─────────────┘
Requirements¶
| Requirement | Version | Notes |
|---|---|---|
| Node.js | >= 22.0.0 | Wrangler 4.x requires Node.js 22+ |
| Python | 3.12 | For mkdocs |
| Wrangler | 4.x | Installed via npm |
GitHub Actions Workflow¶
Location: .github/workflows/deploy-docs.yml
Trigger paths:
on:
push:
branches: [main]
paths:
- 'docs/**'
- 'mkdocs.yml'
- 'wrangler.toml'
- '.github/workflows/**'
- 'workers-site/**'
⚠️ Important: The workers-site/** and .github/workflows/** paths must be included in the trigger configuration. Without these, workflow may not trigger on workers-site changes or workflow modifications.
Wrangler Configuration¶
File: wrangler.toml
name = "saas-courier-docs"
compatibility_date = "2026-05-15"
main = "workers-site/index.js"
compatibility_flags = ["nodejs_compat"]
[[kv_namespaces]]
binding = "ASSETS"
id = "af529f0e7da645da9d39ed407c5a11f2"
[site]
bucket = "./site"
| Field | Description |
|---|---|
main |
Entry point for Cloudflare Workers |
[site] bucket |
Static files output directory (from mkdocs build) |
[kv_namespaces] |
KV namespace for asset caching |
Workers Site¶
The workers-site/index.js file handles KV-based asset serving:
import { getAssetFromKV } from '@cloudflare/kv-asset-handler';
addEventListener('fetch', event => {
event.respondWith(handleEvent(event));
});
async function handleEvent(event) {
try {
return await getAssetFromKV(event);
} catch (e) {
console.error('Error serving asset:', e);
return new Response('Error loading page', { status: 500 });
}
}
Deploy¶
Custom Domain¶
Troubleshooting¶
Wrangler requires Node.js v22.0.0¶
Error:
Solution: Update Node.js version in GitHub Actions workflow:
Build failed - Could not resolve "@cloudflare/kv-asset-handler"¶
Cause: Missing or incorrect workers-site configuration.
Solution: Ensure:
1. workers-site/index.js exists in repository
2. main = "workers-site/index.js" is in wrangler.toml
3. @cloudflare/kv-asset-handler is in package.json dependencies
Workflow not triggering¶
Cause: Missing paths in workflow trigger configuration.
Solution: Ensure trigger includes:
Workers Sites deprecated warning¶
Warning:
Because you've defined a [site] configuration, we're defaulting to "workers-site"
for the deprecated `site.entry-point` field.
This is expected. The workers-site approach is still functional.
Environment Reference¶
| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL connection string |
SECRET_KEY |
JWT signing key (min 32 chars) |
ENVIRONMENT |
development or production |
CORS_ORIGINS |
Allowed origins (comma-separated) |
Backup & Restore¶
PostgreSQL Backup¶
# Backup
docker-compose exec db pg_dump -U courieruser courierflow > backup.sql
# Restore
docker-compose exec -T db psql -U courieruser courierflow < backup.sql
Scaling¶
Horizontal Scaling¶
For multiple API instances:
- Use
REDIS_URLfor shared WebSocket state - Ensure
SECRET_KEYis identical across instances - Use PostgreSQL as shared session store
- Load balancer with sticky sessions for WebSocket
Database Performance¶
-- Add indexes for common queries
CREATE INDEX idx_shipments_tenant_id ON shipments(tenant_id);
CREATE INDEX idx_shipments_tracking_id ON shipments(tracking_id);
CREATE INDEX idx_shipments_status ON shipments(status);
CREATE INDEX idx_sessions_user_id ON sessions(user_id);
Nginx Configuration¶
server {
listen 443 ssl;
server_name api.saas-courier.com;
ssl_certificate /etc/ssl/certs/cert.pem;
ssl_certificate_key /etc/ssl/private/key.pem;
location / {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
}
location /ws {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Ver también: Docker · Backup & Restore · Scaling