Skip to main content

Morphee Production Deployment with Coolify

Complete step-by-step guide to deploy Morphee v1.0 to production using Coolify

Status: ✅ Production-ready Last Updated: February 13, 2026 Estimated Time: 12-16 hours (over 2-3 days)


Table of Contents

  1. Overview
  2. Prerequisites
  3. Phase 1: Server Setup
  4. Phase 2: Domain Configuration
  5. Phase 3: Database & Auth
  6. Phase 5: Backend Deployment
  7. Phase 6: Verify Deployment
  8. Phase 7: Monitoring (Optional)
  9. Phase 8: Backups
  10. Phase 9: Testing
  11. Troubleshooting
  12. Cost Breakdown

Overview

What is Coolify?

Coolify is an open-source, self-hosted Platform-as-a-Service (PaaS) alternative to Heroku, Vercel, and Netlify. It provides:

  • Docker Compose native support — deploy Morphee's multi-service architecture easily
  • Built-in SSL via Let's Encrypt (automatic HTTPS)
  • Built-in reverse proxy (Traefik) — routes traffic to your apps
  • Environment variable management — secure secrets storage
  • Zero-downtime deployments — rolling updates with health checks
  • Git integration — auto-deploy on push to main
  • Web UI — manage everything from a dashboard

Why Coolify for Morphee?

  • Cost-effective: $17-42/mo vs $100+ on AWS
  • Docker Compose ready: Morphee already has docker-compose.yml
  • Easy migration: Move to Kubernetes later if needed
  • Open source: No vendor lock-in

Prerequisites

Required

  • Domain namemorphee.app (or your domain)
    • Registered and accessible
    • DNS managed via Cloudflare, DigitalOcean, or Route53
  • LLM API key — Anthropic (recommended) or OpenAI
  • GitHub account — for repository access
    • Morphee repo: https://github.com/your-org/morphee-beta
  • Email account — for Coolify admin + Let's Encrypt
  • Supabase account (free) — for managed PostgreSQL + Auth
  • Server provider account — Hetzner (cheapest), DigitalOcean (easiest), or AWS
  • Payment method — credit card for server billing

Optional

  • 🔧 AWS account — for S3 offsite backups
  • 🔧 Google OAuth credentials — for Calendar/Gmail integrations
  • 🔧 Firebase account — for mobile push notifications
  • 🔧 Apple Developer account — for iOS push notifications

Phase 1: Server Setup (1-2 hours)

1.1 Choose Server Provider

Recommended: Hetzner Cloud (best price/performance)

SpecsPriceUse Case
CPX21 — 3 vCPU, 4GB RAM, 80GB SSD€8.19/mo (~$9/mo)Small (100-500 users)
CPX31 — 4 vCPU, 8GB RAM, 160GB SSD€16.09/mo (~$17/mo)Medium (500-2000 users)

Alternative: DigitalOcean

SpecsPriceUse Case
Basic — 2 vCPU, 4GB RAM, 80GB SSD$24/moSmall
Basic — 2 vCPU, 8GB RAM, 160GB SSD$48/moMedium

1.2 Create Server

Hetzner Example:

  1. Go to https://console.hetzner.cloud
  2. Create new project: "Morphee Production"
  3. Add server:
    • Location: Closest to your users (e.g., Ashburn, VA for US)
    • Image: Ubuntu 22.04 LTS
    • Type: CPX31 (4 vCPU, 8GB RAM) — recommended
    • Networking: Enable IPv4 + IPv6
    • SSH keys: Add your public key (~/.ssh/id_rsa.pub)
    • Name: morphee-prod
  4. Create server (takes ~60 seconds)
  5. Copy server IP address: YOUR_SERVER_IP

DigitalOcean Example:

  1. Go to https://cloud.digitalocean.com
  2. Create Droplet:
    • Image: Ubuntu 22.04 LTS
    • Plan: Basic, $48/mo (2 vCPU, 8GB RAM, 160GB SSD)
    • Datacenter: Closest to users
    • Authentication: SSH keys (recommended)
    • Hostname: morphee-prod
  3. Create Droplet
  4. Copy IP address

1.3 Initial Server Configuration

