Server Architecture
Problem Statement
Developers are in the Philippines where power outages and internet drops are common. Claude CLI instances running on local laptops means:
- Power outage = work stops, context lost
- Internet drop = CLI session dies
- Developer sick = idle Claude Max seat ($200/mo)
- Developer quits = all work on their laptop, gone
- Weekends = 5 Max accounts sitting unused
Solution: Centralized LXC Dev Server
One dedicated OVH server (OVH5) running LXC containers, one per Claude account. Developers SSH in and work inside their container. tmux keeps sessions alive through disconnections.
Why LXC, Not Docker
Docker containers are for running applications. LXC containers are lightweight VMs — full Linux environments with systemd, SSH, and critically: Docker can run natively inside LXC. This matters because Claude CLI regularly needs to spin up databases, services, and tools in Docker as part of normal project work.
Server Details
| Item | Details |
|---|---|
| Hostname | main |
| IP | 15.204.90.153 |
| SSH | ssh ovh5 |
| OS | Ubuntu 24.04 LTS |
| CPU | 4 vCPU |
| RAM | 8 GB |
| Boot disk | /dev/sda — 387 GB |
| Data disk | /dev/sdb → /data — 98 GB ext4 (84 GB free) |
| LXC storage | /data/lxc/ |
Server Layout
OVH5 "main" (4 vCPU / 8GB RAM / 387GB + 98GB data)
|
+-- HOST (ubuntu user)
| +-- Claude Net SSH tunnel (systemd: claude-net-tunnel)
| +-- LXC management, NAT, port forwarding
| +-- /data/lxc/ -- all container rootfs
| +-- Dynamic IP whitelisting (PAM + fail2ban)
| +-- container-status.sh monitoring script
| +-- nginx: news.ipnoelp.com + news.lifeonroatan.com -> 10.0.3.12:8001
|
+-- LXC: chasclaude (10.0.3.11, port 2211) <- Chas primary
| +-- Claude CLI (Max account) in tmux
| +-- All standard skills + Claude Net MCP
| +-- Docker 29.2.1 with compose
| +-- 8 project repos cloned + npm installed
| +-- Postgres containers (CMS, WIT, Odoo)
| +-- SSH keys to all production servers
| +-- Gmail + Resend MCP servers configured
| +-- Mosh: UDP 60011-60020
|
+-- LXC: infoclaude (10.0.3.12, port 2212) <- Chas secondary
| +-- Claude CLI (Max account) in tmux
| +-- All standard skills + Claude Net MCP
| +-- Docker 29.2.1 with compose
| +-- Mosh: UDP 60021-60030
| +-- Projects: lifeonroatan (roatan_scraper), roatan-news (news_system)
| +-- systemd: roatan-news-api (port 8001)
| +-- Cron: scraper 3AM, Astro build 3:30AM, news scraper every 30min
|
+-- LXC: seanclaude (10.0.3.13, port 2213) <- Sean
| +-- Claude CLI (Max account) in tmux
| +-- All standard skills + Claude Net MCP
| +-- Docker 29.2.1 with compose
| +-- Mosh: UDP 60031-60040
|
+-- LXC: jazclaude (10.0.3.14, port 2214) <- Jaz
| +-- Claude CLI (Max account) in tmux
| +-- All standard skills + Claude Net MCP
| +-- Docker 29.2.1 with compose
| +-- Mosh: UDP 60041-60050
|
+-- LXC: manager (10.0.3.15, port 2215) <- Supervisor (Pro)
+-- Claude CLI (Pro account) in tmux
+-- container-status, session-logs monitoring tools
+-- Docker 29.2.1 with compose
+-- Mosh: UDP 60051-60060
Note: The container at 10.0.3.12 was renamed from “serclaude” to “infoclaude” on 2026-02-28. The name “serclaude” now refers to Chas’s home server (ser-8 mini), accessible via SSH with Cloudflare tunnel fallback.
DNS Hostnames
All containers are accessible via memorable DNS hostnames on the ipnoelp.io domain (Cloudflare DNS-only, no proxy):
| Hostname | Container | Port |
|---|---|---|
| chas.ipnoelp.io | chasclaude | 2211 |
| info.ipnoelp.io | infoclaude | 2212 |
| sean1.ipnoelp.io | seanclaude | 2213 |
| jaz.ipnoelp.io | jazclaude | 2214 |
| mgr.ipnoelp.io | manager | 2215 |
These are configured as A records pointing to 15.204.90.153 with Cloudflare proxy disabled (grey cloud / DNS only) so SSH traffic passes through directly.
Laptop SSH Config
Developers add entries to ~/.ssh/config on their laptops:
Host chasclaude
HostName chas.ipnoelp.io
Port 2211
User dev
IdentityFile ~/.ssh/ovh5_dev
IdentitiesOnly yes
Launcher Scripts
Chas has launcher scripts in ~/bin/ on his laptop that use wmctrl to focus existing Kitty terminal windows or open new SSH connections:
| Script | Target | Notes |
|---|---|---|
chasclaude |
chas.ipnoelp.io:2211 | Direct SSH |
infoclaude |
info.ipnoelp.io:2212 | Direct SSH |
serclaude |
ser-8 mini | Local SSH (3s timeout) with Cloudflare tunnel fallback |
lapclaude |
localhost | Local tmux session |
Networking
Port Forwarding (in /etc/rc.local)
| External Port | Container | Internal Port | Protocol |
|---|---|---|---|
| 2211 | chasclaude (10.0.3.11) | 22 | TCP (SSH) |
| 2212 | infoclaude (10.0.3.12) | 22 | TCP (SSH) |
| 2213 | seanclaude (10.0.3.13) | 22 | TCP (SSH) |
| 2214 | jazclaude (10.0.3.14) | 22 | TCP (SSH) |
| 2215 | manager (10.0.3.15) | 22 | TCP (SSH) |
| 60011-60020 | chasclaude | 60011-60020 | UDP (Mosh) |
| 60021-60030 | infoclaude | 60021-60030 | UDP (Mosh) |
| 60031-60040 | seanclaude | 60031-60040 | UDP (Mosh) |
| 60041-60050 | jazclaude | 60041-60050 | UDP (Mosh) |
| 60051-60060 | manager | 60051-60060 | UDP (Mosh) |
Network Stack
| Component | Details |
|---|---|
| Bridge | lxcbr0 (10.0.3.1/24) |
| Container IPs | Static via netplan (10.0.3.11-15) |
| NAT | iptables MASQUERADE in /etc/rc.local |
| IP forwarding | net.ipv4.ip_forward=1 |
| DNS | 8.8.8.8, 8.8.4.4 (in container netplan) |
Docker in LXC
All containers have Docker 29.2.1 installed with full compose support. This requires LXC nesting configuration on the host:
# Added to each container config in /data/lxc/<name>/config
lxc.include = /usr/share/lxc/config/nesting.conf
lxc.apparmor.profile = unconfined
lxc.mount.auto = proc:rw sys:rw cgroup:rw
Developers use Docker for project databases, services, and tools as part of normal development work.
How Developers Connect
- SSH:
ssh chasclaude(alias in ~/.ssh/config on laptop) - Mosh:
mosh --ssh="ssh -p 2211" dev@chas.ipnoelp.io -p 60011 - tmux auto-attach: Login shell runs ~/.tmux-session.sh — creates or reattaches “main” session
- Power outage recovery: Pick up phone, SSH app, reattach tmux — Claude still running
Claude Net Optimization
Persistent SSH tunnel on host (systemd claude-net-tunnel):
ssh -N -L 0.0.0.0:3500:127.0.0.1:3500 cms-droplet
Containers hit http://10.0.3.1:3500 (host bridge IP) which tunnels to the hub.
Latency: ~95ms (vs ~1.1s with per-request SSH).
Container config uses "sshHost": "local" and "localUrl": "http://10.0.3.1:3500".
Security
| Measure | Details |
|---|---|
| SSH keys only | Password auth disabled on host and all containers |
| fail2ban | 3 retries -> 24h ban |
| UFW | Only required ports open |
| Dynamic IP whitelist | PAM hook whitelists connecting IP for 3 days |
| Unattended upgrades | Security patches auto-applied |
| No root SSH | Root login disabled |
chasclaude: Full Dev Environment
chasclaude is fully provisioned as Chas’s primary development machine:
Projects Cloned
| Project | Local Dir | Status |
|---|---|---|
| hypnoelp-cms | CMS | Full setup (Docker DB, .env, npm, audio) |
| dochub | DocHub | Full setup (.env, npm) |
| wit-proptech | Craig | Full setup (Docker DB, .env, npm) |
| captainvans-odoo | CaptainVans-Odoo | Docker compose ready |
| elevenlabs-tts | ELABS/elevenlabs-v3-tts | npm installed |
| workbook-gen | cc | npm installed (backend + frontend) |
| claude-skills | ClaudeSkills | Cloned |
| whatsapp-crm-site | whatsapp-crm-site | rsync’d (not a git repo) |
Docker Databases
| Container | Port | Project |
|---|---|---|
| cms-db (Postgres) | 5433 | CMS |
| wit-db (Postgres) | 5434 | WIT PropTech |
| odoo-db + odoo | 5435, 8069 | Captain Van’s |
MCP Servers
| Server | Auth |
|---|---|
| Gmail (omelasai) | OAuth tokens in ~/.gmail-mcp/ |
| Gmail (lifeonroatan) | OAuth tokens in ~/.gmail-mcp-lifeonroatan/ |
| Resend | API key in .mcp.json |
| Claude Net | Standard container config |
SSH Keys to Production
All server keys from laptop copied: cms_droplet, ovh_vps, ovh2_dev, ovh3_worker, ovh4_vps, hostinger_hypnoelp.
Audio Files
9.1 GB audio directory rsync’d from laptop to ~/code/audio/ (core + course files).
infoclaude: Project Services
infoclaude (formerly serclaude LXC) hosts two production projects:
Projects
| Project | GitHub Repo | Purpose |
|---|---|---|
| lifeonroatan | OmelasAI/lifeonroatan | Roatan business directory scraper and Astro static site |
| roatan-news | OmelasAI/roatan-news | Roatan news aggregator with API |
Services Inside infoclaude (10.0.3.12)
| Service | Type | Details |
|---|---|---|
| roatan-news-api | systemd | News API on port 8001 |
| roatan_scraper cron | cron | Business directory scraper, runs daily at 3:00 AM |
| Astro build+deploy cron | cron | Builds static site and deploys to Hostinger, runs daily at 3:30 AM |
| News scraper cron | cron | News aggregation, runs every 30 minutes |
nginx on OVH5 Host
The News API is served via nginx on the OVH5 host. The host nginx reverse-proxies external traffic to the infoclaude container.
| Item | Details |
|---|---|
| Config file | /etc/nginx/sites-available/news-api |
| Domains | news.ipnoelp.com, news.lifeonroatan.com |
| Upstream | 10.0.3.12:8001 (infoclaude container) |
Pending Setup
| Task | Blocker |
|---|---|
claude login on seanclaude, jazclaude, manager |
Needs browser |
gh auth login on seanclaude, jazclaude, manager |
Needs browser |
| Send onboarding emails to Sean and Jaz | Drafts ready in Gmail |
| Hourly IP whitelist cleanup cron | Not yet configured |
| LXC snapshots (nightly) | Not yet configured |
| Claude Net v2 (exec, permissions) | Future enhancement |
| Telegram integration | Future enhancement |