geniusDebug

End-to-end deployment guide

A complete, copy-paste walkthrough to run geniusDebug in production — from a bare server to a live dashboard receiving errors from your app. If you just want it running locally, use Self-host with Docker instead; this guide is for a real, internet-facing deployment.

!!! abstract “What you’ll end up with” Three public HTTPS hosts — ingest.<you> (event intake), api.<you> (dashboard API), app.<you> (dashboard UI) — a background workers process, plus PostgreSQL and Redis. Your app keeps its existing Sentry SDK and just points its DSN at geniusDebug.

!!! danger “Golden rule — run on separate infra” geniusDebug must never share a database, Redis, or server with the app it monitors (NFR-PERF-5). If geniusDebug is slow or down, the monitored app must be completely unaffected. Provision a dedicated VPS / managed datastores for it.

Reading order: 1. VPS2. The four apps3. Coolify or 4. AWS / DigitalOcean5. Create a project & wire your SDK6. After-deploy checklist.


1. Initial setup on a dedicated VPS

Applies to any Ubuntu 22.04+ box — a DigitalOcean Droplet, AWS EC2, Hetzner, Linode, etc. Skip this section if you deploy with a PaaS like Coolify (§3) that manages the host for you.

1.1 Size the box

Load vCPU / RAM Notes
Small team, < ~50 events/s 2 vCPU / 4 GB comfortable for all four apps + local Postgres/Redis.
Higher traffic 4 vCPU / 8 GB, or split datastores off to managed scale workers first (it does the heavy pipeline work).

Add ~20 GB disk (events are time-partitioned and purged on a retention schedule, so growth is bounded).

1.2 Create a non-root user + firewall

# as root on a fresh box
adduser genius && usermod -aG sudo genius
# firewall: SSH + HTTP/HTTPS only — datastores stay local
ufw allow OpenSSH && ufw allow 80 && ufw allow 443 && ufw enable

Log back in as genius. Everything below runs as this user.

1.3 Install the runtime

# Node 20 LTS
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs git

# PostgreSQL 16 + Redis 7 (local — or use managed, see §4)
sudo apt-get install -y postgresql redis-server
sudo systemctl enable --now postgresql redis-server

!!! warning “Do not set NODE_ENV=production before installing” Migrations run through tsx (a dev dependency). If NODE_ENV=production is set during npm ci, dev deps are pruned and db:migrate fails with “tsx: not found”. Install with dev deps present; you can export NODE_ENV=production afterwards for the runtime if you like.

1.4 Create the database

sudo -u postgres psql <<'SQL'
CREATE USER genius WITH PASSWORD 'CHANGE_ME_STRONG';
CREATE DATABASE geniusdebug OWNER genius;
SQL

Your DATABASE_URL is then postgres://genius:CHANGE_ME_STRONG@localhost:5432/geniusdebug. For Redis with a password, set requirepass in /etc/redis/redis.conf and use redis://:PASS@localhost:6379.

1.5 Clone, configure, build, migrate

git clone https://github.com/XgeniousLLC/geniusDebug.git
cd geniusDebug
cp .env.example .env

Generate the two required secrets and put them in .env:

openssl rand -hex 32   # → JWT_SECRET
openssl rand -hex 32   # → APP_ENCRYPTION_KEY

Minimum .env for a single-box deploy (full reference: Configuration):

DATABASE_URL=postgres://genius:CHANGE_ME_STRONG@localhost:5432/geniusdebug
REDIS_URL=redis://:REDIS_PASS@localhost:6379
JWT_SECRET=<64 hex>
APP_ENCRYPTION_KEY=<64 hex>
INGEST_PORT=4001
API_PORT=4002
API_PUBLIC_URL=https://api.<you>
WEB_URL=https://app.<you>
VITE_API_URL=https://api.<you>     # baked into the web bundle at build time

Build and apply the schema:

npm ci                       # dev deps included (tsx needed for migrate)
npm run build                # shared + db + all apps + web bundle
npm run db:migrate           # schema + partitioned events table + partitions (idempotent)

