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.