Dokku Deployment Guide - Backend Staging¶
This guide covers deploying the Coaching App backend to the Dokku staging server.
π Prerequisites¶
- SSH access to the Dokku server (fenrir)
- Dokku CLI installed on the server
- PostgreSQL and Redis plugins installed on Dokku server
- Domain records configured for backend and frontend:
- coaching-app-backend.kaiju.com.pa
- coaching-app-frontend.kaiju.com.pa
π Initial Setup (One-time)¶
1. Create the Dokku Application¶
2. Provision PostgreSQL Database¶
# Create database
ssh dokku@fenrir postgres:create praxia-coaching-app-backend-staging-db
# Link database to app
ssh dokku@fenrir postgres:link praxia-coaching-app-backend-staging-db praxia-coaching-app-backend-staging
# This automatically sets DATABASE_URL
3. Provision Redis Cache¶
# Create Redis instance
ssh dokku@fenrir redis:create praxia-coaching-app-backend-staging-redis
# Link Redis to app
ssh dokku@fenrir redis:link praxia-coaching-app-backend-staging-redis praxia-coaching-app-backend-staging
# This automatically sets REDIS_URL
4. Configure Storage (Cloudflare R2)¶
Using Cloudflare R2 for asset storage:
# Get credentials from .credentials/staging-r2.md (gitignored)
ssh dokku@fenrir config:set praxia-coaching-app-backend-staging \
STORAGE_LOCATIONS="r2" \
STORAGE_R2_DRIVER="s3" \
STORAGE_R2_KEY="your-r2-access-key-id" \
STORAGE_R2_SECRET="your-r2-secret-access-key" \
STORAGE_R2_BUCKET="coaching-app-staging" \
STORAGE_R2_REGION="auto" \
STORAGE_R2_ENDPOINT="https://your-account-id.r2.cloudflarestorage.com"
Note: Actual credentials are stored in .credentials/staging-r2.md (gitignored for security).
5. Set Environment Variables¶
Generate a secure secret:
Set all required environment variables:
ssh dokku@fenrir config:set praxia-coaching-app-backend-staging \
BETTER_AUTH_SECRET="your-generated-secret-here" \
BETTER_AUTH_URL="https://coaching-app-backend.kaiju.com.pa" \
FRONTEND_URL="https://coaching-app-frontend.kaiju.com.pa" \
PUBLIC_URL="https://coaching-app-backend.kaiju.com.pa" \
NODE_ENV="production" \
PORT="3001" \
CORS_ORIGIN="https://coaching-app-frontend.kaiju.com.pa" \
TRUSTED_ORIGINS="https://coaching-app-backend.kaiju.com.pa,https://coaching-app-frontend.kaiju.com.pa"
FRONTEND_URL is required for activation and password reset email redirects.
If it is missing, users can land on backend URLs that are not user-accessible.
Optional email configuration (for production):
ssh dokku@fenrir config:set praxia-coaching-app-backend-staging \
EMAIL_FROM="noreply@praxia.com" \
EMAIL_TRANSPORT="smtp" \
EMAIL_SMTP_HOST="smtp.sendgrid.net" \
EMAIL_SMTP_PORT="587" \
EMAIL_SMTP_USER="apikey" \
EMAIL_SMTP_PASSWORD="your-sendgrid-api-key"
6. Configure Domain Routing¶
ssh dokku@fenrir domains:clear praxia-coaching-app-backend-staging || true
ssh dokku@fenrir domains:add praxia-coaching-app-backend-staging coaching-app-backend.kaiju.com.pa
7. Configure Dockerfile Builder¶
# Fenrir requires Dockerfile builder for this monorepo setup
ssh dokku@fenrir builder:set praxia-coaching-app-backend-staging selected dockerfile
ssh dokku@fenrir builder-dockerfile:set praxia-coaching-app-backend-staging dockerfile-path backend/Dockerfile
π Deploying Updates¶
Using the Deployment Script¶
The easiest way to deploy is using the provided script:
# Make script executable (first time only)
chmod +x deploy-backend-staging.sh
# Run deployment
./deploy-backend-staging.sh
Manual Deployment¶
# Add Dokku remote (first time only)
git remote add staging-backend dokku@fenrir:praxia-coaching-app-backend-staging
# Deploy current branch
git push staging-backend main
# Or deploy a specific branch
git push staging-backend your-branch:main
π§ Post-Deployment Tasks¶
1. Run Database Migrations (Required)¶
This runs migrations inside the Dokku container using the checked-in SQL files in backend/drizzle/migrations.
2. Bootstrap the First Platform Admin (One-time per fresh DB)¶
curl -s -X POST https://coaching-app-backend.kaiju.com.pa/api/setup/first-run \
-H "Content-Type: application/json" \
-d '{
"email": "hola@jorgeyau.com",
"password": "StrongPassword123!",
"name": "Jorge Yau"
}'
What it does:
- Creates exactly one platform-level admin user
- Self-disables after first success (
409 Already initialized) - Requires database schema to exist first (run migrations before this call)
3. Staging Fixture Seed (optional, development-like data)¶
pnpm db:seed is fixture data for development only. It intentionally refuses to run when
NODE_ENV is staging or production.
If you need fixture-like data in staging, use explicit API/admin flows after bootstrapping the first user.
π Monitoring & Maintenance¶
View Logs¶
# Stream live logs
ssh dokku@fenrir logs praxia-coaching-app-backend-staging -t
# View recent logs
ssh dokku@fenrir logs praxia-coaching-app-backend-staging --tail 100
Check Application Status¶
# App status
ssh dokku@fenrir ps:report praxia-coaching-app-backend-staging
# Database status
ssh dokku@fenrir postgres:info praxia-coaching-app-backend-staging-db
# Redis status
ssh dokku@fenrir redis:info praxia-coaching-app-backend-staging-redis
Access Application Console¶
# Run interactive shell
ssh dokku@fenrir run praxia-coaching-app-backend-staging bash
# Run Node.js REPL
ssh dokku@fenrir run praxia-coaching-app-backend-staging node
Database Operations¶
# Connect to PostgreSQL
ssh dokku@fenrir postgres:connect praxia-coaching-app-backend-staging-db
# Create database backup
ssh dokku@fenrir postgres:export praxia-coaching-app-backend-staging-db > backup-$(date +%Y%m%d).sql
# Import database backup
ssh dokku@fenrir postgres:import praxia-coaching-app-backend-staging-db < backup.sql
π Rollback¶
Rollback to Previous Deployment¶
# List deployments
ssh dokku@fenrir releases praxia-coaching-app-backend-staging
# Rollback to previous release
ssh dokku@fenrir ps:rollback praxia-coaching-app-backend-staging
Deploy Specific Commit¶
π Troubleshooting¶
Application Won't Start¶
# Check logs for errors
ssh dokku@fenrir logs praxia-coaching-app-backend-staging
# Check configuration
ssh dokku@fenrir config praxia-coaching-app-backend-staging
# Restart application
ssh dokku@fenrir ps:restart praxia-coaching-app-backend-staging
Database Connection Issues¶
# Verify database link
ssh dokku@fenrir postgres:links praxia-coaching-app-backend-staging-db
# Check database is running
ssh dokku@fenrir postgres:info praxia-coaching-app-backend-staging-db
# Reconnect database
ssh dokku@fenrir postgres:unlink praxia-coaching-app-backend-staging-db praxia-coaching-app-backend-staging
ssh dokku@fenrir postgres:link praxia-coaching-app-backend-staging-db praxia-coaching-app-backend-staging
Build Failures¶
# Check build logs
ssh dokku@fenrir logs praxia-coaching-app-backend-staging --build
# Clear build cache
ssh dokku@fenrir repo:purge-cache praxia-coaching-app-backend-staging
# Rebuild application
git commit --allow-empty -m "Trigger rebuild"
git push staging-backend main
Out of Memory¶
# Check resource usage
ssh dokku@fenrir ps:report praxia-coaching-app-backend-staging
# Increase memory limit (if supported)
ssh dokku@fenrir docker-options:add praxia-coaching-app-backend-staging deploy "--memory=1g"
# Restart with new settings
ssh dokku@fenrir ps:restart praxia-coaching-app-backend-staging
Schema Changes Not Appearing¶
With Drizzle ORM, all schema changes go through migrations. Never run raw SQL against the schema tables.
# 1. Add a new migration locally when schema changes:
# cd backend && pnpm db:generate
# Commit the migration file, redeploy, then run the command above
# 2. Apply migrations inside Dokku after each deployment
ssh dokku@fenrir run praxia-coaching-app-backend-staging node backend/dist/scripts/migrate.js
# 3. Restart if needed (usually not required after successful deploy)
ssh dokku@fenrir ps:restart praxia-coaching-app-backend-staging
Proper workflow for schema changes:
- Edit
backend/src/db/schema/files - Run
pnpm db:generateto create a migration - Commit migration file to git
- Deploy and run
ssh dokku@fenrir run praxia-coaching-app-backend-staging node backend/dist/scripts/migrate.js
Sample Users Missing¶
If test users aren't available for development:
# Re-run seed
DATABASE_URL="$(ssh dokku@fenrir config:get praxia-coaching-app-backend-staging DATABASE_URL)" pnpm --filter @coaching-app/backend db:seed
# Verify users via API
curl -s https://coaching-app-backend.kaiju.com.pa/api/health
Expected sample users:
- platformadmin@coaching.test (Platform Admin)
- orgadmin@coaching.test (Org Admin)
- manager@coaching.test (Manager)
- coach@coaching.test (Coach)
- teacher@coaching.test (Teacher)
Migration Conflicts¶
If you see "column already exists" or migration errors:
# Check current migration status
DATABASE_URL="$(ssh dokku@fenrir config:get praxia-coaching-app-backend-staging DATABASE_URL)" pnpm --filter @coaching-app/backend db:migrate
# If conflicts exist, you may need to reset (β οΈ THIS DESTROYS DATA)
# 1. Backup first!
ssh dokku@fenrir postgres:export praxia-coaching-app-backend-staging-db > emergency-backup.sql
# 2. Drop and recreate database
ssh dokku@fenrir postgres:unlink praxia-coaching-app-backend-staging-db praxia-coaching-app-backend-staging
ssh dokku@fenrir postgres:destroy praxia-coaching-app-backend-staging-db
ssh dokku@fenrir postgres:create praxia-coaching-app-backend-staging-db
ssh dokku@fenrir postgres:link praxia-coaching-app-backend-staging-db praxia-coaching-app-backend-staging
# 3. Run migrations
DATABASE_URL="$(ssh dokku@fenrir config:get praxia-coaching-app-backend-staging DATABASE_URL)" pnpm --filter @coaching-app/backend db:migrate
# 4. Seed data
DATABASE_URL="$(ssh dokku@fenrir config:get praxia-coaching-app-backend-staging DATABASE_URL)" pnpm --filter @coaching-app/backend db:seed
π Security Checklist¶
- Strong
BETTER_AUTH_SECRETset (32+ chars) - SSL/TLS enabled via Cloudflare
- CORS properly configured for your frontend domain
- Database backups configured
- Firewall rules configured on server
- Environment variables stored securely (not in git)
- Email templates customized with branding
π Environment Variables Reference¶
Required¶
| Variable | Description | Example |
|---|---|---|
BETTER_AUTH_SECRET |
Better-auth signing secret | (generated random string) |
BETTER_AUTH_URL |
Public URL of the API | https://coaching-app-backend.kaiju.com.pa |
PUBLIC_URL |
Public URL of the API | https://coaching-app-backend.kaiju.com.pa |
DATABASE_URL |
PostgreSQL connection string | (auto-set by postgres:link) |
REDIS_URL |
Redis connection string | (auto-set by redis:link) |
Optional¶
| Variable | Description | Default |
|---|---|---|
NODE_ENV |
Node environment | production |
LOG_LEVEL |
Logging level | info |
CORS_ENABLED |
Enable CORS | false |
CORS_ORIGIN |
Allowed CORS origins | * |
EMAIL_FROM |
Email sender address | - |
EMAIL_TRANSPORT |
Email transport method | smtp |
RATE_LIMITER_ENABLED |
Enable rate limiting | false |
RATE_LIMITER_POINTS |
Rate limit points | 50 |
RATE_LIMITER_DURATION |
Rate limit duration (seconds) | 60 |
Frontend Deployment¶
This section covers deploying the Expo web frontend to Dokku staging.
π Prerequisites¶
- Backend API deployed and accessible
- Dockerfile builder available on Dokku server
- Domain name configured (optional but recommended)
π Initial Setup (One-time)¶
1. Create the Dokku Application¶
2. Set Environment Variables¶
Set the API URL to point to your backend:
ssh dokku@fenrir config:set praxia-coaching-app-frontend-staging \
EXPO_PUBLIC_API_URL="https://coaching-app-backend.kaiju.com.pa" \
NODE_ENV="production" \
EXPO_PUBLIC_API_TIMEOUT="30000"
3. Configure Domain (Optional)¶
# Add domain
ssh dokku@fenrir domains:add praxia-coaching-app-frontend-staging coaching-app-frontend.kaiju.com.pa
# Note: SSL is handled by Cloudflare
4. Configure Build Settings¶
ssh dokku@fenrir builder:set praxia-coaching-app-frontend-staging selected dockerfile
ssh dokku@fenrir builder-dockerfile:set praxia-coaching-app-frontend-staging dockerfile-path frontend/Dockerfile
ssh dokku@fenrir ps:set praxia-coaching-app-frontend-staging procfile-path frontend/Procfile
5. Pin Frontend Port Mapping (Zero Trust)¶
The frontend container starts Expo on port 3000 (expo start --port 3000 --web).
When using Cloudflare Zero Trust origins, pin Dokku mapping to container port 3000.
# Keep the existing Dokku proxy port, but route it to container port 3000
DOKKU_PROXY_PORT="$(ssh dokku@fenrir config:get praxia-coaching-app-frontend-staging DOKKU_PROXY_PORT)"
ssh dokku@fenrir ports:set praxia-coaching-app-frontend-staging http:${DOKKU_PROXY_PORT}:3000
π Deploying Updates¶
Using the Deployment Script¶
# Make script executable (first time only)
chmod +x deploy-frontend-staging.sh
# Run deployment
./deploy-frontend-staging.sh
Manual Deployment¶
# Add Dokku remote (first time only)
git remote add staging-frontend dokku@fenrir:praxia-coaching-app-frontend-staging
# Deploy current branch
git push staging-frontend main
# Or deploy a specific branch
git push staging-frontend your-branch:main
ποΈ Build Process¶
The frontend deployment follows these steps:
- Install Dependencies: Yarn installs all workspace dependencies
- Build Expo Web:
expo export:webcreates optimized static files - Serve: Dokku reads
frontend/Procfile, which runs Expo through the frontend web script (pnpm start:web)
The build output is stored in dist-web/ and served on the configured port.
π Monitoring & Maintenance¶
View Logs¶
# Stream live logs
ssh dokku@fenrir logs praxia-coaching-app-frontend-staging -t
# View recent logs
ssh dokku@fenrir logs praxia-coaching-app-frontend-staging --tail 100
Check Application Status¶
# App status
ssh dokku@fenrir ps:report praxia-coaching-app-frontend-staging
# View app URL
ssh dokku@fenrir urls praxia-coaching-app-frontend-staging
Update Environment Variables¶
# Update API URL
ssh dokku@fenrir config:set praxia-coaching-app-frontend-staging \
EXPO_PUBLIC_API_URL="https://new-backend-url.com"
# Restart to apply changes
ssh dokku@fenrir ps:restart praxia-coaching-app-frontend-staging
π Rollback¶
# List deployments
ssh dokku@fenrir releases praxia-coaching-app-frontend-staging
# Rollback to previous release
ssh dokku@fenrir ps:rollback praxia-coaching-app-frontend-staging
π Troubleshooting¶
Build Failures¶
# Check build logs
ssh dokku@fenrir logs praxia-coaching-app-frontend-staging --build
# Clear build cache
ssh dokku@fenrir repo:purge-cache praxia-coaching-app-frontend-staging
# Rebuild
git commit --allow-empty -m "Trigger rebuild"
git push staging-frontend main
App Not Loading¶
- Check that environment variables are set correctly:
- Verify backend API is accessible:
- Check browser console for errors
Cloudflare Zero Trust 502 / Bad Gateway¶
If backend works but frontend returns Bad Gateway via Zero Trust, check for a Dokku port mismatch. We hit this when frontend was mapped to container port 8081 while Expo was actually listening on 3000.
# Inspect current mapping and process logs
ssh dokku@fenrir ports:report praxia-coaching-app-frontend-staging
ssh dokku@fenrir logs praxia-coaching-app-frontend-staging --num 120
# Correct mapping: existing proxy port -> container port 3000
DOKKU_PROXY_PORT="$(ssh dokku@fenrir config:get praxia-coaching-app-frontend-staging DOKKU_PROXY_PORT)"
ssh dokku@fenrir ports:set praxia-coaching-app-frontend-staging http:${DOKKU_PROXY_PORT}:3000
# Verify
ssh dokku@fenrir ports:report praxia-coaching-app-frontend-staging
Expo Web Build Issues¶
If the Expo web build fails:
# SSH into the build container
ssh dokku@fenrir run praxia-coaching-app-frontend-staging bash
# Try building manually to see detailed errors
cd frontend
npx expo export:web --output-dir dist-web
π Frontend Environment Variables Reference¶
| Variable | Description | Example |
|---|---|---|
EXPO_PUBLIC_API_URL |
Backend API URL | https://coaching-app-backend.kaiju.com.pa |
EXPO_PUBLIC_API_TIMEOUT |
API timeout (ms) | 30000 |
NODE_ENV |
Node environment | production |
π Useful Links¶
- Dokku Documentation
- Hono Documentation
- Drizzle ORM Docs
- PostgreSQL Plugin
- Redis Plugin
- Cloudflare SSL
π Support¶
If you encounter issues:
- Check the logs:
ssh dokku@fenrir logs praxia-coaching-app-backend-staging -t - Review the configuration:
ssh dokku@fenrir config praxia-coaching-app-backend-staging - Consult the troubleshooting section above
- Contact the DevOps team