DocHub
Droplet setup, nginx reverse proxy, SSL, PM2, and the setup script

Production Deployment

DocHub runs on the same DigitalOcean droplet as the CMS at 178.128.183.166, served at docs.ipnoelp.com.

Port Allocation

Service Port Domain
CMS Frontend 3000 cms.ipnoelp.com
CMS Backend 3001 cms.ipnoelp.com/api
DocHub 3002 docs.ipnoelp.com
PostgreSQL 5433 localhost only

nginx Configuration

deploy/nginx-docs.conf defines two server blocks:

  1. Port 80 — redirects all HTTP to HTTPS
  2. Port 443 — SSL termination, proxies to 127.0.0.1:3002

Key proxy headers set:

  • X-Real-IP and X-Forwarded-For for client IP
  • X-Forwarded-Proto for scheme detection (secure cookies)
  • Upgrade and Connection headers for potential WebSocket support

SSL certificates are managed by Let’s Encrypt via certbot with auto-renewal.

PM2 Process Management

PM2 is installed globally on the droplet (npm install -g pm2). ecosystem.config.js configures a single process:

Setting Value
Name dochub
Script dist/index.js
Working dir /home/chas-watkins/code/DocHub
Memory limit 256MB (auto-restart)
Logs /var/log/dochub/out.log, /var/log/dochub/error.log

PM2 handles auto-restart on crash. pm2 startup && pm2 save ensures it survives server reboots.

Coexistence with CMS: The CMS start-cms.sh and stop-cms.sh scripts explicitly avoid killing port 3002, so restarting the CMS does not affect DocHub. DocHub is managed exclusively through PM2.

Setup Script

deploy/setup-droplet.sh automates the full deployment:

  1. Creates /var/log/dochub/ and reports/ directories
  2. Runs npm ci to install dependencies
  3. Compiles TypeScript with npx tsc
  4. Creates .env from template (if not exists) with placeholder values
  5. Copies nginx config to /etc/nginx/sites-available/docs and enables it
  6. Requests SSL certificate via certbot
  7. Starts the app with PM2 and saves the process list
  8. Installs the daily cron job (3:00 AM UTC)

After running the script, you must manually:

  1. Add DNS A record: docs.ipnoelp.com -> droplet IP
  2. Add docs.ipnoelp.com to Google Cloud Console authorized origins
  3. Add https://docs.ipnoelp.com/auth/google/callback to authorized redirect URIs
  4. Edit .env with real Google OAuth credentials and session secret (same as CMS)
  5. Add COOKIE_DOMAIN=.ipnoelp.com to CMS’s .env
  6. Rebuild CMS backend (npx tsc) and restart both services

Shared Auth Setup

For SSO between CMS and DocHub, both apps need:

Setting Value
SESSION_SECRET Same value in both .env files
COOKIE_DOMAIN .ipnoelp.com (note the leading dot)
PostgreSQL session table session (auto-created by connect-pg-simple)
Google OAuth credentials Same client ID and secret

The leading dot on .ipnoelp.com makes the cookie accessible to all subdomains (cms.ipnoelp.com, docs.ipnoelp.com).

Changing the CMS cookie domain will invalidate all existing CMS sessions — users will need to log in again once. This is a one-time event.

Trust Proxy

The Express app sets app.set('trust proxy', 1) so it trusts nginx’s X-Forwarded-Proto header. Without this, secure cookies would not be set because Express sees plain HTTP from nginx (SSL is terminated at nginx, not at Express).

Build and Compile

DocHub runs from compiled JavaScript in dist/. After editing any .ts file:

npx tsc                    # Compile TypeScript locally
rsync src/ dist/ content/  # Sync to droplet via SSH
pm2 restart dochub         # Restart the process on droplet

For content-only changes (markdown or overview HTML), only content/ needs syncing — no recompile needed.

The daily cron also handles this: it pulls from git, compiles, and restarts automatically at 3:00 AM UTC.

Deployment-Relevant Endpoints

Endpoint Purpose in Deployment
GET /api/health Post-deploy verification — confirms app is running, database connected, returns uptime and environment
GET /report Serves the latest HTML coverage report (generated by /dochub-report skill)
GET /api/report/latest JSON version of the latest agent report for programmatic access
GET /auth/me Verify OAuth is working after deploy (returns user info or dev mode status)

After every deployment, the health check should return "status": "healthy" and "database": "connected".

Database Schema

DocHub uses a single database table:

session table

Created automatically by connect-pg-simple if missing (createTableIfMissing: true).

Column Type Purpose
sid varchar Primary key — session ID from cookie
sess json Serialized session data including Passport user object (email, displayName)
expire timestamp(6) Expiration timestamp — sessions last 24 hours by default

This table is shared with the CMS. Both apps read and write to it. There is no migration system — the table is created on first connection if it doesn’t exist.

No other tables are used. All documentation content lives on the filesystem, not in the database.