DocHub
DigitalOcean server hosting Hypnoelp CMS, DocHub, WIT PropTech — specs, services, and operational concerns

CMS Droplet Overview

The CMS droplet is a DigitalOcean VPS that hosts three web applications (Hypnoelp CMS, DocHub, WIT PropTech), an internal TTS service, and a PostgreSQL database in Docker. It serves production traffic for three domains over HTTPS via nginx reverse proxy.

Server Specifications

Item Details
Hostname cms-droplet
Provider DigitalOcean
Public IP `178.128.183.166`
OS Ubuntu 24.04.3 LTS
CPU 2 vCPU
RAM 1.9 GB + 2 GB swap
Disk 58 GB (72% used)
Node.js v20.20.0
Docker 29.2.0
nginx 1.24.0
PM2 6.0.14

SSH Access

Method Command
Primary `ssh -i ~/.ssh/cms_droplet root@178.128.183.166`
Key file `~/.ssh/cms_droplet`

Services Running

Seven Node.js processes serve the three applications plus an internal TTS tool:

Service Port Started Via Domain Notes
CMS Frontend (Vite dev) 3000 start-cms.sh (nohup) cms.ipnoelp.com Vite dev server in production
CMS Backend 3001 start-cms.sh (nohup) cms.ipnoelp.com/api Express + TypeScript
DocHub 3002 PM2 (cluster mode) docs.ipnoelp.com Express + Markdown renderer
WIT Backend 3100 start-wit.sh (nohup) wit.ipnoelp.com/api Express + TypeScript
WIT Frontend nginx (static files) wit.ipnoelp.com Pre-built React SPA
ElevenLabs TTS 3200 start-cms.sh (nohup) — (internal only) Text-to-speech API proxy
PostgreSQL 15 5433 Docker Single database container

Startup Methods

  • Docker: PostgreSQL starts automatically on boot via Docker restart policy
  • nginx: Starts automatically on boot via systemd
  • PM2 (DocHub): Auto-starts on boot via `pm2 startup` — only PM2-managed service
  • nohup scripts: CMS and WIT use `start-cms.sh` / `start-wit.sh` — these do NOT survive reboot

Nginx Configuration

Four site configurations in `/etc/nginx/sites-enabled/`:

Config File Domain Upstream Notes
cms cms.ipnoelp.com proxy to :3000 (frontend), :3001 (API) 100M upload limit for audio files
docs docs.ipnoelp.com proxy to :3002 Standard proxy
wit wit.ipnoelp.com static files + auth subrequest + proxy to :3100 Auth-gated access
wa-slices.conf wa1–wa8.ipnoelp.com proxy to :3101–:3108 ORPHANED — no backends

Routing Summary

``` Internet → nginx (port 443, SSL termination) ├── cms.ipnoelp.com → localhost:3000 (frontend) + localhost:3001 (/api) ├── docs.ipnoelp.com → localhost:3002 (DocHub) ├── wit.ipnoelp.com → /var/www/wit/ (static) + localhost:3100 (/api) └── wa1-8.ipnoelp.com → ORPHANED (no upstream) ```

SSL Certificates

All certificates are managed by Let`s Encrypt (certbot) with automatic renewal:

Certificate Domains Expires Status
cms.ipnoelp.com cms.ipnoelp.com 2026-05-02 Active
docs.ipnoelp.com docs.ipnoelp.com 2026-05-12 Active
wa1.ipnoelp.com wa1–wa8.ipnoelp.com (SAN) 2026-05-19 Orphaned
wit.ipnoelp.com wit.ipnoelp.com 2026-05-22 Active

Database

PostgreSQL 15 runs in Docker, exposed on port 5433 (non-standard to avoid conflicts).

Item Details
Container `hypnoelp-cms-db`
Database `hypnoelp_cms`
User `cms_user`
Port 5433 (mapped from container 5432)
Tables 26

Key Tables

Table Purpose
courses Course metadata (ID, name, code, status)
audio_files Course audio tracks with R2 URLs
core_audio_files Core audio library (intros, outros, backgrounds)
workbooks HTML workbook content per course position
ads In-app advertisement configuration
faqs FAQ entries with HTML content
background_music 30-second ambient music clips
shop_configurations In-app purchase settings
sampler_courses Bundled sampler course definitions
voice_references Target audio levels for ElevenLabs voices

Access

```bash docker exec hypnoelp-cms-db psql -U cms_user -d hypnoelp_cms -c “YOUR QUERY” ```

Operational Concerns

Disk Usage — 72% (Amber)

The 58 GB disk is at 72% capacity. Audio files in `/home/chas-watkins/code/audio/` are the largest consumer. No automated cleanup or alerting is in place.

No Reboot Survival for CMS/WIT (Red)

The CMS frontend, CMS backend, ElevenLabs TTS, and WIT backend are all started via nohup shell scripts. If the server reboots, only Docker (PostgreSQL), nginx, and PM2 (DocHub) will restart automatically. CMS and WIT must be manually restarted:

```bash ./start-cms.sh cd /home/chas-watkins/code/Craig && ./start-wit.sh ```

DocHub Memory Pressure (Amber)

PM2 reports DocHub heap at 94.46% with 30 restarts. The process is running in cluster mode but is under memory pressure on this resource-constrained server.

Orphaned WhatsApp Slice Configs (Amber)

The `wa-slices.conf` nginx configuration routes wa1–wa8.ipnoelp.com to ports 3101–3108, but no WhatsApp backends run on this server. The WhatsApp CRM was migrated to OVH. These configs and their SSL certificate can be safely removed.

No Cron Jobs (Amber)

No cron jobs exist for maintenance tasks such as log rotation, disk usage alerts, SSL renewal verification, or database backups. Certbot auto-renewal is handled by systemd timer, not cron.

File System Layout

Path Purpose
`/home/chas-watkins/code/CMS/` CMS source code
`/home/chas-watkins/code/DocHub/` DocHub source code
`/home/chas-watkins/code/Craig/` WIT PropTech source code
`/home/chas-watkins/code/ELABS/` ElevenLabs TTS tool
`/home/chas-watkins/code/audio/` Audio file storage (course + core)
`/var/www/wit/` WIT frontend static build

Deployment Workflow

Changes are synced from the local development machine using rsync:

```bash rsync -avz --exclude=‘node_modules’ --exclude=‘dist’ -e “ssh -i ~/.ssh/cms_droplet”
/home/chas-watkins/code/CMS/backend/src/ root@178.128.183.166:/home/chas-watkins/code/CMS/backend/src/ ```

After syncing, rebuild and restart the relevant service on the droplet.