# Web Hosting Stack — SNS Digital

**Entity:** SNS Digital · **Status:** Buildable now

## What it is

A self-hosted web hosting platform for client sites and internal properties.
Caddy as reverse proxy/TLS terminator, static sites via Hugo or plain HTML,
dynamic sites via WordPress (when clients insist) or Ghost.

## Stack

| Component | Software | FOSS | Role |
|-----------|----------|------|------|
| Reverse proxy + TLS | Caddy 2 | Yes | Auto HTTPS, reverse proxy, static file server |
| Static site generator | Hugo | Yes | Fast builds, no runtime dependencies |
| CMS (when needed) | WordPress or Ghost | Yes | Client-managed content |
| Database | MariaDB | Yes | WordPress backend |
| Object storage | MinIO or S3 | Yes / Partial | Media uploads, large assets |
| DNS | Route53 (AWS) | No | Reliable, cheap, API-driven |
| Deployment | Git push → Gitea Actions → rsync/docker | Yes | Zero-touch deploys |

## Topology

```
Internet
   │
   ▼
┌─────────────────────────────────┐
│  Caddy (LXC on Proxmox)        │  ← public IP via port-forward or Linode proxy
│  - TLS termination              │
│  - Rate limiting                │
│  - Static file serving          │
│  - Reverse proxy to backends    │
└──────────┬──────────────────────┘
           │
     ┌─────┼──────────┐
     ▼     ▼          ▼
  Hugo    WordPress   Ghost
 (static) (VM/Docker) (Docker)
```

### Where it runs

| Site type | Runs on | Why |
|-----------|---------|-----|
| Static (Hugo) | Caddy serves files directly | No runtime, fastest possible |
| WordPress | Docker Compose VM on Proxmox | Isolation, easy backup/restore |
| Ghost | Docker Compose VM on Proxmox | Cleaner than WP for blogs |
| High-traffic / client SLA | Linode or AWS (CloudFront + S3) | Uptime guarantee > home connection |

## Build Steps

### Static site (Hugo — default choice)

1. Scaffold site: `hugo new site client-name`
2. Add theme as git submodule.
3. Push to Gitea repo.
4. Gitea Actions workflow: build → rsync to Caddy's serve directory.
5. Caddyfile entry:
   ```
   client-name.com {
       root * /srv/sites/client-name/public
       file_server
       encode gzip
   }
   ```

### WordPress (when client requires it)

1. Clone Docker Compose template:
   ```yaml
   services:
     wordpress:
       image: wordpress:6-apache
       volumes: [./wp-content:/var/www/html/wp-content]
       environment:
         WORDPRESS_DB_HOST: db
         WORDPRESS_DB_NAME: wp
     db:
       image: mariadb:11
       volumes: [./data:/var/lib/mysql]
       environment:
         MARIADB_ROOT_PASSWORD_FILE: /run/secrets/db_root_pw
   ```
2. Caddy reverse proxies to `wordpress:80`.
3. Backup: PBS snapshots VM nightly + wp-content rsync to S3.

## Security Posture

- **Caddy handles TLS** — automatic cert renewal, no manual certs to manage.
- **No PHP on the proxy LXC.** WordPress runs isolated in its own VM/container.
- **WordPress hardening:** disable XML-RPC, limit login attempts (Fail2ban or WP plugin),
  auto-update minor versions, restrict `wp-admin` to Tailscale IP if internal.
- **Rate limiting** in Caddy for login pages and API endpoints.
- **CSP headers** set per-site in Caddyfile.

## Upgrade Path

- **Cloudflare Tunnel:** If home IP isn't reliable enough for client SLAs, put Caddy
  behind a Cloudflare tunnel (free tier, no public port needed).
- **Multi-node:** If hosting 20+ WordPress sites, move to a dedicated VM cluster with
  shared MariaDB (Galera) and NFS/Ceph for wp-content.
- **Coolify or CapRover:** Self-hosted PaaS if you want a client-facing deploy dashboard.
  YAGNI until you have 5+ active web clients.

<!-- ponytail: No PaaS dashboard until 5+ web clients. Ceiling: manual Caddyfile +
     docker-compose management doesn't scale past ~20 sites. Upgrade: Coolify. -->
