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:
- Port 80 — redirects all HTTP to HTTPS
- Port 443 — SSL termination, proxies to
127.0.0.1:3002
Key proxy headers set:
X-Real-IPandX-Forwarded-Forfor client IPX-Forwarded-Protofor scheme detection (secure cookies)UpgradeandConnectionheaders 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:
- Creates
/var/log/dochub/andreports/directories - Runs
npm cito install dependencies - Compiles TypeScript with
npx tsc - Creates
.envfrom template (if not exists) with placeholder values - Copies nginx config to
/etc/nginx/sites-available/docsand enables it - Requests SSL certificate via certbot
- Starts the app with PM2 and saves the process list
- Installs the daily cron job (3:00 AM UTC)
After running the script, you must manually:
- Add DNS A record:
docs.ipnoelp.com-> droplet IP - Add
docs.ipnoelp.comto Google Cloud Console authorized origins - Add
https://docs.ipnoelp.com/auth/google/callbackto authorized redirect URIs - Edit
.envwith real Google OAuth credentials and session secret (same as CMS) - Add
COOKIE_DOMAIN=.ipnoelp.comto CMS’s.env - 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.