platform/site-templates
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
..
images Phase 3: site templates & base images 2026-07-07 17:06:25 +02:00
profiles Phase 3: site templates & base images 2026-07-07 17:06:25 +02:00
README.md Phase 3: site templates & base images 2026-07-07 17:06:25 +02:00

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:

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.

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). 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`)
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.