Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.verifow.com/llms.txt

Use this file to discover all available pages before exploring further.

This guide is for the person responsible for running and maintaining Verifow in production. All commands assume you are working from /opt/ratel-security on your VPS.

Daily Operations

Check System Health

# View all running containers
docker compose ps

# Check resource usage per container
docker stats --no-stream

# API health check
curl http://localhost:3001/api/v1/health

# Watchman health check
curl http://localhost:8084/ping
Expected output of docker compose ps -all should show running:
ContainerPublic Port(s)Notes
ratel-app3001 (API), 4000 (dashboard)Main app
ratel-postgres5432 (localhost only)Primary DB
ratel-redis6379 (localhost only)Cache / queues
ratel-minio9000, 9001 (localhost only)Object storage
ratel-marble8080 (internal)Decision engine
ratel-tirreno8585 (internal)Fraud analytics
ratel-ciso-assistant8443 (internal)GRC engine
ratel-watchman8084 (internal)Global sanctions screening

Deployment

Standard Deployment (SaaS Multi-Tenant)

# 1. SSH into the VPS
ssh root@your-vps-ip
cd /opt/ratel-security

# 2. Pull latest code
git pull origin main

# 3. Build new image + recreate container + auto-run migrations
docker compose up -d --build --force-recreate ratel-app

What Happens During Startup

The start.sh entrypoint inside the container now runs this sequence automatically:
  1. prisma migrate deploy -Applies any committed migration files
  2. prisma db push -Syncs any schema changes that lack migration files (safety net)
  3. prisma db seed -Seeds default data if fresh database
  4. Start Next.js dashboard (background, port 4000)
  5. Start NestJS API (foreground, port 3001)
Note: The old start.sh had a bug where db push never ran if migrate deploy returned exit code 0. This has been fixed -both commands now run unconditionally.

What if I just run docker compose up -d?

What you runWhat happensWhen to use
docker compose up -dStarts stopped containers. No build. No recreate. Already-running containers are untouched.After a host reboot
docker compose up -d --force-recreate ratel-appRecreates container from existing image. Picks up .env changes. Does NOT build new code.Env var changes only
docker compose up -d --build --force-recreate ratel-appBuilds new image from current source + recreates container. This is the full deploy.After git pull with code changes
docker compose restart ratel-appRestarts the same container. Fastest. No build, no recreate.Clearing memory, reloading config
docker compose up -d without --build is the #1 cause of “my changes aren’t live”. The container starts fine — but it’s still running the old image.

Shortcut alias

Add this to ~/.bashrc for one-command deploys:
alias ratel-deploy='cd /opt/ratel-security && git pull && docker compose up -d --build --force-recreate ratel-app && docker logs -f ratel-app'

Logs & Debugging

Read App Logs

# Live log stream
docker compose logs -f ratel-app

# Last 100 lines
docker compose logs --tail=100 ratel-app

# Filter for errors only
docker compose logs ratel-app 2>&1 | grep -i 'error\|exception\|fatal'

# Logs since a specific time
docker compose logs --since="2026-05-17T10:00:00" ratel-app

Read Database Logs

docker compose logs -f ratel-postgres

Open a Database Shell

docker compose exec ratel-postgres psql -U ratel -d ratel_security
Useful psql commands:
\dt                          -- list all tables
\d users                     -- describe users table
SELECT * FROM tenants;       -- list all tenants
SELECT email, role, is_active FROM users WHERE role = 'SUPER_ADMIN';
\q                           -- exit

Database Management

Run Migrations Manually

If you ever need to apply migrations manually (e.g., start.sh was bypassed):
docker compose exec ratel-app \
  npx prisma migrate deploy --schema=packages/db/prisma/schema.prisma

Check Migration Status

docker compose exec ratel-app \
  npx prisma migrate status --schema=packages/db/prisma/schema.prisma

Backup the Database

docker compose exec ratel-postgres \
  pg_dump -U ratel ratel_security > /opt/ratel-security/backups/manual_$(date +%Y%m%d_%H%M%S).sql

Restore from Backup

docker compose exec -T ratel-postgres \
  psql -U ratel ratel_security < /opt/ratel-security/backups/manual_20260517_120000.sql

