Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Paramount-Intelligence/HR_Monitoring_System/llms.txt

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

Railway is the recommended platform for production pilot deployments of the Workforce Intelligence & Execution OS. A complete deployment consists of five Railway services: a managed PostgreSQL database, a managed Redis instance, the FastAPI backend, the Next.js frontend, and a Celery background worker. This guide walks through provisioning each service, setting the correct environment variables, and verifying the deployment with a smoke-test checklist.
Do not copy your local .env file directly into Railway. Set only the variables each service needs, and never put backend secrets (database credentials, SMTP passwords, VAPID keys) on the frontend service.

Required Railway Services

ServiceTypePurpose
PostgreSQLManaged databasePrimary transactional store
RedisManaged cacheCelery message broker
Backend APIGitHub deploymentFastAPI application + auto-migrations
FrontendGitHub deploymentNext.js web application
WorkerGitHub deploymentCelery background task processor

Deployment Steps

1

Create a Railway project

Log in to Railway and create a new project. Give it a name that reflects your organisation — for example, workforce-os-production.
2

Provision PostgreSQL and Redis

From the project canvas, click New Service → Database and add:
  • PostgreSQL — Railway injects DATABASE_URL and DATABASE_PUBLIC_URL automatically into any service that references it.
  • Redis — Railway injects REDIS_URL automatically.
Ensure the PostgreSQL database is created with UTF-8 encoding to support emoji and Unicode in messages and call logs. Railway’s managed Postgres uses UTF-8 by default, so no extra configuration is needed for provisioned databases.
3

Add the Backend API service

Click New Service → GitHub Repo, select your repository, and set:
  • Root Directory: apps/api
  • Build Command: pip install -r requirements.txt
  • Start Command: read from Procfile (Railway picks this up automatically)
The Procfile in apps/api defines the web process:
Procfile
web: python -m alembic upgrade head && gunicorn -w 1 -k uvicorn.workers.UvicornWorker app.main:app --bind 0.0.0.0:$PORT --timeout 120
alembic upgrade head runs automatically on every deploy before gunicorn starts, keeping the schema current without a separate migration step.
Set the following environment variables on the Backend API service:Required
APP_ENV=production
APP_HOST=0.0.0.0
APP_SECRET_KEY=<generate-a-long-random-secret>

DATABASE_URL=${{Postgres.DATABASE_URL}}
DATABASE_PUBLIC_URL=${{Postgres.DATABASE_PUBLIC_URL}}

FRONTEND_BASE_URL=https://<your-web-service>.up.railway.app
CORS_ORIGINS=https://<your-web-service>.up.railway.app

ACCESS_TOKEN_EXPIRE_MINUTES=120
REFRESH_TOKEN_EXPIRE_DAYS=30

BOOTSTRAP_ADMIN_EMAIL=<admin-email>
BOOTSTRAP_ADMIN_PASSWORD=<strong-initial-password>
BOOTSTRAP_ADMIN_NAME=HR Admin
Optional (but recommended for production)
REDIS_URL=${{Redis.REDIS_URL}}

SMTP_TLS=True
SMTP_PORT=587
SMTP_HOST=smtp.gmail.com
SMTP_USER=<smtp-user>
SMTP_PASSWORD=<smtp-app-password>
EMAILS_FROM_EMAIL=<from-address>
EMAILS_FROM_NAME=Workforce OS

VAPID_PUBLIC_KEY=<public-key>
VAPID_PRIVATE_KEY=<private-key>
VAPID_SUBJECT=mailto:<admin-email>
Only APP_SECRET_KEY signs JWTs. Do not set SECRET_KEY, JWT_SECRET_KEY, or any duplicate secret variable — they are not read by the codebase and will cause confusion.
4

Add the Frontend service

Click New Service → GitHub Repo, select the same repository, and set:
  • Root Directory: apps/web
  • Build Command: npm run build
  • Start Command: npm run start
