platform/docs/04-site-profiles.md
Bart Van Geyt 9f819df4d9 Phase 0: architecture docs, ADRs, and repo scaffold
Establish the design foundation for the heleosv2 multi-tenant hosting
platform before any implementation code:

- Monorepo skeleton: docs/, platform-infra/, site-templates/,
  deployments/, control-panel/ with orientation READMEs.
- docs/: roadmap index, architecture + threat model, naming conventions,
  site profiles, provisioning workflow, backup & DR runbook, repo/GitOps
  layout, and the approved architecture plan.
- docs/adr/: 9 ADRs recording the rationale for single-host Compose,
  Traefik edge, nginx+fpm split, shared MariaDB, ZFS-per-customer,
  decoupled backup streams, Forgejo, CLI-first, and SFTP-only.
- Secrets hygiene: .gitignore (only *.enc.* committed) and .gitattributes
  (LF for scripts/Dockerfiles/YAML run on the Linux host).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 12:26:00 +02:00

91 lines
3.6 KiB
Markdown

# 04 — Site Profiles
A **profile** is a standard site stack with a compose template in
`site-templates/`. The provisioning CLI renders a profile with a site's slug,
domains, and options. Four profiles cover all current workloads.
Common to every profile:
- Traefik labels for routing + TLS (Host rule from domains, ACME resolver).
- Joined to the site's own `<slug>_net` network; only Traefik bridges to `proxy`.
- Web root bind-mounted from `tank/customers/<slug>/web`.
- Per-project CPU/memory limits.
- Containers run non-root; rootfs read-only where the profile allows.
| Profile | Web server | PHP | Database | Typical use |
|---------|-----------|-----|----------|-------------|
| `static` | nginx (or Traefik direct) | — | — | HTML/JS sites, landing pages |
| `redirect` | Traefik router / tiny nginx | — | — | Domain redirects |
| `custom-php` | nginx | php-fpm | optional | Bespoke PHP apps |
| `wordpress` | nginx | php-fpm | required | WordPress sites |
---
## `static`
- **Containers:** one nginx serving the web root read-only. For very simple
cases, Traefik can serve files directly with no container.
- **Volumes:** `tank/customers/<slug>/web``/usr/share/nginx/html` (read-only).
- **DB:** none.
- **Notes:** cheapest profile; near-zero backup incrementals when unchanged.
## `redirect`
- **Containers:** none preferred — implemented as a Traefik router rule with a
redirect middleware (`RedirectRegex`/`RedirectScheme`). A tiny nginx is the
fallback if complex rewrite logic is needed.
- **Volumes:** none.
- **DB:** none.
- **Config:** target URL(s) and redirect type (301/302), preserve-path flag —
all in `site.yaml`.
## `custom-php`
- **Containers:**
- `nginx` — serves static assets, proxies `.php` to fpm over FastCGI.
- `php-fpm` — non-root; only the web root (and a small tmp) writable.
- **Volumes:** `tank/customers/<slug>/web` shared by both (web root).
- **DB:** optional — if requested, a `db_<slug>` + `u_<slug>` on shared MariaDB;
credentials injected via env from `secrets.enc.yaml`.
- **Options:** PHP version (pinned image tag), extensions, `php.ini` overrides,
cron (via a scheduled fpm exec) if needed.
## `wordpress`
- **Containers:** same nginx + php-fpm pair as `custom-php`, using a WordPress
base image (or WP installed into the web root on first provision).
- **Volumes:** `tank/customers/<slug>/web` (WordPress core, themes, plugins,
uploads).
- **DB:** **required**`db_<slug>` + `u_<slug>` on shared MariaDB.
- **Hardening baked in:**
- php-fpm non-root; `wp-content/uploads` writable, PHP execution denied there
(nginx rule) to blunt upload-based RCE.
- Read-only rootfs where WordPress tolerates it; XML-RPC restricted; sensible
security headers via Traefik middleware.
- WP-CLI available for provisioning/maintenance (install, update, search-
replace on domain migration).
- **Options:** PHP version, multisite flag, alias domains, initial admin user.
---
## Profile inputs (from `site.yaml`)
Every profile is rendered from the same declarative fields; unused fields are
ignored per profile:
```yaml
slug: example-com
profile: wordpress # static | redirect | custom-php | wordpress
domains: # first is primary → drives cert + slug
- example.com
- www.example.com
php_version: "8.3" # custom-php / wordpress
database: true # custom-php (wordpress forces true)
redirect_to: null # redirect profile only
resources:
cpu: "1.0"
memory: "512m"
```
Adding a profile = adding a template in `site-templates/` and a case in the CLI
renderer. Keep the set small; special cases are options on a profile, not new
profiles.