Re-seed the Database

The seed script is idempotent -it skips existing records:
docker compose exec ratel-app \
  npx prisma db seed --schema=packages/db/prisma/schema.prisma

User Management

List All Users

docker compose exec ratel-postgres psql -U ratel -d ratel_security \
  -c "SELECT id, email, role, is_active, created_at FROM users ORDER BY created_at;"

Activate / Deactivate a User

# Deactivate
docker compose exec ratel-postgres psql -U ratel -d ratel_security \
  -c "UPDATE users SET is_active = false WHERE email = 'user@example.com';"

# Reactivate
docker compose exec ratel-postgres psql -U ratel -d ratel_security \
  -c "UPDATE users SET is_active = true WHERE email = 'user@example.com';"

Change a User’s Role

docker compose exec ratel-postgres psql -U ratel -d ratel_security \
  -c "UPDATE users SET role = 'COMPLIANCE_OFFICER' WHERE email = 'user@example.com';"
Valid roles: SUPER_ADMIN, BANK_ADMIN, COMPLIANCE_OFFICER, ANALYST, AUDITOR

Restarting Services

# Restart just the app (fastest, no data loss)
docker compose restart ratel-app

# Restart database (connections drop briefly)
docker compose restart ratel-postgres

# Restart everything
docker compose restart

# Full stop then start (after .env changes or major updates)
docker compose down
docker compose up -d

# Force recreate app container (picks up .env changes, NO rebuild)
docker compose up -d --force-recreate ratel-app

# Rebuild + recreate (use after code changes)
docker compose up -d --build --force-recreate ratel-app
--force-recreate alone does not rebuild the Docker image. It only recreates the container from the existing image. If you pushed new code, you must add --build or the old code will still run.

Environment Variables

Edit /opt/ratel-security/.env to change configuration. After any change:
# For .env changes only (no code rebuild)
docker compose up -d --force-recreate ratel-app

