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>
3.2 KiB
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.