SSH into server:

ssh root@YOUR_SERVER_IP

Update system:

apt update && apt upgrade -y
apt install -y curl wget git vim ufw fail2ban

Configure firewall:

# Allow SSH
ufw allow 22/tcp

# Allow HTTP/HTTPS (for Coolify)
ufw allow 80/tcp
ufw allow 443/tcp

# Allow Coolify dashboard (port 8000)
ufw allow 8000/tcp

# Enable firewall
ufw --force enable

# Verify
ufw status

Optional: Create non-root user

# Create user
adduser morphee
usermod -aG sudo morphee

# Add SSH key for new user
mkdir -p /home/morphee/.ssh
cp /root/.ssh/authorized_keys /home/morphee/.ssh/
chown -R morphee:morphee /home/morphee/.ssh
chmod 700 /home/morphee/.ssh
chmod 600 /home/morphee/.ssh/authorized_keys

# Test login (from local machine)
ssh morphee@YOUR_SERVER_IP

# Disable root SSH login (security best practice)
sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
systemctl restart ssh

1.4 Install Coolify

One-line installer:

curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash

This installs:

  • Docker Engine (latest)
  • Docker Compose (v2)
  • Coolify server (runs on port 8000)
  • Traefik reverse proxy (for routing + SSL)
  • PostgreSQL (Coolify's database)
  • Redis (Coolify's cache)

Installation takes ~5-10 minutes.

Expected output:

✅ Docker installed
✅ Docker Compose installed
✅ Coolify installed
✅ Traefik proxy started
✅ Coolify server started

🎉 Coolify is now running!

Access dashboard: http://YOUR_SERVER_IP:8000

Verify installation:

docker ps

You should see containers:

  • coolify
  • coolify-db
  • coolify-redis
  • coolify-proxy (Traefik)

1.5 Access Coolify Dashboard

  1. Open browser: http://YOUR_SERVER_IP:8000
  2. First-time setup wizard:
    • Email: your-email@example.com (for Let's Encrypt SSL notifications)
    • Username: admin (or your preferred username)
    • Password: Create strong password (min 12 characters)
    • Root Domain: morphee.app (your domain)
  3. Click Complete Setup
  4. You'll be redirected to Coolify dashboard

Security: Restrict Dashboard Access

After setup, restrict Coolify dashboard to your IP only:

# On server
sudo ufw delete allow 8000/tcp
sudo ufw allow from YOUR_HOME_IP to any port 8000 proto tcp

# Verify
sudo ufw status

Access dashboard via SSH tunnel instead:

# From local machine
ssh -L 8000:localhost:8000 morphee@YOUR_SERVER_IP

# Then open: http://localhost:8000

Phase 2: Domain Configuration (30 minutes)

2.1 DNS Records

You need 3 subdomains:

  1. api.morphee.app — Backend API
  2. app.morphee.app — Frontend web app
  3. monitoring.morphee.app — Grafana (optional)

Option A: Individual A records

TypeNameValueTTL
AapiYOUR_SERVER_IP300
AappYOUR_SERVER_IP300
AmonitoringYOUR_SERVER_IP300
AwwwYOUR_SERVER_IP300
A@ (root)YOUR_SERVER_IP300

Option B: Wildcard (simpler)

TypeNameValueTTL
A*YOUR_SERVER_IP300
A@YOUR_SERVER_IP300

Cloudflare Example:

  1. Go to https://dash.cloudflare.com
  2. Select your domain (morphee.app)
  3. Go to DNSRecords
  4. Click Add record
  5. Add records above
  6. Click Save

Verify DNS propagation:

# Test from local machine
dig api.morphee.app +short
# Should return: YOUR_SERVER_IP

dig app.morphee.app +short
# Should return: YOUR_SERVER_IP

DNS propagation takes 5-60 minutes (usually ~5 min with Cloudflare).

2.2 SSL Certificates

Coolify uses Let's Encrypt for automatic SSL certificates.

Wildcard SSL setup (recommended):

  1. Go to Coolify → SettingsSSL
  2. Choose DNS Provider: Cloudflare (or your provider)
  3. Add API Token:
    • Cloudflare: Go to My ProfileAPI TokensCreate Token
    • Use template: Edit zone DNS
    • Permissions: Zone.DNS.Edit for morphee.app
    • Copy token
  4. Paste token in Coolify
  5. Click Request Wildcard Certificate
  6. Coolify will request *.morphee.app cert via DNS-01 challenge
  7. Wait ~2 minutes for certificate issuance

Verify SSL:

curl -I https://api.morphee.app
# Should return "HTTP/2 200" (after backend is deployed)

Phase 3: Database & Auth (Automatic)

With docker-compose.coolify.yml, PostgreSQL and GoTrue are self-hosted in the same stack. No external Supabase account needed.

Everything is automatic on first deploy:

  • PostgreSQL 15 with pgvector starts with auto-generated password (SERVICE_PASSWORD_POSTGRES)
  • Migrations run automatically via the migrations one-shot container (tracks applied migrations in _migrations table, safe to re-run)
  • GoTrue connects to PostgreSQL internally, uses auto-generated JWT secret shared with backend
  • No manual setup — no psql, no CREATE EXTENSION, no migration scripts to run

SMTP Configuration (required for email confirmation)

GoTrue sends confirmation emails on signup. You need an SMTP provider:

ProviderFree TierSetup
Resend3,000 emails/moSet SMTP_HOST=smtp.resend.com, SMTP_USER=resend, SMTP_PASS=re_...
Mailgun1,000 emails/moSet SMTP_HOST=smtp.mailgun.org, user + pass from Mailgun dashboard
SendGrid100 emails/daySet SMTP_HOST=smtp.sendgrid.net, SMTP_USER=apikey, SMTP_PASS=SG...

Set these in Coolify UI:

SMTP_HOST=smtp.resend.com
SMTP_PORT=587
SMTP_USER=resend
SMTP_PASS=re_your_api_key
SMTP_FROM=noreply@morphee.app

Quick start without SMTP: Set GOTRUE_MAILER_AUTOCONFIRM=true to skip email confirmation (not recommended for production).

SSO Providers (optional)

To enable Google/Apple/Microsoft login, set in Coolify UI:

# Google SSO
GOOGLE_SSO_ENABLED=true
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-your-client-secret
GOTRUE_GOOGLE_REDIRECT_URI=http://supabase-auth:9999/callback

# Apple SSO
APPLE_SSO_ENABLED=true
APPLE_CLIENT_ID=app.morphee.mobile
APPLE_CLIENT_SECRET=your-apple-secret

# Microsoft / Azure AD SSO
AZURE_SSO_ENABLED=true
AZURE_CLIENT_ID=your-azure-client-id
AZURE_CLIENT_SECRET=your-azure-secret

Optional: External Supabase Database

If you prefer managed PostgreSQL (automatic backups, point-in-time recovery), use Supabase Cloud ($25/mo) instead:

  1. Create a project at https://supabase.com/dashboard
  2. In Coolify UI, override these variables:
    • DATABASE_URL → Supabase connection string
    • SUPABASE_AUTH_URLhttps://[PROJECT_ID].supabase.co/auth/v1
    • SUPABASE_JWT_SECRET → from Supabase Settings → API
  3. Remove or stop the supabase-db, supabase-auth, and migrations services in Coolify

Phase 5: Backend Deployment (2 hours)

5.1 Create Application in Coolify

  1. Go to Coolify → Applications → New Application

  2. Select deployment type:

    • Choose Docker Compose
    • Morphee has a Coolify-specific compose file
  3. Connect Git repository:

    • Source: GitHub
    • Repository: https://github.com/your-org/morphee-beta
    • Branch: main
    • Compose file path: docker-compose.coolify.yml (at root)
    • Click Connect
  4. Assign domains to services:

    • frontend: https://app.morphee.app
    • backend: https://api.morphee.app:8000
    • Coolify's Traefik proxy routes traffic automatically — no port mapping needed

5.2 Configure Environment Variables

In Coolify → Application → Environment, set the required variables.

Auto-generated secrets (do NOT set manually):

Coolify auto-generates and persists these across redeployments:

VariablePurpose
SERVICE_PASSWORD_POSTGRESPostgreSQL password
SERVICE_PASSWORD_REDISRedis authentication

Required variables (set in Coolify UI):

# Secrets (generate ONCE, never change — see commands in compose file header)
JWT_SECRET=<base64-secret> # Generate: openssl rand -base64 32
ENCRYPTION_KEY=<fernet-key> # Generate: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

# URLs
VITE_API_URL=https://api.morphee.app
VITE_WS_URL=wss://api.morphee.app
CORS_ORIGINS=https://app.morphee.app,tauri://localhost,http://tauri.localhost,https://tauri.localhost
FRONTEND_URL=https://app.morphee.app

# LLM (at least one required for chat)
ANTHROPIC_API_KEY=sk-ant-api03-YOUR_KEY_HERE

Generate the Fernet encryption key (run once):

python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

Optional variables:

# SMTP (strongly recommended — see Phase 3 for providers)
SMTP_HOST=smtp.resend.com
SMTP_PORT=587
SMTP_USER=resend
SMTP_PASS=re_your_api_key
SMTP_FROM=noreply@morphee.app

# Google OAuth (Calendar/Gmail integrations)
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-your-client-secret
GOOGLE_REDIRECT_URI=https://api.morphee.app/api/oauth/google/callback

# Push Notifications
APNS_KEY_ID=your-apns-key-id
APNS_TEAM_ID=your-apple-team-id
APNS_BUNDLE_ID=app.morphee.mobile
FCM_PROJECT_ID=your-firebase-project-id

Important:

  • Click the lock icon next to sensitive vars (API keys, encryption key) — encrypts them at rest
  • Mark VITE_* variables as Build Variables — they are baked into the JS bundle at build time, not runtime

5.3 Persistent Volumes

The docker-compose.coolify.yml uses Coolify's is_directory: true directive to auto-create host directories on first deploy. No manual directory creation is needed.

VolumeContainer PathPurpose
./data/memory/data/memoryGit-backed memory repos (per group)
./data/files/data/filesSandboxed file storage (per group)
redis-data/dataRedis AOF persistence

These are bind mounts relative to Coolify's deployment directory. Coolify creates the host directories automatically.

5.4 Deploy Backend

  1. Click "Deploy" button in Coolify
  2. Monitor build logs:
    • Coolify will:
      • Clone repo
      • Build Docker image (using Dockerfile.backend)
      • Start Redis container
      • Start backend container
      • Run health check
  3. Wait for "Deployed" status (green checkmark)
  4. Check logs:
    • Coolify → Application → Logs

    • Look for:

      INFO:     Started server process
      INFO: Uvicorn running on http://0.0.0.0:8000
      INFO: Application startup complete

5.5 Verify Backend Deployment

Test health endpoint:

curl https://api.morphee.app/health
# Expected: {"status":"healthy","version":"2.0.0"}

Test API docs (if enabled):

curl https://api.morphee.app/docs
# Should return HTML with FastAPI Swagger UI

Test database connection:

# SSH into server
ssh morphee@YOUR_SERVER_IP

# Exec into backend container
docker exec -it morphee-backend bash

# Test database
python -c "
import asyncio
from db.client import get_db

async def check():
db = get_db()
await db.initialize()
health = await db.health_check()
print('Database health:', health)

asyncio.run(check())
"

# Exit container
exit

5.6 Troubleshooting Backend

Issue: Build fails

# Check build logs in Coolify UI
# Common issues:
# - Missing requirements in requirements.txt
# - Python 3.12 base image pull failure
# - Out of disk space

# Fix: SSH into server, check disk
df -h

# Clean up old Docker images if needed
docker system prune -a

Issue: Health check fails

# Check backend logs
docker logs morphee-backend -f

# Common issues:
# - DATABASE_URL not set or incorrect
# - Redis not reachable
# - Port 8000 already in use

# Verify environment variables
docker exec morphee-backend env | grep DATABASE_URL

Issue: 502 Bad Gateway

# Backend is not listening on port 8000
# Check backend logs for errors
docker logs morphee-backend --tail 50

# Verify backend is running
docker ps | grep backend

# Check if port 8000 is bound
docker exec morphee-backend netstat -tlnp | grep 8000

Phase 6: Verify Deployment (30 minutes)

With docker-compose.coolify.yml, frontend, backend, and Redis all deploy together as one stack. There is no separate frontend deployment step.

6.1 Deploy the Stack

  1. Click "Deploy" button in Coolify
  2. Monitor build logs — Coolify will:
    • Clone repo
    • Build frontend image (npm ci → Vite build → nginx)
    • Build backend image (Python + dependencies)
    • Pull Redis image
    • Start all 3 services
    • Run health checks
  3. Wait for "Deployed" status (green checkmark)

6.2 Verify Frontend

Test frontend loads:

curl https://app.morphee.app | grep -i morphee

Open in browser: https://app.morphee.app — you should see the login page.

Test API connection (browser console):

fetch('https://api.morphee.app/health')
.then(r => r.json())
.then(console.log)
// Expected: {status: "healthy", version: "2.0.0"}

6.3 Troubleshooting

Issue: Blank page

// Check browser console for errors
// Verify build args were applied
console.log(import.meta.env.VITE_API_URL)
// Should show: https://api.morphee.app
// If undefined: VITE_* vars need to be marked as Build Variables in Coolify

Issue: CORS errors

# Verify CORS_ORIGINS includes frontend domain
docker exec <backend-container> env | grep CORS_ORIGINS
# Should include: https://app.morphee.app

Issue: Assets 404

# Check nginx serves files
docker exec <frontend-container> ls -la /usr/share/nginx/html

Phase 7: Monitoring (Optional - 2 hours)

Why Grafana Cloud?

  • Free tier: 10k metrics, 50GB logs, 14-day retention
  • Managed: No server setup required
  • Professional dashboards: Pre-built Prometheus dashboards
  • Alerts: Email/Slack notifications

Setup:

  1. Sign up: https://grafana.com/auth/sign-up
  2. Create stack: Choose closest region
  3. Add Prometheus data source:
    • Copy Remote Write URL from Grafana Cloud
    • Configure Prometheus to remote-write metrics
  4. Import dashboards:
    • Node Exporter Full (ID: 1860)
    • Docker Container Metrics (ID: 193)

Cost: $0/mo (free tier), $8/mo for 200k metrics (paid)

Option B: Self-Hosted Grafana + Prometheus

Deploy monitoring stack:

  1. Upload compose file to server:

    # From local machine
    scp docker-compose.monitoring.yml morphee@YOUR_SERVER_IP:/opt/morphee/
    scp prometheus.yml morphee@YOUR_SERVER_IP:/opt/morphee/
  2. SSH into server and deploy:

    ssh morphee@YOUR_SERVER_IP
    cd /opt/morphee

    # Set environment variables
    export DATABASE_URL="postgresql://..."
    export GRAFANA_PASSWORD="your-secure-password"
    export GRAFANA_SECRET_KEY=$(openssl rand -base64 32)

    # Start monitoring stack
    docker compose -f docker-compose.monitoring.yml up -d

    # Verify
    docker ps | grep -E "prometheus|grafana|node-exporter"
  3. Access Grafana:

    • Add DNS record: monitoring.morphee.appYOUR_SERVER_IP
    • Configure in Coolify (or add to Traefik config)
    • Open: https://monitoring.morphee.app
    • Login: admin / YOUR_GRAFANA_PASSWORD
  4. Import dashboards:

    • Go to Dashboards → Import
    • Enter dashboard ID:
      • 1860 — Node Exporter Full
      • 193 — Docker Container Metrics
      • 9628 — PostgreSQL Database
    • Select Prometheus as data source
    • Click Import
  5. Verify metrics:

    • Open "Node Exporter Full" dashboard
    • You should see: CPU usage, RAM, disk, network

Phase 8: Backups (1 hour)

8.1 Database Backups

Option A: Supabase Automatic Backups (RECOMMENDED)

If using Supabase Database:

  • Automatic daily backups included (7-day retention on Pro)
  • Point-in-time recovery (restore to any minute within 7 days)
  • Download backups via dashboard (Settings → Database → Backups)

Option B: Manual Backup Script

  1. Upload backup script:

    scp scripts/backup-morphee.sh morphee@YOUR_SERVER_IP:/root/
  2. SSH into server and configure:

    ssh morphee@YOUR_SERVER_IP

    # Make executable
    chmod +x /root/backup-morphee.sh

    # Test run
    DATABASE_URL="postgresql://..." /root/backup-morphee.sh

    # Verify backup created
    ls -lh /data/backups/morphee/
  3. Schedule daily backups via cron:

    # Edit crontab
    crontab -e

    # Add line (runs daily at 2 AM)
    0 2 * * * DATABASE_URL="postgresql://..." /root/backup-morphee.sh >> /var/log/morphee-backup.log 2>&1
  4. Optional: Upload to S3 for offsite storage:

    # Install AWS CLI
    apt install -y awscli

    # Configure AWS credentials
    aws configure
    # Enter: Access Key ID, Secret Access Key, Region (us-east-1)

    # Test S3 upload
    DATABASE_URL="postgresql://..." \
    S3_BUCKET="s3://your-morphee-backups" \
    /root/backup-morphee.sh

8.2 Test Disaster Recovery

Simulate data loss and restore:

# SSH into server
ssh morphee@YOUR_SERVER_IP

# 1. Create test data
psql $DATABASE_URL -c "INSERT INTO conversations (id, title, user_id, group_id, space_id, created_at, updated_at) VALUES (gen_random_uuid(), 'Test Conversation', (SELECT id FROM users LIMIT 1), (SELECT id FROM groups LIMIT 1), (SELECT id FROM spaces LIMIT 1), NOW(), NOW());"

# 2. Backup
DATABASE_URL="postgresql://..." /root/backup-morphee.sh

# 3. Delete test data
psql $DATABASE_URL -c "DELETE FROM conversations WHERE title = 'Test Conversation';"

# 4. Restore from latest backup
LATEST_BACKUP=$(ls -t /data/backups/morphee/morphee_db_*.sql.gz | head -1)
gunzip -c $LATEST_BACKUP | psql $DATABASE_URL

# 5. Verify restoration
psql $DATABASE_URL -c "SELECT * FROM conversations WHERE title = 'Test Conversation';"
# Should return the restored row

Phase 9: Testing (1 hour)

9.1 Smoke Tests

Backend API:

# Health check
curl https://api.morphee.app/health

# Test signup
curl -X POST https://api.morphee.app/api/auth/signup \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"Test1234!","name":"Test User"}'

# Test signin
curl -X POST https://api.morphee.app/api/auth/signin \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"Test1234!"}'
# Save token from response

# Test authenticated endpoint
curl https://api.morphee.app/api/auth/me \
-H "Authorization: Bearer YOUR_TOKEN_HERE"

Frontend:

# Test HTML loads
curl https://app.morphee.app | grep -i morphee

# Test assets load
curl -I https://app.morphee.app/assets/index.js
# Should return 200 OK

WebSocket:

// In browser console (after login)
const ws = new WebSocket('wss://api.morphee.app/ws?token=YOUR_JWT_TOKEN');
ws.onopen = () => console.log('✅ WebSocket connected');
ws.onmessage = (e) => console.log('📨 Message:', e.data);
ws.onerror = (e) => console.error('❌ Error:', e);

9.2 End-to-End Test

Complete user flow:

  1. Open https://app.morphee.app in browser
  2. Click Sign Up
  3. Enter: email, password, name
  4. Click Create Account
  5. Should redirect to /onboarding
  6. Chat with AI: "I'm a parent with 2 kids"
  7. AI should create group + spaces
  8. Redirect to /chat
  9. Send message: "Hello Morphee!"
  10. AI should respond
  11. Check WebSocket events in DevTools → Network → WS

9.3 Load Test (Optional)

Install k6:

# macOS
brew install k6

# Linux
sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install k6

Run load test:

// load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';

export let options = {
stages: [
{ duration: '2m', target: 100 }, // Ramp to 100 users
{ duration: '5m', target: 100 }, // Stay at 100
{ duration: '2m', target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95% < 500ms
http_req_failed: ['rate<0.01'], // <1% error rate
},
};

export default function () {
let response = http.get('https://api.morphee.app/health');
check(response, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
});
sleep(1);
}

Run:

k6 run load-test.js

Expected results:

  • ✅ 95th percentile < 500ms
  • ✅ Error rate < 1%
  • ✅ All checks pass

Troubleshooting

Common Issues

1. SSL certificate not provisioning

# Check Traefik logs
docker logs coolify-proxy -f

# Common issues:
# - DNS not propagated yet (wait 5-60 min)
# - Cloudflare DNS-only mode (should be "Proxied")
# - Rate limit (Let's Encrypt allows 5 certs/week per domain)

# Fix: Re-trigger certificate
# Coolify → Application → Domain → Re-issue Certificate

2. Backend can't connect to database

# Check DATABASE_URL format
# Correct: postgresql://user:pass@host:5432/dbname
# Incorrect: postgres://... (should be postgresql://)

# Test connection from backend container
docker exec morphee-backend bash -c "
python -c '
import asyncpg
import asyncio
async def test():
conn = await asyncpg.connect(\"$DATABASE_URL\")
print(\"Connected:\", await conn.fetchval(\"SELECT version()\"))
await conn.close()
asyncio.run(test())
'
"

3. Frontend can't reach backend (CORS)

# Check CORS_ORIGINS in backend
docker exec morphee-backend env | grep CORS_ORIGINS

# Should include: https://app.morphee.app

# Update in Coolify → Backend → Environment → CORS_ORIGINS
# Redeploy backend

4. WebSocket connection fails

// In browser console, check error
const ws = new WebSocket('wss://api.morphee.app/ws?token=YOUR_TOKEN');
ws.onerror = (e) => console.error(e);

// Common issues:
// - Invalid JWT token
// - Backend not listening on /ws
// - Traefik not upgrading connection

// Verify WebSocket endpoint
curl -I https://api.morphee.app/ws
// Should return "426 Upgrade Required"

5. Out of disk space

# Check disk usage
df -h

# Clean up Docker images/containers
docker system prune -a -f --volumes

# Remove old logs
journalctl --vacuum-time=7d

# Check large directories
du -sh /var/lib/docker
du -sh /data/coolify

Cost Breakdown

Everything in docker-compose.coolify.yml — no external accounts.

ServiceProviderCost/mo
Server (8GB RAM)Hetzner CPX31€16 (~$17)
Domain + SSLCloudflare$0 (free)
SMTPResend$0 (free tier: 3k emails/mo)
Total~$17/mo

Pros: Cheapest, full control, data sovereignty, no vendor dependency Cons: Single server (no redundancy), manual backups

Option 2: Hybrid (Managed Database)

Self-hosted app + managed PostgreSQL for automatic backups.

ServiceProviderCost/mo
Server (4GB RAM)Hetzner CPX21€8 (~$9)
Database + AuthSupabase Pro$25
Domain + SSLCloudflare$0 (free)
Total~$34/mo

Pros: Automatic backups, point-in-time recovery Cons: Vendor dependency, higher cost

Option 3: Full Managed (Future)

ServiceProviderCost/mo
DatabaseAWS RDS$30
App HostingAWS ECS Fargate$30
Load BalancerAWS ALB$20
RedisElastiCache$15
CDNCloudFront$5
Total~$100/mo

When to migrate: >10k users, multi-region, compliance needs


Next Steps

After successful deployment:

  1. Update documentation:

    • Mark deployment.md as "✅ Deployed"
    • Add production URLs to README.md
  2. Set up monitoring alerts:

    • CPU > 80% for 5 min
    • Disk > 90%
    • HTTP error rate > 5%
  3. Enable CI/CD:

    • GitHub Actions → Coolify webhook
    • Auto-deploy on push to main
  4. User testing:

    • Invite beta users
    • Monitor errors in Sentry/Grafana
  5. Performance tuning:

    • Add database indexes if slow
    • Enable Redis caching
    • Optimize Docker images

Support

Need help?

Emergency contacts:


Last updated: February 13, 2026 Deployment status: ✅ Production-ready Next review: After first production deployment