# For code changes (rebuild + recreate)
docker compose up -d --build --force-recreate ratel-app
VariableWhat It Does
NEXT_PUBLIC_API_URLBrowser-facing API URL -must match your public IP or domain
DASHBOARD_URLAllowed CORS origin for the API
DEFAULT_ADMIN_EMAILEmail used when seeding the super admin
DEFAULT_ADMIN_PASSWORDPassword used when seeding the super admin
JWT_SECRETSigns access tokens -never change on a live system
JWT_REFRESH_SECRETSigns refresh tokens -never change on a live system
DATABASE_PASSWORDPostgres password
LOG_LEVELinfo (default) · debug · trace
RESEND_API_KEYEmail delivery via Resend
PREMBLY_API_KEYIdentity verification (NIN/BVN) -embedded mode
DOJAH_API_KEYFallback identity verification
CAC_API_KEYCorporate Affairs Commission lookup -set for live KYB
WATCHMAN_API_URLMoov Watchman internal URL (default: http://ratel-watchman:8084)
WATCHMAN_MATCH_THRESHOLDFuzzy match threshold 0–1 (default: 0.85)
USE_MARBLEEnable Marble decision engine (true/false)

Watchlist & Sanctions

Check Watchman Health

curl http://localhost:8084/ping
# Expected: HTTP 200
curl "http://localhost:8084/search?name=Osama+Bin+Laden&limit=3" | jq '.'

Update Watchman Lists

Lists are bundled in the Docker image. To update:
docker compose pull ratel-watchman
docker compose up -d ratel-watchman

Run NiGSAC Scraper Manually

docker compose exec ratel-app python3 /app/scripts/nigsac_scraper.py

Check Scraper Log

docker compose exec ratel-postgres psql -U ratel -d ratel_security \
  -c "SELECT status, entries_found, entries_loaded, error_msg, ran_at \
      FROM scraper_log ORDER BY ran_at DESC LIMIT 10;"

MinIO Document Storage Operations

MinIO is the S3-compatible object storage for KYC/KYB document uploads.

Access the MinIO Console

# SSH tunnel (recommended for production — console is localhost-only)
ssh -L 9001:localhost:9001 root@your-vps-ip
# Then open http://localhost:9001 in your browser
Default credentials are in .env:
  • Access Key: ratel_minio_access
  • Secret Key: ratel_minio_secret

Common MinIO Tasks

# List buckets
docker compose exec ratel-minio mc ls local

# List files in the documents bucket
docker compose exec ratel-minio mc ls local/ratel-documents

# List files for a specific KYC application
docker compose exec ratel-minio mc ls local/ratel-documents/kyc/kyc_abc123/

# Backup all documents
docker compose exec ratel-minio mc mirror local/ratel-documents /data/backup-minio

# Check disk usage
docker compose exec ratel-minio mc du local/ratel-documents

Document Storage Architecture

ComponentRole
ratel-minioStores actual files (PDFs, images)
ratel-minio-initOne-shot container that creates the bucket on first deploy
KYCDocument / KYBDocument tablesStore metadata (filename, type, MinIO object key)
Presigned URLs5-minute expiring download links returned by the API

Migrating to External S3 (AWS S3, Cloudflare R2, etc.)

  1. Update .env with new endpoint and credentials:
    MINIO_ENDPOINT=s3.amazonaws.com
    MINIO_PORT=443
    MINIO_USE_SSL=true
    MINIO_ACCESS_KEY=AKIA...
    MINIO_SECRET_KEY=...
    MINIO_BUCKET=your-bucket-name
    
  2. Recreate the app container: docker compose up -d --force-recreate ratel-app
  3. The same AWS S3 SDK code works — only the endpoint changes.

Troubleshooting

App Won’t Start

# 1. Check if all containers started
docker compose ps

# 2. Read the last 100 app log lines
docker compose logs --tail=100 ratel-app

# 3. Check Postgres is healthy
docker compose exec ratel-postgres pg_isready -U ratel

# 4. Check database connection URL in .env
grep DATABASE_URL /opt/ratel-security/.env

# 5. Check Redis is reachable
docker compose exec ratel-redis redis-cli ping
# Should return: PONG

# 6. Force a clean rebuild
docker compose down
docker compose build --no-cache ratel-app
docker compose up -d

Database Schema Errors (500 on auth/me)

If you see The column X does not exist in the current database, the schema is out of sync:
# Fix: push schema directly
docker compose exec ratel-app \
  npx prisma db push --schema=packages/db/prisma/schema.prisma --accept-data-loss

# Then restart
docker compose restart ratel-app

Network Still In Use After docker compose down

If other services (Marble, Tirreno) are running, the shared network persists:
# Stop everything in the compose project
docker compose down

# Or stop specific services only
docker compose stop ratel-app ratel-postgres ratel-redis

Quick Reference Card

TaskCommand
View live logsdocker compose logs -f ratel-app
Restart appdocker compose restart ratel-app
Open DB shelldocker compose exec ratel-postgres psql -U ratel -d ratel_security
Change admin passwordGenerate hash → UPDATE users SET password_hash (see User Management)
Re-seed databasedocker compose exec ratel-app npx prisma db seed --schema=packages/db/prisma/schema.prisma
Run migrationsdocker compose exec ratel-app npx prisma migrate deploy --schema=packages/db/prisma/schema.prisma
Push schemadocker compose exec ratel-app npx prisma db push --schema=packages/db/prisma/schema.prisma
Health checkcurl http://localhost:3001/api/v1/health
Update codegit pull && docker compose up -d --build --force-recreate ratel-app
Backup DBdocker compose exec ratel-postgres pg_dump -U ratel ratel_security > backup.sql
Apply .env changesdocker compose up -d --force-recreate ratel-app
Deploy shortcutalias ratel-deploy='cd /opt/ratel-security && git pull && docker compose up -d --build --force-recreate ratel-app && docker logs -f ratel-app'
Watchman healthcurl http://localhost:8084/ping
MinIO console tunnelssh -L 9001:localhost:9001 root@your-vps-ip
Backup documentsdocker compose exec ratel-minio mc mirror local/ratel-documents /data/backup-minio
Run NiGSAC scraperdocker compose exec ratel-app python3 /app/scripts/nigsac_scraper.py