ο»Ώ# Deploying Pram on Coolify
Pram is a Turborepo monorepo with separate web (Vite SPA) and server (Hono API) apps. Deploy them as two Coolify applications from the same Git repository, plus PostgreSQL and Redis.
User β app.example.com (Web) β api.example.com (Server) β PostgreSQL + Redis
Resources overview
| Coolify resource | Public URL | Purpose |
|---|---|---|
| Web application | https://app.example.com |
Frontend (static SPA) |
| Server application | https://api.example.com |
API, auth, tRPC, job queue |
| PostgreSQL | internal only | Drizzle ORM |
| Redis | internal only | BullMQ AI generation queue |
Both applications use the repository root as Base Directory. They differ only in Nixpacks config and environment variables.
1. PostgreSQL
- In Coolify, create a PostgreSQL database service.
- Note the internal hostname, port, username, and password.
- Create a database named
pram(or adjustDATABASE_URLaccordingly). - Keep the service in the same Coolify project/network as the server app.
Example internal connection (hostname varies by Coolify):
DATABASE_URL=postgresql://postgres:YOUR_PASSWORD@YOUR_POSTGRES_HOST:5432/pram
2. Redis
- Create a Redis service in Coolify (or use a managed Redis instance).
- Note the internal hostname.
REDIS_URL=redis://YOUR_REDIS_HOST:6379
Redis is required β AI question generation uses BullMQ.
3. Server application (API)
Coolify settings
| Setting | Value |
|---|---|
| Source | Your Git repository |
| Base Directory | / (repository root) |
| Build Pack | Nixpacks |
| Nixpacks config | Set env NIXPACKS_CONFIG_FILE=deploy/server.nixpacks.toml |
| Port | 3000 |
| Domain | api.example.com |
| HTTPS | Enabled (required for auth cookies) |
Equivalent build/start commands if configuring manually in Coolify UI:
# Install
bun install --frozen-lockfile
# Build
bunx turbo build -F server
# Start
bun run --cwd apps/server start
Environment variables (runtime)
Set these on the server application. All are validated at startup in packages/env/src/server.ts.
NODE_ENV=production
DATABASE_URL=postgresql://postgres:YOUR_PASSWORD@YOUR_POSTGRES_HOST:5432/pram
REDIS_URL=redis://YOUR_REDIS_HOST:6379
BETTER_AUTH_SECRET=YOUR_RANDOM_SECRET_MIN_32_CHARS
BETTER_AUTH_URL=https://api.example.com
CORS_ORIGIN=https://app.example.com
API_KEY_ENCRYPTION_KEY=YOUR_ENCRYPTION_KEY_MIN_32_CHARS
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your-smtp-user
SMTP_PASS=your-smtp-password
SMTP_FROM=Pram <noreply@example.com>
Generate secrets:
openssl rand -base64 32
URL correlation (critical):
| Variable | Must match |
|---|---|
BETTER_AUTH_URL |
Public URL of this server app (https://api.example.com) |
CORS_ORIGIN |
Public URL of the web app (https://app.example.com) |
VITE_SERVER_URL (on web app) |
Same value as BETTER_AUTH_URL |
CORS_ORIGIN is also used as Better Auth trustedOrigins. Use exact URLs including https:// and no trailing slash mismatch.
Optional:
PLATFORM_AI_API_KEY=
PLATFORM_AI_BASE_URL=
PLATFORM_AI_MODEL=
FREE_CREDITS_ENABLED=false
Database migrations
Migrations run automatically on container start (see deploy/server.nixpacks.toml). Remove any Post-deployment Command for db:migrate β it often runs without runtime env vars in Coolify.
DATABASE_URL must be set on the server application (not on the PostgreSQL resource). The container does not have apps/server/.env (gitignored).
- Open server application β Environment Variables
- Add
DATABASE_URLwith Available at Runtime enabled (default) - Do not set it as Build-only
To get the connection string:
- Open your PostgreSQL resource β Connect / Configuration
- Copy the internal URL (Docker hostname, not
localhost) - Paste into the server app env:
DATABASE_URL=postgresql://postgres:YOUR_PASSWORD@YOUR_POSTGRES_HOST:5432/pram
Verify in the server app Terminal:
echo $DATABASE_URL
If empty, the variable is missing or not marked as runtime β fix and redeploy.
Alternative for local-style sync (not recommended in production): bun run db:push.
Optional seed (run once via Terminal on the server app):
bun run db:seed
Verify
After deploy, open https://api.example.com/ β it should return OK.
4. Web application (frontend)
Option A β Static Site (recommended)
Lightweight: no long-running Node process.
| Setting | Value |
|---|---|
| Resource type | Static Site |
| Base Directory | / |
| Build command | bun install --frozen-lockfile && bunx turbo build -F web |
| Publish directory | apps/web/dist |
| Domain | app.example.com |
Option B β Nixpacks application
Use when you need a containerized static server (e.g. SPA fallback via serve).
| Setting | Value |
|---|---|
| Build Pack | Nixpacks |
| Nixpacks config | NIXPACKS_CONFIG_FILE=deploy/web.nixpacks.toml |
| Port | 3001 |
| Domain | app.example.com |
Config file: deploy/web.nixpacks.toml
Environment variables (build time)
VITE_SERVER_URL=https://api.example.com
Enable "Available at Buildtime" in Coolify. Vite embeds VITE_* variables during vite build; runtime env changes have no effect.
Rebuild the web app whenever the API URL changes.
Verify
- Open
https://app.example.comβ app loads. - Sign up / sign in works (requires valid SMTP on server).
- Client-side routes work on refresh (Static Site on Coolify handles this; Nixpacks uses
serve -sfor SPA fallback).
First deploy checklist
- Deploy PostgreSQL and Redis; confirm both are healthy.
- Create server app, set all runtime env vars.
- Set
DATABASE_URLon server app env (Runtime). Remove Post-deployment migrate command. - Deploy server; confirm
https://api.example.com/returnsOK. - Create web app, set
VITE_SERVER_URLas buildtime env. - Deploy web; confirm app loads and API calls reach the server.
- Test sign-up (email verification needs SMTP).
- Add an AI API key in Settings and test question generation (needs Redis).
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Migrate fails: url: '' / Please provide required params |
DATABASE_URL not set in Coolify env |
Add internal Postgres URL to server app Environment Variables, redeploy |
API calls go to localhost:3000 |
VITE_SERVER_URL not set at build time |
Rebuild web with buildtime env |
| CORS or auth errors | CORS_ORIGIN β web URL |
Match exact public web URL on server |
| Server crashes on start | Missing or invalid env (SMTP, secrets) | Check Coolify logs; compare with apps/server/.env.example |
| AI generation stuck | Redis unreachable | Verify REDIS_URL and network |
| Email sign-up fails | Invalid SMTP credentials | Test SMTP vars independently |
| SPA 404 on page refresh | Missing SPA fallback | Use Coolify Static Site, or Nixpacks with serve -s (see start:prod in apps/web) |
Nixpacks config files
| File | Service |
|---|---|
deploy/server.nixpacks.toml |
API server |
deploy/web.nixpacks.toml |
Web app (Option B) |
Both run bun install from the monorepo root so workspace packages (@pram/*) resolve correctly.