db:migrate is safe to re-run — it only applies what’s missing. Continue to §2 the four apps to start them, then front them with TLS (§4.3).


2. The four apps

geniusDebug is a monorepo of four independent Node services plus two shared packages (@geniusdebug/shared, @geniusdebug/db). Each app is a plain node dist/main.js process.

App Role Port Public? Build target Start
ingest Sentry envelope intake — auth, rate-limit, size-cap, enqueue. The hot path. 4001 Yesingest.<you> -w @geniusdebug/shared -w @geniusdebug/db -w @geniusdebug/ingest node apps/ingest/dist/main.js
api Dashboard REST API — issues, projects, auth, admin, GitHub. 4002 Yes → api.<you> …-w @geniusdebug/api node apps/api/dist/main.js
workers Queue consumers — normalize → symbolicate → group → persist → alert. Runs retention + partition jobs. 4003 (health only) No (background) …-w @geniusdebug/workers node apps/workers/dist/main.js
web React SPA dashboard (static bundle). static Yes → app.<you> -w @geniusdebug/web serve apps/web/dist

!!! info “Every service now serves a landing + health page” Hitting a service root in a browser shows a small status page; API clients get JSON. ingest, api, and workers all answer GET /health{status:"ok"} — use these for your load balancer / platform healthchecks. workers listens on WORKERS_PORT (default 4003) only for this health face; it does no HTTP work otherwise.

Dependency & start order: datastores (Postgres, Redis) → db:migrate (one-shot) → ingest, api, workers, web (any order). Only ingest must be publicly reachable for events to flow; web + api are for humans.

2.1 Run them with pm2 (bare VPS)

sudo npm i -g pm2
pm2 start apps/ingest/dist/main.js  --name gd-ingest
pm2 start apps/api/dist/main.js     --name gd-api
pm2 start apps/workers/dist/main.js --name gd-workers
pm2 save && pm2 startup             # restart on reboot
pm2 logs gd-workers                 # the pipeline logs here

The web app is static — you serve apps/web/dist from your reverse proxy (§4.3), not pm2.


3. Deploy on Coolify

Coolify runs each service from this repo with Nixpacks (no Dockerfile). Create four separate Coolify Applications (one per app) plus Coolify-managed PostgreSQL and Redis, all in one Coolify Project so they can share environment variables.

3.1 Add the datastores

Coolify → your Project → + NewDatabase → PostgreSQL 16, and again → Redis 7. After they start, copy each one’s internal connection URL (see the gotcha below).

3.2 Set shared environment variables

On the Project → Environment Variables, add the values every backend shares: DATABASE_URL, REDIS_URL, JWT_SECRET, APP_ENCRYPTION_KEY, API_PUBLIC_URL, WEB_URL. Generate the two secrets with openssl rand -hex 32.

3.3 Create the four applications

For each, source = this repo, branch = dev (or main), Build Pack = Nixpacks.

=== “ingest”

- **Build:** `npm ci && npm run build -w @geniusdebug/shared -w @geniusdebug/db -w @geniusdebug/ingest`
- **Start:** `node apps/ingest/dist/main.js`
- **Port:** `4001` · **Domain:** `ingest.<you>` (public)

=== “api”