Set the following environment variables on the Frontend service before the first buildNEXT_PUBLIC_* values are baked into the client bundle at build time:
NEXT_PUBLIC_APP_NAME=Workforce Intelligence & Execution OS
NEXT_PUBLIC_API_URL=https://<your-api-service>.up.railway.app/api/v1
NEXT_PUBLIC_WS_URL=wss://<your-api-service>.up.railway.app/api/v1/ws
APP_ENV=production
NEXT_PUBLIC_WS_URL must point at the API host (/api/v1/ws), not the frontend domain. Do not set NEXT_PUBLIC_VAPID_PUBLIC_KEY — the browser fetches the VAPID public key from GET /api/v1/notifications/push-public-key. Do not set EXPORT_STATIC=true on the Railway web service.
5

Add the Worker service

Click New Service → GitHub Repo, select the same repository, and set:
  • Root Directory: apps/api
  • Start Command: celery -A app.core.celery_app worker --loglevel=info
The worker needs the same DATABASE_URL, REDIS_URL, and APP_SECRET_KEY as the API service. Share them via Railway’s variable references:
APP_ENV=production
DATABASE_URL=${{Postgres.DATABASE_URL}}
REDIS_URL=${{Redis.REDIS_URL}}
APP_SECRET_KEY=<same-secret-as-api>
6

Deploy all services

After variables are configured, trigger a deploy for all services. Railway builds and starts services in dependency order. The backend API will automatically run alembic upgrade head and seed permissions and the bootstrap admin on first start.

Call Recording Storage (S3-Compatible)

For production call recording uploads, configure an S3-compatible bucket on the Backend API service:
CALL_RECORDINGS_STORAGE_DRIVER=s3
CALL_RECORDINGS_MAX_UPLOAD_MB=100
AWS_ENDPOINT_URL=https://<your-bucket-endpoint>
AWS_S3_BUCKET_NAME=<bucket-name>
AWS_ACCESS_KEY_ID=<access-key>
AWS_SECRET_ACCESS_KEY=<secret-key>
AWS_DEFAULT_REGION=auto
AWS_S3_URL_STYLE=virtual
The driver also accepts railway_bucket or railway as aliases for s3.

Multiple Frontend Domains

If more than one frontend domain needs API access, provide a comma-separated list for CORS_ORIGINS:
CORS_ORIGINS=https://your-app.up.railway.app,https://your-custom-domain.com
FRONTEND_BASE_URL must also be present in CORS_ORIGINS, or the API will refuse to start in production.

Post-Deployment Smoke Test

After all services are running, verify the deployment with this checklist:
  • API health: GET https://<your-api>.up.railway.app/health returns 200 OK
  • Frontend loads: the web app opens and can reach the API without console CORS errors
  • Admin bootstrap: log in with your BOOTSTRAP_ADMIN_EMAIL / BOOTSTRAP_ADMIN_PASSWORD credentials — you should land on /admin/dashboard
  • User list: navigate to Users & Teams and confirm at least one user (the bootstrap admin) appears
  • Attendance flow: run a single Check-In / Check-Out cycle and confirm it persists
  • Worker health: inspect the Railway logs for the Worker service — it should print celery@... ready with no connection errors
Change the bootstrap admin password immediately after the first successful login via the user profile settings page.

Important Production Notes

No wildcard CORS. Never set CORS_ORIGINS=* in production. The application enforces this at startup — an empty or wildcard CORS_ORIGINS will raise a RuntimeError and refuse to start when APP_ENV=production.
PostgreSQL only. SQLite is not supported in production. The config validator will raise a ValueError if a sqlite:// URL is detected. Always use the Railway-provided DATABASE_URL reference.
Enable automated backups. In the Railway dashboard, navigate to your PostgreSQL service → Settings → Backups and enable scheduled backups before going live. Railway retains multiple restore points and supports point-in-time recovery on paid plans.

Updating URLs After a Redeploy

If either service URL changes (for example, after linking a custom domain):
  1. Update FRONTEND_BASE_URL on the API service.
  2. Update CORS_ORIGINS on the API service to include the new frontend URL(s).
  3. Update NEXT_PUBLIC_API_URL and NEXT_PUBLIC_WS_URL on the Frontend service.
  4. Redeploy both services — the Frontend must rebuild for NEXT_PUBLIC_* changes to take effect.

Build docs developers (and LLMs) love