platform/site-templates/README.md
Bart Van Geyt edfb4c44b8 Phase 3: site templates & base images
Add the building blocks the provisioning CLI renders per site.

Base images (site-templates/images):
- php-fpm: non-root (www-data) php:<ver>-fpm-alpine with pdo_mysql, mysqli,
  gd, intl, zip, opcache, exif; tuned php.ini + pool; built per PHP version.
- nginx: hardened nginx:1.27-alpine with shared security + fastcgi snippets;
  per-site server block mounted at runtime.
- build.sh: build + Trivy-scan (+ optional push) for both images.

Profile templates (site-templates/profiles), Jinja2 rendered:
- static, redirect (tiny nginx 301/302), custom-php (nginx + our php-fpm,
  optional DB), wordpress (nginx + official wordpress-fpm, DB required,
  upload-exec denied). Only nginx carries Traefik labels and joins proxy;
  php-fpm uses the private <slug>_net and joins platform only when a DB is
  needed. Secrets stay in a git-ignored .env, not the compose.

CI: .forgejo/workflows/images.yml builds/scans images (gitleaks + Trivy).
README documents the render context and the Phase 4 web-root ownership item.

Templates validated: all profiles render to valid compose YAML across the
database on/off branches and single/multi-domain host rules.

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

70 lines
3.2 KiB
Markdown

# site-templates
Building blocks the provisioning CLI (Phase 4) renders into
`deployments/<customer>/<site>/`. Two parts: **base images** and **profile
templates**.
## Images (`images/`)
Hardened base images, built and pushed to the Forgejo registry.
| Image | Base | Notes |
|-------|------|-------|
| `nginx` | `nginx:1.27-alpine` | Hardened `nginx.conf` + reusable snippets (`security.conf`, `fastcgi-php.conf`). Per-site server block mounted at runtime. |
| `php-fpm` | `php:<ver>-fpm-alpine` | **Runs as non-root** (`www-data`). Extensions: pdo_mysql, mysqli, gd, intl, zip, opcache, exif. OPcache tuned. Built per PHP version. |
Build (and Trivy-scan) locally:
```bash
REGISTRY=git.example.com/heleos ./images/build.sh # build + scan
REGISTRY=git.example.com/heleos PUSH=1 ./images/build.sh # also push
```
CI does the same on push — see [`.forgejo/workflows/images.yml`](../.forgejo/workflows/images.yml).
WordPress uses the official `wordpress:<ver>-fpm-alpine` image directly (it
bundles WordPress + php-fpm); only `custom-php` uses our `php-fpm` image.
## Profiles (`profiles/`)
One directory per profile (see
[../docs/04-site-profiles.md](../docs/04-site-profiles.md)). Each has a
`docker-compose.yml.j2` and its nginx config template.
| Profile | Containers | DB | Networks |
|---------|-----------|----|---------|
| `static` | nginx (web root ro) | — | proxy |
| `redirect` | nginx (301/302) | — | proxy |
| `custom-php` | nginx + php-fpm | optional | proxy, site, [platform] |
| `wordpress` | nginx + wordpress-fpm | required | proxy, site, platform |
Routing pattern (all profiles): only **nginx** carries Traefik labels and joins
`proxy`; php-fpm joins the private `site` network (`<slug>_net`) and, when a DB
is used, the shared internal `platform` network to reach MariaDB. nginx reaches
php-fpm at `fpm:9000`.
## Render context
The CLI renders the `.j2` files with these variables (source: the site's
`site.yaml` + platform config):
| Variable | Example | Meaning |
|----------|---------|---------|
| `customer` / `site` | `acme` / `shop` | Ids. |
| `slug` | `acme-shop` | `<customer>-<site>`; Docker/router key. |
| `slug_underscored` | `acme_shop` | For DB names (`db_`, `u_`). |
| `domains` | `[shop.acme.com]` | Host rule is `Host(\`d1\`) || Host(\`d2\`)…` |
| `webroot` | `/tank/customers/acme/shop/web` | Bind-mounted web root. |
| `database` | `true`/`false` | custom-php only; wordpress is always true. |
| `redirect_to` / `redirect_code` | `https://acme.com` / `301` | redirect profile. |
| `nginx_image` / `php_image` / `wordpress_image` | `git.example.com/heleos/nginx:latest` | Resolved image refs. |
| `resources.cpu` / `resources.memory` | `1.0` / `512m` | Per-project limits. |
Secrets (DB password) are **not** rendered into the compose file — they go into a
git-ignored `.env` (from `secrets.enc.yaml`) that the compose reads via
`env_file`.
## Provisioning note (for Phase 4)
The web root dataset must be writable by the php-fpm user (`www-data`, uid 82 in
these alpine images) **and** by the customer's SFTP user. Provisioning will set
web-root ownership/permissions accordingly (e.g. shared group + setgid) — this is
an open item to finalize in the Phase 4 CLI, not baked into these templates.