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>
31 lines
731 B
Django/Jinja
31 lines
731 B
Django/Jinja
server {
|
|
listen 80 default_server;
|
|
root /var/www/html;
|
|
index index.php;
|
|
|
|
include /etc/nginx/snippets/security.conf;
|
|
|
|
# WordPress pretty permalinks.
|
|
location / {
|
|
try_files $uri $uri/ /index.php?$args;
|
|
}
|
|
|
|
location ~ \.php$ {
|
|
include /etc/nginx/snippets/fastcgi-php.conf;
|
|
fastcgi_pass fpm:9000;
|
|
}
|
|
|
|
# Never execute PHP uploaded into wp-content/uploads (blunts upload RCE).
|
|
location ~* /wp-content/uploads/.*\.php$ {
|
|
deny all;
|
|
}
|
|
|
|
# Reduce attack surface / noise.
|
|
location = /xmlrpc.php { deny all; }
|
|
|
|
# Long-cache static assets.
|
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
|
|
expires 30d;
|
|
access_log off;
|
|
}
|
|
}
|