This guide covers running PSA in production-like environments: Docker Compose (recommended for self-hosting), container platforms, and manual Node deployment.
- AUTH_SECRET — at least 32 characters (
openssl rand -base64 32) - AUTH_URL — public URL users reach (e.g.
https://psa.example.com) - PostgreSQL 16 with the
psa_appRLS role (created automatically in Docker setups below)
The fastest way to run Postgres + the app together:
export AUTH_SECRET="$(openssl rand -base64 32)"
docker compose -f docker-compose.stack.yml up --build -dOpen http://localhost:3000 and sign in:
| Field | Value |
|---|---|
| Organization | demo-firm |
admin@demo.com |
|
| Password | password123 |
The stack automatically runs migrations, applies RLS policies, and seeds demo data on first start (SEED_DEMO=true by default).
| Variable | Default | Purpose |
|---|---|---|
AUTH_SECRET |
(required) | Session signing secret |
AUTH_URL |
http://localhost:3000 |
Public app URL |
APP_PORT |
3000 |
Host port mapped to the container |
SEED_DEMO |
true |
Seed demo org/users on startup |
To disable demo seeding in production:
SEED_DEMO=false AUTH_SECRET="..." docker compose -f docker-compose.stack.yml up -ddocker compose -f docker-compose.stack.yml logs -f app
docker compose -f docker-compose.stack.yml down # keep data volume
docker compose -f docker-compose.stack.yml down -v # delete dataBuild and run against an existing Postgres instance:
docker build -t psa-platform .
docker run --rm -p 3000:3000 \
-e DATABASE_URL="postgresql://psa_app:PASSWORD@db-host:5432/psa?schema=public" \
-e DIRECT_URL="postgresql://postgres:PASSWORD@db-host:5432/psa?schema=public" \
-e AUTH_SECRET="$(openssl rand -base64 32)" \
-e AUTH_URL="https://psa.example.com" \
-e PGHOST=db-host \
psa-platformEnsure the psa_app role exists and RLS policies are applied (prisma/rls.sql) before serving traffic.
Each GitHub release publishes:
ghcr.io/safetymp/professional-service-automation:<version>
docker pull ghcr.io/safetymp/professional-service-automation:0.1.3
docker run --rm -p 3000:3000 \
-e DATABASE_URL="postgresql://psa_app:PASSWORD@db-host:5432/psa?schema=public" \
-e DIRECT_URL="postgresql://postgres:PASSWORD@db-host:5432/psa?schema=public" \
-e AUTH_SECRET="$(openssl rand -base64 32)" \
-e AUTH_URL="http://localhost:3000" \
-e PGHOST=db-host \
ghcr.io/safetymp/professional-service-automation:0.1.3Make the package public under Packages in the repo settings if pulls fail with 403.
-
Create a PostgreSQL plugin and a GitHub repo service.
-
Set service variables:
Variable Value DATABASE_URLRLS app URL (create psa_approle — seedocker/init-db.sql)DIRECT_URLRailway Postgres URL (superuser) AUTH_SECRETRandom 32+ char secret AUTH_URLhttps://${{RAILWAY_PUBLIC_DOMAIN}}SEED_DEMOfalsein production -
Set Build Command:
npm run build -
Set Start Command:
npx prisma migrate deploy && node .next/standalone/server.js
(Or deploy via Dockerfile for migrate + RLS in the entrypoint.) -
Run RLS once:
psql $DIRECT_URL -f prisma/rls.sql
fly launch --no-deploy
fly postgres create
fly postgres attach <pg-app-name>Set secrets:
fly secrets set AUTH_SECRET="$(openssl rand -base64 32)"
fly secrets set AUTH_URL="https://<your-app>.fly.dev"
fly secrets set SEED_DEMO=falseDeploy with the included Dockerfile:
fly deployApply RLS after first deploy:
fly ssh console -C "psql \$DIRECT_URL -f prisma/rls.sql"npm ci
cp .env.example .env # configure production values
npx prisma migrate deploy
psql "$DIRECT_URL" -f prisma/rls.sql
npm run build
npm start # listens on PORT (default 3000)Use a process manager (systemd, PM2) and a reverse proxy (nginx, Caddy) for TLS termination.
- Rotate
AUTH_SECRETand all demo passwords - Set
SEED_DEMO=false - Use TLS (
AUTH_URLmust match the public HTTPS URL) - Back up Postgres regularly
- Restrict database access to the app network
- Review
SECURITY.mdbefore exposing to the internet
| Symptom | Fix |
|---|---|
| 500 after deploy | Check AUTH_SECRET is set and migrations ran |
| Login fails after reseed | Sign out and back in (session validation) |
| RLS errors | Confirm DATABASE_URL uses psa_app, not superuser |
| Prisma client stale | Run npx prisma generate after schema changes |
See also docs/development.md for local development workflows.