Skip to content

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

ssh dokku@fenrir apps:create praxia-coaching-app-backend-staging

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:

openssl rand -base64 32  # Use for BETTER_AUTH_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)

ssh dokku@fenrir run praxia-coaching-app-backend-staging node backend/dist/scripts/migrate.js

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

# Deploy a specific git commit
git push dokku-staging <commit-hash>:main

πŸ› 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:

  1. Edit backend/src/db/schema/ files
  2. Run pnpm db:generate to create a migration
  3. Commit migration file to git
  4. 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_SECRET set (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

ssh dokku@fenrir apps:create praxia-coaching-app-frontend-staging

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:

  1. Install Dependencies: Yarn installs all workspace dependencies
  2. Build Expo Web: expo export:web creates optimized static files
  3. 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

  1. Check that environment variables are set correctly:
ssh dokku@fenrir config praxia-coaching-app-frontend-staging
  1. Verify backend API is accessible:
curl https://coaching-app-backend.kaiju.com.pa/server/health
  1. 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

πŸ†˜ Support

If you encounter issues:

  1. Check the logs: ssh dokku@fenrir logs praxia-coaching-app-backend-staging -t
  2. Review the configuration: ssh dokku@fenrir config praxia-coaching-app-backend-staging
  3. Consult the troubleshooting section above
  4. Contact the DevOps team