Deliver the full Docker stack for Bloc 5 DevOps (Cr 7.c.3 and 7.c.4):
- docker/apache/ Custom httpd:2.4-alpine with hardened main config,
MPM event tuning and 3 vhosts (healthz, kiosk static,
admin reverse FCGI to wakdo-app:9000). Kiosk vhost
explicitly denies .php to enforce Bloc 1 isolation.
- docker/php-fpm/ Custom php:8.3-fpm-alpine3.20 with pdo_mysql, opcache,
intl, exif, zip and tini for signal handling.
Dynamic pool 3-10 workers listening on TCP 9000.
- docker/cron/ Custom alpine:3.20 with dcron, mariadb-client, gzip.
Nightly mysqldump at 03h00 with 14-day rotation and
512-byte sanity check. Purge and stats jobs templated.
- docker-compose.yml 4 services orchestrated on 2 networks (internal
bridge + external reverse-proxy). 2 named volumes
for DB and uploads, bind-mount for backups.
Traefik labels for 2 routers with HTTPS redirect.
Makefile adds `make backup` (manual dump) and `make backup-ls`.
.gitignore adds /var/ for backup bind-mount path.
docs/journal/2026-04-24--infra-docker.md documents 5 decisions with
alternatives, maps 16 RNCP criteria to artefacts and prepares 6 jury Q&A.
Validated: `docker compose config --quiet` passes. Smoke test deferred
to next session (requires server .env).
149 lines
5.6 KiB
Text
149 lines
5.6 KiB
Text
#
|
|
# Wakdo - vhosts applicatifs
|
|
#
|
|
# Un seul conteneur Apache derriere Traefik sert **les deux** FQDN :
|
|
# - TRAEFIK_DOMAIN_KIOSK -> /var/www/html/public/borne (borne client, Bloc 1)
|
|
# - TRAEFIK_DOMAIN_ADMIN -> /var/www/html/public/admin (back-office + API, Bloc 2)
|
|
#
|
|
# Comme Traefik termine TLS en amont et communique en HTTP clair avec Apache
|
|
# sur le reseau docker, les vhosts ecoutent sur :80 et font confiance aux
|
|
# headers X-Forwarded-* fournis par Traefik.
|
|
#
|
|
# Le SetHandler "proxy:fcgi://wakdo-app:9000" est le coeur du reverse FastCGI :
|
|
# toute requete *.php est relayee au pool PHP-FPM via TCP sur le reseau interne
|
|
# wakdo_internal. wakdo-app n'est JAMAIS joignable directement depuis l'exterieur.
|
|
#
|
|
|
|
# === Healthcheck interne Docker (non expose publiquement) ===
|
|
# Listen sans ServerName = catch-all. Utilise par le HEALTHCHECK du Dockerfile.
|
|
<VirtualHost *:80>
|
|
DocumentRoot "/var/www/html/public"
|
|
|
|
<Location /healthz>
|
|
SetHandler none
|
|
Require all granted
|
|
</Location>
|
|
|
|
Alias /healthz /dev/null
|
|
<Directory "/var/www/html/public">
|
|
Require all granted
|
|
</Directory>
|
|
|
|
RewriteEngine On
|
|
RewriteRule ^/healthz$ - [R=200,L]
|
|
</VirtualHost>
|
|
|
|
# === Borne client (Bloc 1 - front vanilla HTML/CSS/JS) ===
|
|
<VirtualHost *:80>
|
|
# Hostname injecte par la var d'env TRAEFIK_DOMAIN_KIOSK au runtime.
|
|
ServerName ${TRAEFIK_DOMAIN_KIOSK}
|
|
|
|
DocumentRoot "/var/www/html/public/borne"
|
|
|
|
# Confiance aux headers X-Forwarded-* de Traefik.
|
|
# mod_remoteip pourrait etre active pour restaurer la vraie IP client
|
|
# dans les logs, mais en l'etat le header X-Forwarded-For est loggue
|
|
# dans combined, ce qui suffit pour un projet RNCP.
|
|
<Directory "/var/www/html/public/borne">
|
|
Options -Indexes +FollowSymLinks
|
|
AllowOverride None
|
|
Require all granted
|
|
|
|
# SPA-like fallback : toute URL non-fichier -> index.html
|
|
# (pour permettre de bookmarker un chemin profond dans la borne).
|
|
RewriteEngine On
|
|
RewriteCond %{REQUEST_FILENAME} !-f
|
|
RewriteCond %{REQUEST_FILENAME} !-d
|
|
RewriteRule ^ index.html [L]
|
|
</Directory>
|
|
|
|
# Securite supplementaire : expose uniquement public/borne, pas la racine.
|
|
<Directory "/var/www/html/public/borne/..">
|
|
Require all denied
|
|
</Directory>
|
|
|
|
# Compression text/html, css, js, json (Cr 1.e.8 temps de chargement).
|
|
<IfModule mod_deflate.c>
|
|
AddOutputFilterByType DEFLATE text/html text/css text/javascript \
|
|
application/javascript application/json image/svg+xml
|
|
</IfModule>
|
|
|
|
# Cache long sur les assets versionnes (futur : hash dans le nom de fichier).
|
|
<FilesMatch "\.(jpg|jpeg|png|gif|ico|svg|webp|woff2|woff)$">
|
|
Header set Cache-Control "public, max-age=604800"
|
|
</FilesMatch>
|
|
|
|
# Borne : pas de code PHP execute cote kiosk (Bloc 1 = front only).
|
|
# Si une requete *.php arrive ici, on la refuse pour forcer l'isolation.
|
|
<FilesMatch "\.php$">
|
|
Require all denied
|
|
</FilesMatch>
|
|
|
|
ErrorLog /proc/self/fd/2
|
|
CustomLog /proc/self/fd/1 combined
|
|
</VirtualHost>
|
|
|
|
# === Back-office + API REST (Bloc 2 - PHP from scratch + MVC) ===
|
|
<VirtualHost *:80>
|
|
ServerName ${TRAEFIK_DOMAIN_ADMIN}
|
|
|
|
DocumentRoot "/var/www/html/public/admin"
|
|
|
|
<Directory "/var/www/html/public/admin">
|
|
Options -Indexes +FollowSymLinks
|
|
AllowOverride None
|
|
Require all granted
|
|
|
|
# Front controller MVC : toute requete non-fichier passe par index.php
|
|
# qui dispatche via le Router (src/Core/Router.php a venir en P2).
|
|
RewriteEngine On
|
|
RewriteCond %{REQUEST_FILENAME} !-f
|
|
RewriteCond %{REQUEST_FILENAME} !-d
|
|
RewriteRule ^ index.php [L]
|
|
</Directory>
|
|
|
|
# Reverse FastCGI : toute requete *.php est executee par wakdo-app:9000.
|
|
# Format proxy:fcgi://<host>:<port><path-to-proxy>
|
|
# SetHandler s'applique au chemin courant du <FilesMatch>.
|
|
<FilesMatch "\.php$">
|
|
SetHandler "proxy:fcgi://wakdo-app:9000"
|
|
</FilesMatch>
|
|
|
|
# Protection des fichiers sensibles du projet si jamais un chemin se
|
|
# retrouve sous DocumentRoot par erreur (.env, .git, config/).
|
|
<FilesMatch "^\.(env|git|htaccess)">
|
|
Require all denied
|
|
</FilesMatch>
|
|
<DirectoryMatch "/\.(git|env)">
|
|
Require all denied
|
|
</DirectoryMatch>
|
|
|
|
# CORS : l'API admin sous /api/* doit accepter les requetes venant
|
|
# de la borne kiosk (TRAEFIK_DOMAIN_KIOSK). Wildcard interdit.
|
|
# La vraie valeur vient de CORS_ALLOWED_ORIGIN dans .env, lue cote PHP.
|
|
# Ici on pose juste les headers de prealable OPTIONS.
|
|
<Location /api>
|
|
<IfModule mod_headers.c>
|
|
# Les headers definitifs sont poses par le middleware PHP.
|
|
# Apache ne fait que relayer le preflight sans le casser.
|
|
Header set X-Wakdo-Handled-By "apache-vhost"
|
|
</IfModule>
|
|
</Location>
|
|
|
|
# Compression back-office (HTML admin + JSON API)
|
|
<IfModule mod_deflate.c>
|
|
AddOutputFilterByType DEFLATE text/html text/css text/javascript \
|
|
application/javascript application/json
|
|
</IfModule>
|
|
|
|
# Securite : cookies httpOnly + secure sont forces cote PHP (voir php.ini).
|
|
# Ici on ajoute un header CSP minimal : l'admin n'a pas besoin de charger
|
|
# de resources externes pour un MVP (pas de CDN, pas d'analytics).
|
|
<IfModule mod_headers.c>
|
|
Header always set Content-Security-Policy "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'"
|
|
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
|
</IfModule>
|
|
|
|
ErrorLog /proc/self/fd/2
|
|
CustomLog /proc/self/fd/1 combined
|
|
</VirtualHost>
|