- **Build:** `npm ci && npm run build -w @geniusdebug/shared -w @geniusdebug/db -w @geniusdebug/api`
- **Start:** `node apps/api/dist/main.js`
- **Pre-Deploy:** `npm run db:migrate`  *(runs the schema migration before each deploy; tsx is present because dev deps aren't pruned)*
- **Port:** `4002` · **Domain:** `api.<you>` (public)

=== “workers”

- **Build:** `npm ci && npm run build -w @geniusdebug/shared -w @geniusdebug/db -w @geniusdebug/workers`
- **Start:** `node apps/workers/dist/main.js`
- **Port:** `4003` · **Domain:** none (background). Healthcheck → `GET /health` on `4003`.

=== “web”

- Build Pack = **Nixpacks → Static site** (nginx) with **SPA** enabled.
- **Base Directory:** `/`  ·  **Build:** `npm ci && npm run build -w @geniusdebug/web`
- **Publish Directory:** `apps/web/dist`
- **Build variable:** `VITE_API_URL=https://api.<you>` (absolute → the SPA calls the API directly)
- **Domain:** `app.<you>` (public)

On each of ingest / api / workers, reference the shared vars (see the gotcha) so they inherit DATABASE_URL, REDIS_URL, JWT_SECRET, APP_ENCRYPTION_KEY, API_PUBLIC_URL, WEB_URL.

3.4 Coolify gotchas (learned the hard way)

??? danger “Shared variables don’t auto-inject — reference them per app” A Project-level variable is not automatically visible inside an application. In each app’s Environment Variables, add a reference row: KEY= (e.g. DATABASE_URL=). Without it the app falls back to localhost and can’t reach the datastores.

??? danger “Use the INTERNAL datastore URLs, not the external TLS ones” Wire DATABASE_URL / REDIS_URL to Coolify’s internal URLs. In particular Redis must be the internal redis://…:6379 — the external rediss://…:6380 (TLS) URL is mis-parsed by ioredis and fails with an ENOENT socket error.

??? danger “Set a Start Command — an empty one restart-loops” Leaving Start Command blank yields bash -c: option requires an argument and the container restart-loops. Always set the node …/dist/main.js start command.

??? danger “Static web: Base Directory must be /” For the web static app, Base Directory has to be / (repo root — npm workspaces need every package.json + the lockfile). Pointing it at apps/web yields “failed to detect app type”. Do not use the repo’s apps/web/Dockerfile here — its nginx hardcodes an api:4002 upstream that only exists inside the compose network.

??? warning “Background worker still needs a Port field” Coolify’s create form requires a port even for a background service — workers now serves a real /health on 4003, so set 4003 and point the healthcheck there.


4. Deploy on AWS or DigitalOcean

Managed datastores + a VPS (or PaaS) for the Node apps. Pick the split that fits.

4.1 DigitalOcean

=== “Droplet + managed DB (recommended)”

1. **Managed PostgreSQL** and **Managed Redis** from the DO console — same region as the Droplet.
   Copy their connection strings into `DATABASE_URL` / `REDIS_URL`. Add the Droplet to each
   database's **Trusted Sources** so only it can connect.
2. A **Droplet** (Ubuntu 22.04, 2 vCPU/4 GB). Follow [§1.2–1.5](#1-initial-setup-on-a-dedicated-vps)
   but skip the local Postgres/Redis install — point at the managed URLs instead.
3. Run the four apps with **pm2** ([§2.1](#21-run-them-with-pm2-bare-vps)); front them with Caddy
   ([§4.3](#43-tls-reverse-proxy)).

=== “App Platform”

Create **four Components** from this GitHub repo:

- three **Web Services** — `ingest` (HTTP port 4001), `api` (4002), and a **Worker** component for
  `workers`; build/start commands exactly as in the [Coolify table](#33-create-the-four-applications).
- one **Static Site** — build `npm ci && npm run build -w @geniusdebug/web`, output dir
  `apps/web/dist`, build-time env `VITE_API_URL=https://api.<you>`.

Add DO **Managed Postgres + Redis**, bind their URLs as component env vars, and run `db:migrate`
once as a **pre-deploy Job** on the `api` component.

4.2 AWS

!!! tip “AWS-native storage & email fit naturally” You’ll likely already have IAM — SES for alert email is a first-class fit, and R2 (or S3, via the S3-compatible client) holds source maps + replays. Configure both in §6.

4.3 TLS reverse proxy

Any deploy that isn’t a managed static host needs a TLS proxy in front of ingest and api, and a static file server for the web bundle. Caddy (auto-HTTPS) example:

ingest.<you> { reverse_proxy localhost:4001 }
api.<you>    { reverse_proxy localhost:4002 }
app.<you> {
    root * /home/genius/geniusDebug/apps/web/dist
    try_files {path} /index.html      # SPA fallback — required
    file_server
}

try_files … /index.html (SPA fallback) is mandatory or dashboard deep-links 404 on refresh.


5. Set up a project and use your existing Sentry SDK

geniusDebug speaks the Sentry envelope protocol, so any Sentry SDK works against it unchanged — you only repoint the DSN. Full detail: Integrate an app.

5.1 Create a project & copy the DSN

Open app.<you> → the first visitor registers and becomes admin (this provisions a default project, DSN, environments, and a default alert rule). Settings → your project shows a DSN:

https://<publicKey>@ingest.<you>/<projectId>

The public key is write-only (send-only, cannot read data) — safe in a client bundle. Add more projects from the Projects page; each member sees only the projects an admin grants them.

5.2 JavaScript / Next.js (@sentry/nextjs)

If your Next.js app already uses @sentry/nextjs, change three things:

// sentry.client.config.ts / server / edge
Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,  // = the geniusDebug DSN above
  tunnelRoute: '/monitoring',               // first-party forward, beats ad-blockers
  environment: process.env.NEXT_PUBLIC_ENV,
  release: process.env.VERCEL_GIT_COMMIT_SHA,
  tracesSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,
  replaysSessionSampleRate: 0,
});
// next.config.mjs — stop Sentry's SaaS source-map upload; geniusDebug uploads maps itself
export default withSentryConfig(nextConfig, {
  sourcemaps: { disable: true },
  release: { create: false },
  tunnelRoute: '/monitoring',
  // do NOT set org / project / authToken
});

Then add the same-origin tunnel route and run geniusDebug’s map uploader in CI so frames symbolicate. A complete drop-in reference (client/server/edge config, instrumentation, global-error, tunnel route, remote kill switch) lives in taskip-integration/ in the repo.

Upload source maps on each deploy of your app:

GENIUSDEBUG_API=https://api.<you> \
GENIUSDEBUG_ORG_TOKEN=<secret org token from Settings> \
GENIUSDEBUG_PROJECT_ID=<project id> \
R2_BUCKET=R2_ENDPOINT=R2_ACCESS_KEY_ID=R2_SECRET_ACCESS_KEY=\
RELEASE=$VERCEL_GIT_COMMIT_SHA \
node scripts/upload-sourcemaps.mjs

5.3 Laravel / PHP (sentry/sentry-laravel)

The backend is platform-agnostic — PHP events group, get culprits, and render like JS events, and symbolication is skipped for non-JS (PHP already has real file paths). Install the SDK and point its DSN at geniusDebug:

composer require sentry/sentry-laravel
php artisan sentry:publish --dsn=https://<publicKey>@ingest.<you>/<projectId>
# .env (Laravel app)
SENTRY_LARAVEL_DSN=https://<publicKey>@ingest.<you>/<projectId>
SENTRY_TRACES_SAMPLE_RATE=0.1

That’s the whole integration — no tunnel and no source maps for server platforms. (Laravel is formally a v2 target per SRS §12, but the ingest + pipeline already accept platform:"php" events today.)

5.4 Any other Sentry SDK

Server-to-server SDKs (@sentry/node, Python, Go, Ruby…) need only the DSN:

Sentry.init({ dsn: process.env.SENTRY_DSN, tracesSampleRate: 0.1 });

5.5 Verify the loop

Trigger a handled error → it appears in Issues within seconds (grouped, with a culprit file) → open it for the stack, highlights, and trace ID. Once maps are uploaded and a repo is linked, frames symbolicate and deep-link to the exact GitHub commit + line.


6. What you must configure after deploy

Do these before treating the deployment as production.

Must-do

Connect the optional services (Settings → Integrations)

The core capture → group → triage loop works without these; each one lights up a feature. Prefer the in-app Settings → Integrations tab (credentials are encrypted in the DB) over env vars — though env vars win when both are set.

Operations & safety

Post-deploy checklist (copy/paste)