chore(docker): docker-compose.yml standalone portable + .env local-first (#41)
This commit is contained in:
parent
e613adc24a
commit
9d75fabdca
5 changed files with 78 additions and 323 deletions
119
.env.example
119
.env.example
|
|
@ -3,17 +3,13 @@
|
|||
#
|
||||
# Usage :
|
||||
# cp .env.example .env
|
||||
# Editer .env (gitignore) avec les valeurs reelles.
|
||||
# docker compose up -d
|
||||
#
|
||||
# Audience :
|
||||
# Destine a l'auteur, au jury et aux contributeurs futurs.
|
||||
#
|
||||
# Modele de deploiement :
|
||||
# Ce projet tourne sur serveur derriere un reverse proxy Traefik. Il n'y a
|
||||
# pas de binding de ports hote : l'acces se fait uniquement via les FQDN
|
||||
# configures ci-dessous et routes par Traefik (reseau admin_proxy).
|
||||
# Les distinctions dev / staging / prod se font par FQDN distincts
|
||||
# (ex : .dev.acadenice.fr vs .acadenice.fr) et par .env dedie.
|
||||
# Ce template fonctionne EN LOCAL tel quel (valeurs dev) : la stack est joignable
|
||||
# sur http://kiosk.localhost:8080 (borne) et http://admin.localhost:8080 (admin).
|
||||
# Le deploiement derriere un reverse proxy (Traefik) se fait via l'overlay
|
||||
# docker-compose.prod.yml + les variables du bloc "Deploiement prod" en fin de
|
||||
# fichier. En prod : changer les mots de passe, APP_DEBUG=false, vrais FQDN.
|
||||
#
|
||||
|
||||
# ===================================================================
|
||||
|
|
@ -24,27 +20,30 @@ APP_ENV=dev # dev | staging | prod
|
|||
APP_DEBUG=true # true en dev, false en prod
|
||||
APP_TIMEZONE=Europe/Paris
|
||||
|
||||
# URL publique de la borne (Bloc 1), doit pointer vers le FQDN Traefik.
|
||||
# Placeholder example.com (RFC 2606) - a remplacer par le FQDN reel.
|
||||
APP_URL_KIOSK=https://kiosk.example.com
|
||||
# Port hote publie par wakdo-web (acces local). Change si 8080 est pris.
|
||||
HTTP_PORT=8080
|
||||
|
||||
# URL publique du back-office + API (Bloc 2).
|
||||
# Placeholder example.com (RFC 2606) - a remplacer par le FQDN reel.
|
||||
APP_URL_ADMIN=https://admin.example.com
|
||||
# Hostnames des deux vhosts Apache (ServerName). En local : *.localhost resout
|
||||
# vers 127.0.0.1 nativement. En prod : les vrais FQDN (voir bloc prod en bas).
|
||||
APP_HOST_KIOSK=kiosk.localhost
|
||||
APP_HOST_ADMIN=admin.localhost
|
||||
|
||||
# URLs publiques (consommees par l'app). En local = les hostnames sur HTTP_PORT.
|
||||
APP_URL_KIOSK=http://kiosk.localhost:8080
|
||||
APP_URL_ADMIN=http://admin.localhost:8080
|
||||
|
||||
# ===================================================================
|
||||
# Base de donnees (MariaDB)
|
||||
# ===================================================================
|
||||
# Valeurs ci-dessous = PLACEHOLDERS. Remplacer par des mots de passe forts.
|
||||
# Pas accessible depuis l'exterieur : le service wakdo-db est sur le reseau
|
||||
# interne uniquement, aucun port exposé a l'hote.
|
||||
# Valeurs dev ci-dessous : OK en local. EN PROD : mots de passe forts.
|
||||
# wakdo-db est sur le reseau interne, aucun port expose a l'hote.
|
||||
|
||||
DB_HOST=wakdo-db # nom du service docker-compose
|
||||
DB_PORT=3306
|
||||
DB_NAME=wakdo
|
||||
DB_USER=wakdo
|
||||
DB_PASSWORD=change_me_strong_password
|
||||
DB_ROOT_PASSWORD=change_me_root_password
|
||||
DB_PASSWORD=wakdo_dev_password
|
||||
DB_ROOT_PASSWORD=wakdo_dev_root_password
|
||||
|
||||
# ===================================================================
|
||||
# Sessions
|
||||
|
|
@ -58,18 +57,15 @@ SESSION_NAME=WAKDO_SID # nom du cookie (evite PHPSESSID)
|
|||
# Securite
|
||||
# ===================================================================
|
||||
|
||||
# Origine autorisee pour les requetes CORS de l'API.
|
||||
# Doit correspondre exactement a APP_URL_KIOSK (pas de wildcard).
|
||||
CORS_ALLOWED_ORIGIN=https://kiosk.example.com
|
||||
# Origine autorisee pour les requetes CORS de l'API. Doit correspondre
|
||||
# exactement a APP_URL_KIOSK (pas de wildcard).
|
||||
CORS_ALLOWED_ORIGIN=http://kiosk.localhost:8080
|
||||
|
||||
# Algorithme de hashage : argon2id, FIXE dans le code (App\Auth\PasswordHasher),
|
||||
# choix security-by-design non configurable (pas de bascule runtime vers un algo
|
||||
# plus faible). Seuls les COUTS ci-dessous sont reglables.
|
||||
# choix security-by-design non configurable. Seuls les COUTS ci-dessous sont reglables.
|
||||
|
||||
# Parametres de cout argon2id (password_hash options).
|
||||
# Defauts alignes sur les recommandations OWASP Password Storage Cheat Sheet
|
||||
# (memoire >= 19 MiB, >= 2 iterations). 65536 KiB = 64 MiB, marge confortable.
|
||||
# Ces valeurs servent aussi au hash du PIN equipier (pin_hash, actions sensibles).
|
||||
# Parametres de cout argon2id (password_hash options). Defauts alignes OWASP
|
||||
# (memoire >= 19 MiB, >= 2 iterations). Servent aussi au hash du PIN equipier.
|
||||
ARGON2_MEMORY_COST=65536 # KiB (64 MiB)
|
||||
ARGON2_TIME_COST=4 # nombre d'iterations
|
||||
ARGON2_THREADS=1 # parallelisme (1 = portable, deterministe)
|
||||
|
|
@ -77,49 +73,38 @@ ARGON2_THREADS=1 # parallelisme (1 = portable, determini
|
|||
# ===================================================================
|
||||
# Anti brute-force - throttling de connexion (security-by-design)
|
||||
# ===================================================================
|
||||
# Deux gardes complementaires (cf. docs/merise/mld.md 4.21 + PROJECT_CONTEXT 19) :
|
||||
# 1. par compte : colonnes user.failed_login_attempts / user.lockout_until
|
||||
# 2. par IP source : table login_throttle (entite 21)
|
||||
# Backoff degressif (pas de lock definitif) : evite le DoS sur compte legitime.
|
||||
# Deux gardes : par compte (user.failed_login_attempts / lockout_until) et par IP
|
||||
# (table login_throttle). Backoff degressif, pas de lock definitif.
|
||||
|
||||
# Compteur par compte : nombre d'echecs avant declenchement du backoff.
|
||||
ACCOUNT_LOCKOUT_THRESHOLD=5
|
||||
# Duree de base du backoff (secondes). Croit de facon degressive a chaque
|
||||
# palier d'echecs supplementaires (ex : 60 -> 300 -> 900).
|
||||
ACCOUNT_LOCKOUT_BASE_SECONDS=60
|
||||
ACCOUNT_LOCKOUT_MAX_SECONDS=900 # plafond du backoff (15 min)
|
||||
|
||||
# Gate par IP : fenetre glissante et plafond de tentatives sur cette fenetre.
|
||||
IP_THROTTLE_WINDOW_SECONDS=900 # 15 min
|
||||
IP_THROTTLE_MAX_ATTEMPTS=20 # par IP sur la fenetre
|
||||
|
||||
# PIN equipier pour actions sensibles (annulation, override). Chiffres uniquement,
|
||||
# bornes min ET max (RG-T18 : validation serveur + longueur bornee).
|
||||
# PIN equipier pour actions sensibles. Chiffres, bornes min ET max (RG-T18).
|
||||
STAFF_PIN_MIN_LENGTH=4
|
||||
STAFF_PIN_MAX_LENGTH=12
|
||||
|
||||
# Throttle du PIN d'action sensible (RG-T22) - compteurs SEPARES du login : la
|
||||
# dimension est l'utilisateur AGISSANT (session), pas l'email cible ni l'IP. Bornes
|
||||
# volontairement plus permissives que le login (controle de dissuasion) : ne pas
|
||||
# bloquer un manager en plein rush sur quelques fautes de frappe.
|
||||
PIN_THROTTLE_THRESHOLD=5 # echecs avant le backoff (par acteur)
|
||||
PIN_THROTTLE_BASE_SECONDS=30 # 1er palier (vs 60 au login)
|
||||
PIN_THROTTLE_MAX_SECONDS=300 # plafond du backoff (5 min, vs 900 au login)
|
||||
PIN_THROTTLE_WINDOW_SECONDS=900 # fenetre glissante (15 min)
|
||||
# Throttle du PIN d'action sensible (RG-T22) - compteurs SEPARES du login,
|
||||
# dimension = utilisateur agissant. Bornes plus permissives que le login.
|
||||
PIN_THROTTLE_THRESHOLD=5
|
||||
PIN_THROTTLE_BASE_SECONDS=30
|
||||
PIN_THROTTLE_MAX_SECONDS=300
|
||||
PIN_THROTTLE_WINDOW_SECONDS=900
|
||||
|
||||
# Expiration du token de reinitialisation de mot de passe (secondes).
|
||||
PASSWORD_RESET_TTL=3600 # 1h
|
||||
|
||||
# ===================================================================
|
||||
# Retention des donnees (RGPD - minimisation et limitation de conservation)
|
||||
# Retention des donnees (RGPD)
|
||||
# ===================================================================
|
||||
# Purges executees par le service cron (docker/cron/crontab).
|
||||
# Justification documentee : interet legitime / obligations probatoires.
|
||||
|
||||
AUDIT_LOG_RETENTION_DAYS=365 # journal d'audit ~12 mois
|
||||
THROTTLE_PURGE_AFTER_HOURS=24 # login_throttle : lignes sans lockout actif > 24h
|
||||
ORDER_RETENTION_DAYS=1095 # commandes (historique/stats) ~3 ans
|
||||
# Purge des sessions expirees : deja geree par le cron */15 (voir crontab).
|
||||
|
||||
# ===================================================================
|
||||
# Upload images produits
|
||||
|
|
@ -131,34 +116,18 @@ UPLOAD_ALLOWED_MIME=image/jpeg,image/png,image/webp
|
|||
# ===================================================================
|
||||
# Cron (fenetre de maintenance 01h30 - 09h30)
|
||||
# ===================================================================
|
||||
# Les jobs sont definis dans docker/cron/crontab. Ici uniquement le TZ.
|
||||
|
||||
CRON_TIMEZONE=Europe/Paris
|
||||
|
||||
# ===================================================================
|
||||
# Exposition via Traefik
|
||||
# Deploiement prod (overlay docker-compose.prod.yml) - OPTIONNEL
|
||||
# ===================================================================
|
||||
# FQDN consommes par les labels docker-compose.yml pour generer les routes
|
||||
# Traefik et les certificats TLS (Traefik gere le resolver par defaut).
|
||||
# Le Traefik de l'hote prend en charge Let's Encrypt automatiquement.
|
||||
|
||||
TRAEFIK_DOMAIN_KIOSK=kiosk.example.com
|
||||
TRAEFIK_DOMAIN_ADMIN=admin.example.com
|
||||
|
||||
# ===================================================================
|
||||
# Reseau Docker externe du reverse proxy
|
||||
# ===================================================================
|
||||
# Nom du reseau Docker externe auquel le service web doit se connecter
|
||||
# pour etre expose par le reverse proxy de l'hote.
|
||||
# A ignorer pour un usage local. Necessaire UNIQUEMENT derriere un reverse proxy
|
||||
# Traefik, avec : docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
||||
#
|
||||
# Adapter selon votre infrastructure. Valeurs courantes :
|
||||
# traefik_proxy - convention neutre (placeholder)
|
||||
# traefik_public - convention doc Traefik
|
||||
# traefik - setups simples
|
||||
# proxy - autre convention frequente
|
||||
# En prod, surcharger aussi : APP_ENV=prod, APP_DEBUG=false, mots de passe forts,
|
||||
# et APP_HOST_*/APP_URL_*/CORS_ALLOWED_ORIGIN avec les vrais FQDN HTTPS.
|
||||
#
|
||||
# Le reseau doit exister AVANT 'docker compose up' (cree par votre stack de
|
||||
# reverse proxy, ou manuellement : docker network create <nom>). Sinon
|
||||
# 'docker compose up' echoue avec une erreur sur le reseau externe introuvable.
|
||||
|
||||
REVERSE_PROXY_NETWORK=traefik_proxy
|
||||
# Nom du reseau Docker externe partage avec le Traefik de l'hote (doit exister
|
||||
# AVANT le up : cree par la stack Traefik, ou `docker network create <nom>`).
|
||||
REVERSE_PROXY_NETWORK=admin_proxy
|
||||
|
|
|
|||
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -5,6 +5,11 @@
|
|||
*.pem
|
||||
*.key
|
||||
|
||||
# Compose de production (propre a chaque hote : Traefik/reverse proxy, FQDN).
|
||||
# Le repo ne ship que docker-compose.yml (standalone, local). Chaque hote derriere
|
||||
# un proxy maintient son propre docker-compose.prod.yml (hors versionnement).
|
||||
docker-compose.prod.yml
|
||||
|
||||
# === BYAN — plateforme (moteur), masquee ===
|
||||
# Le code moteur des agents n'est pas part du rendu RNCP.
|
||||
# La methodologie appliquee (CLAUDE.md + rules + hooks) reste dans .claude/
|
||||
|
|
|
|||
77
README.md
77
README.md
|
|
@ -89,80 +89,41 @@ Reseaux, volumes, services et decoupage reseau interne / reseau proxy : voir `do
|
|||
|
||||
---
|
||||
|
||||
## Quickstart
|
||||
|
||||
Ce projet tourne **sur serveur derriere un reverse proxy Traefik** : pas de binding de ports hote, pas d'acces `localhost`. L'acces public se fait par FQDN HTTPS (TLS gere automatiquement par Traefik). Les environnements `dev`, `staging` et `prod` se distinguent par des FQDN et des fichiers `.env` separes.
|
||||
|
||||
### Prerequis sur l'hote
|
||||
|
||||
1. Docker Engine + docker compose v2 (voir ci-dessous)
|
||||
2. Un reverse proxy Traefik deja en place, avec un reseau Docker externe dedie. Le **nom du reseau** est configurable via la variable `REVERSE_PROXY_NETWORK` du `.env` (defaut : `admin_proxy` — convention de l'auteur). A adapter a votre infrastructure.
|
||||
3. Les FQDN cibles pointent en DNS vers l'hote
|
||||
|
||||
### Sur un hote deja equipe (Docker + Traefik)
|
||||
## Quickstart (local)
|
||||
|
||||
```bash
|
||||
git clone git@github.com:AcadeNice/wakdo_corentin.git
|
||||
cd wakdo_corentin
|
||||
cp .env.example .env
|
||||
# Editer .env : DB_PASSWORD, DB_ROOT_PASSWORD, APP_URL_*, TRAEFIK_DOMAIN_*
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
> **Attention au `.env` pre-existant.** Si un fichier `.env` existe deja a la racine (tooling externe, autre plateforme installee dans le meme repertoire), **ne pas faire** `cp .env.example .env` — cela ecraserait les variables existantes. Faire un **merge manuel** a la place : ajouter les variables manquantes du template dans le `.env` actuel. Les prefixes de variables de ce projet (`APP_`, `DB_`, `SESSION_`, `CORS_`, `UPLOAD_`, `CRON_`, `TRAEFIK_`, `REVERSE_PROXY_`) sont disjoints de ceux utilises par des outils tiers courants, donc la cohabitation est safe.
|
||||
Une seule commande lance la stack complete (Cr 7.c.4) : le service one-shot
|
||||
`wakdo-migrate` applique les migrations puis le seed (idempotents, tables de suivi
|
||||
`schema_migrations` / `seeds_applied`) avant que l'app ne serve. Ensuite :
|
||||
|
||||
Critere RNCP Cr 7.c.4 couvert : une seule commande (`docker compose up -d`) lance la
|
||||
stack complete. Le service one-shot `wakdo-migrate` applique le schema (migrations)
|
||||
puis les donnees de reference (seed) avant que l'app ne serve ; `wakdo-app`/`wakdo-web`
|
||||
attendent sa completion (`depends_on: service_completed_successfully`). Migrations et
|
||||
seed sont idempotents (tables de suivi `schema_migrations` / `seeds_applied`).
|
||||
Detail : `docs/journal/2026-06-17--makefile-to-compose-migrate.md`.
|
||||
- Borne : http://kiosk.localhost:8080
|
||||
- Admin + API : http://admin.localhost:8080
|
||||
|
||||
Services accessibles apres `docker compose up -d` :
|
||||
- Borne : la valeur de `TRAEFIK_DOMAIN_KIOSK` dans `.env`
|
||||
- Admin + API : la valeur de `TRAEFIK_DOMAIN_ADMIN` dans `.env`
|
||||
`*.localhost` resout vers `127.0.0.1` nativement ; changer le port via `HTTP_PORT`
|
||||
dans `.env`. Le `.env.example` fonctionne tel quel en local (valeurs dev).
|
||||
|
||||
### Installation Docker sur un hote neuf (Debian / Ubuntu)
|
||||
Docker non installe ? Voir https://docs.docker.com/engine/install/
|
||||
|
||||
Procedure officielle detaillee : `https://docs.docker.com/engine/install/` (selectionner la distribution). Resume pour Debian stable :
|
||||
### Deploiement prod (derriere un reverse proxy Traefik)
|
||||
|
||||
Le repo ne ship que `docker-compose.yml` (standalone). En production derriere un
|
||||
reverse proxy, chaque hote maintient son **propre `docker-compose.prod.yml`**
|
||||
(gitignore, hors repo, comme `.env`) : meme stack, mais exposee via Traefik (reseau
|
||||
externe + labels TLS) au lieu d'un port hote.
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install -y ca-certificates curl
|
||||
sudo install -m 0755 -d /etc/apt/keyrings
|
||||
sudo curl -fsSL https://download.docker.com/linux/debian/gpg \
|
||||
-o /etc/apt/keyrings/docker.asc
|
||||
sudo chmod a+r /etc/apt/keyrings/docker.asc
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
|
||||
https://download.docker.com/linux/debian \
|
||||
$(. /etc/os-release && echo $VERSION_CODENAME) stable" \
|
||||
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
sudo apt update
|
||||
sudo apt install -y docker-ce docker-ce-cli containerd.io \
|
||||
docker-buildx-plugin docker-compose-plugin
|
||||
sudo usermod -aG docker $USER
|
||||
# Fermer et rouvrir la session pour activer le groupe docker
|
||||
docker compose -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
### Reseau externe du reverse proxy
|
||||
|
||||
Le `docker-compose.yml` attend un reseau Docker externe deja existant sur l'hote, dont le nom est donne par la variable `REVERSE_PROXY_NETWORK` (defaut : `admin_proxy`).
|
||||
|
||||
Si vous avez deja un Traefik en place, ce reseau a generalement ete cree par son propre stack. Adaptez la variable `REVERSE_PROXY_NETWORK` dans votre `.env` au nom utilise par votre proxy. Sinon, creez-le manuellement :
|
||||
|
||||
```bash
|
||||
docker network create mon_reseau_proxy
|
||||
# puis dans .env :
|
||||
# REVERSE_PROXY_NETWORK=mon_reseau_proxy
|
||||
```
|
||||
|
||||
Avant le premier `docker compose up`, s'assurer que le reseau existe. Verification rapide :
|
||||
|
||||
```bash
|
||||
docker network inspect "$(grep ^REVERSE_PROXY_NETWORK .env | cut -d= -f2)"
|
||||
```
|
||||
|
||||
Si la commande retourne une erreur, soit adapter `REVERSE_PROXY_NETWORK` au nom du reseau utilise par votre proxy, soit creer le reseau manuellement (le reseau externe doit exister avant `docker compose up`).
|
||||
Avec un `.env` adapte : `APP_ENV=prod`, `APP_DEBUG=false`, mots de passe forts,
|
||||
`APP_HOST_*` / `APP_URL_*` / `CORS_ALLOWED_ORIGIN` en vrais FQDN HTTPS, et
|
||||
`REVERSE_PROXY_NETWORK` = reseau Docker du Traefik de l'hote (doit exister avant le up).
|
||||
|
||||
*Section mise a jour au fil de l'implementation (migrations reelles, seed, CI/CD deploiement).*
|
||||
|
||||
|
|
|
|||
|
|
@ -1,87 +1,19 @@
|
|||
#
|
||||
# Wakdo - orchestration des 4 services de la stack (Bloc 5 DevOps, Cr 7.c.3 / 7.c.4).
|
||||
#
|
||||
# Services :
|
||||
# wakdo-web : Apache httpd, reverse proxy FastCGI -> wakdo-app, expose 80 via Traefik
|
||||
# wakdo-app : PHP-FPM 8.3, execute le code PHP back-office + API
|
||||
# wakdo-db : MariaDB 11.4, persistance des donnees metier
|
||||
# wakdo-cron : Alpine + dcron, backups nocturnes et taches planifiees
|
||||
#
|
||||
# Reseaux :
|
||||
# default (nom = wakdo_internal) : bridge interne pour l'inter-service,
|
||||
# non expose a l'hote. app/db/cron ne sont PAS joignables publiquement.
|
||||
# <REVERSE_PROXY_NETWORK> : reseau externe (ex. traefik_proxy) partage
|
||||
# avec le Traefik de l'hote. Seul wakdo-web y est attache.
|
||||
#
|
||||
# Volumes :
|
||||
# wakdo_db_data : named volume pour /var/lib/mysql (persistance BDD)
|
||||
# wakdo_uploads : named volume pour les uploads produits back-office
|
||||
# ./var/backups : bind-mount lecture-ecriture pour les dumps SQL
|
||||
#
|
||||
# Variables d'env consommees depuis .env : docker compose lit automatiquement le
|
||||
# fichier .env a la racine et fait l'expansion des ${...} ci-dessous.
|
||||
#
|
||||
# Persistance : `docker compose down` preserve les named volumes ; seul
|
||||
# `docker compose down -v` supprime les donnees.
|
||||
#
|
||||
|
||||
name: wakdo
|
||||
|
||||
networks:
|
||||
# Reseau interne : isolement des services applicatifs.
|
||||
# wakdo-app, wakdo-db et wakdo-cron n'y sont accessibles que via les autres
|
||||
# conteneurs de la stack. Aucun port hote expose.
|
||||
wakdo_internal:
|
||||
driver: bridge
|
||||
internal: false
|
||||
# internal: false (par defaut) car wakdo-app et wakdo-cron ont besoin
|
||||
# de sortir sur internet (pour telecharger des packages au build et pour
|
||||
# de potentiels appels API externes futurs). L'isolation vient du fait
|
||||
# qu'aucun port hote n'est binde ici.
|
||||
#
|
||||
# Subnet explicite (RFC 1918) : l'auto-allocateur Docker du daemon hote
|
||||
# est sature (15 /16 + 15 /20 deja alloues par d'autres stacks), il ne
|
||||
# peut plus creer de reseau bridge sans subnet explicite. 192.168.148.0/24
|
||||
# est dans le gap libre 192.168.144-159 (256 IP, largement suffisant pour
|
||||
# 4 services), aucune collision avec les /24 acquagest voisins (150/154/
|
||||
# 155/157). Choix defendable : right-sizing + isolation des fluctuations
|
||||
# d'allocation auto sur cet hote mutualise.
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 192.168.148.0/24
|
||||
|
||||
# Reseau du reverse proxy (Traefik) pre-existant sur l'hote.
|
||||
# Son nom est configurable via REVERSE_PROXY_NETWORK dans .env pour
|
||||
# supporter differents setups (traefik_proxy, traefik_public, proxy, ...).
|
||||
reverse_proxy:
|
||||
name: ${REVERSE_PROXY_NETWORK}
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
wakdo_db_data:
|
||||
# Named volume MariaDB. Permissions gerees par Docker (UID mysql=999
|
||||
# dans le conteneur), zero souci cote hote. Survit a `docker compose down`.
|
||||
# Pour remise a zero : `docker compose down -v` (destructif : supprime les volumes).
|
||||
|
||||
wakdo_uploads:
|
||||
# Images produits uploadees par les equipiers depuis le back-office.
|
||||
# Named volume pour les memes raisons que wakdo_db_data : permissions
|
||||
# propres (www-data UID 82 en Alpine) et pas de pollution du repo git
|
||||
# par des binaires.
|
||||
|
||||
services:
|
||||
|
||||
# =======================================================================
|
||||
# wakdo-db : MariaDB 11.4 LTS
|
||||
# =======================================================================
|
||||
wakdo-db:
|
||||
image: mariadb:11.4
|
||||
container_name: wakdo-db
|
||||
restart: unless-stopped
|
||||
|
||||
# Variables d'env d'initialisation (ne s'appliquent qu'au premier demarrage
|
||||
# sur volume vide - voir docs/notes/docker-volumes-vs-bind-mounts.md).
|
||||
environment:
|
||||
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
|
||||
MARIADB_DATABASE: ${DB_NAME}
|
||||
|
|
@ -89,22 +21,11 @@ services:
|
|||
MARIADB_PASSWORD: ${DB_PASSWORD}
|
||||
MARIADB_AUTO_UPGRADE: "1"
|
||||
TZ: ${APP_TIMEZONE:-Europe/Paris}
|
||||
|
||||
volumes:
|
||||
- wakdo_db_data:/var/lib/mysql
|
||||
# Scripts d'init MariaDB, executes UNIQUEMENT au premier demarrage sur
|
||||
# volume vierge : durcit le privilege du user applicatif (moindre
|
||||
# privilege, db/init/10-scope-app-user.sh). Lecture seule.
|
||||
- ./db/init:/docker-entrypoint-initdb.d:ro
|
||||
|
||||
networks:
|
||||
- wakdo_internal
|
||||
|
||||
# Pas de ports exposes a l'hote : seuls wakdo-app et wakdo-cron peuvent
|
||||
# joindre wakdo-db:3306 via le reseau interne.
|
||||
|
||||
# Healthcheck officiel fourni par l'image mariadb : le script bundled
|
||||
# healthcheck.sh teste la connexion et l'init innodb.
|
||||
healthcheck:
|
||||
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||
interval: 10s
|
||||
|
|
@ -112,48 +33,30 @@ services:
|
|||
retries: 6
|
||||
start_period: 30s
|
||||
|
||||
# =======================================================================
|
||||
# wakdo-migrate : service ONE-SHOT (migrations + seed idempotents)
|
||||
# =======================================================================
|
||||
# Tourne une fois apres que wakdo-db soit healthy, applique le schema puis les
|
||||
# donnees de reference (idempotent via schema_migrations / seeds_applied), et
|
||||
# sort. app/web attendent sa COMPLETION : `docker compose up` rend ainsi la stack
|
||||
# complete + utilisable en UNE commande, sans dependance a l'hote (Cr 7.c.4).
|
||||
wakdo-migrate:
|
||||
image: mariadb:11.4
|
||||
container_name: wakdo-migrate
|
||||
restart: "no"
|
||||
|
||||
environment:
|
||||
DB_HOST: ${DB_HOST}
|
||||
DB_PORT: ${DB_PORT}
|
||||
DB_NAME: ${DB_NAME}
|
||||
# Root : migrations = DDL, seeds = INSERT de reference.
|
||||
DB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
|
||||
|
||||
volumes:
|
||||
# migrations + seeds + le runner, en lecture seule.
|
||||
- ./db:/db:ro
|
||||
|
||||
networks:
|
||||
- wakdo_internal
|
||||
|
||||
depends_on:
|
||||
wakdo-db:
|
||||
condition: service_healthy
|
||||
|
||||
entrypoint: ["bash", "/db/migrate-container.sh"]
|
||||
|
||||
# =======================================================================
|
||||
# wakdo-app : PHP-FPM 8.3 (execute le code back-office + API)
|
||||
# =======================================================================
|
||||
wakdo-app:
|
||||
build:
|
||||
context: ./docker/php-fpm
|
||||
dockerfile: Dockerfile
|
||||
container_name: wakdo-app
|
||||
restart: unless-stopped
|
||||
|
||||
environment:
|
||||
APP_ENV: ${APP_ENV}
|
||||
APP_DEBUG: ${APP_DEBUG}
|
||||
|
|
@ -169,80 +72,50 @@ services:
|
|||
SESSION_LIFETIME_ABSOLUTE: ${SESSION_LIFETIME_ABSOLUTE}
|
||||
SESSION_NAME: ${SESSION_NAME}
|
||||
CORS_ALLOWED_ORIGIN: ${CORS_ALLOWED_ORIGIN}
|
||||
# Cout argon2id (password_hash) : aligne sur .env.example / OWASP. Sert au
|
||||
# hash du mot de passe ET du PIN equipier (actions sensibles, P3).
|
||||
ARGON2_MEMORY_COST: ${ARGON2_MEMORY_COST}
|
||||
ARGON2_TIME_COST: ${ARGON2_TIME_COST}
|
||||
ARGON2_THREADS: ${ARGON2_THREADS}
|
||||
# Anti brute-force : backoff degressif par compte (user.lockout_until) et
|
||||
# par IP source (table login_throttle). Voir mlt.md 12.1 RG-8/RG-9.
|
||||
ACCOUNT_LOCKOUT_THRESHOLD: ${ACCOUNT_LOCKOUT_THRESHOLD}
|
||||
ACCOUNT_LOCKOUT_BASE_SECONDS: ${ACCOUNT_LOCKOUT_BASE_SECONDS}
|
||||
ACCOUNT_LOCKOUT_MAX_SECONDS: ${ACCOUNT_LOCKOUT_MAX_SECONDS}
|
||||
IP_THROTTLE_WINDOW_SECONDS: ${IP_THROTTLE_WINDOW_SECONDS}
|
||||
IP_THROTTLE_MAX_ATTEMPTS: ${IP_THROTTLE_MAX_ATTEMPTS}
|
||||
# Bornes du PIN equipier (actions sensibles, P3) : longueur min ET max.
|
||||
STAFF_PIN_MIN_LENGTH: ${STAFF_PIN_MIN_LENGTH}
|
||||
STAFF_PIN_MAX_LENGTH: ${STAFF_PIN_MAX_LENGTH}
|
||||
# Throttle du PIN d'action sensible (RG-T22) : compteurs SEPARES du login.
|
||||
PIN_THROTTLE_THRESHOLD: ${PIN_THROTTLE_THRESHOLD}
|
||||
PIN_THROTTLE_BASE_SECONDS: ${PIN_THROTTLE_BASE_SECONDS}
|
||||
PIN_THROTTLE_MAX_SECONDS: ${PIN_THROTTLE_MAX_SECONDS}
|
||||
PIN_THROTTLE_WINDOW_SECONDS: ${PIN_THROTTLE_WINDOW_SECONDS}
|
||||
# Expiration du token de reinitialisation de mot de passe (mlt.md 12.3).
|
||||
PASSWORD_RESET_TTL: ${PASSWORD_RESET_TTL}
|
||||
UPLOAD_MAX_SIZE_MB: ${UPLOAD_MAX_SIZE_MB}
|
||||
UPLOAD_ALLOWED_MIME: ${UPLOAD_ALLOWED_MIME}
|
||||
|
||||
volumes:
|
||||
# Bind-mount du code source pour le hot-reload en dev.
|
||||
# En prod, cette ligne est remplacee par un COPY dans l'image
|
||||
# via docker-compose.prod.yml (override a venir en P7).
|
||||
- ./src:/var/www/html
|
||||
|
||||
# Named volume pour les uploads, plus specifique que le bind-mount
|
||||
# parent : les uploads ne vont pas dans ./src, ils restent dans le
|
||||
# volume Docker et survivent aux `docker compose down`.
|
||||
- wakdo_uploads:/var/www/html/public/uploads
|
||||
|
||||
networks:
|
||||
- wakdo_internal
|
||||
|
||||
depends_on:
|
||||
# Le schema + les donnees de reference sont en place AVANT que l'app serve.
|
||||
wakdo-migrate:
|
||||
condition: service_completed_successfully
|
||||
wakdo-db:
|
||||
condition: service_healthy
|
||||
|
||||
# Healthcheck defini dans le Dockerfile (php -r exit 0).
|
||||
|
||||
# =======================================================================
|
||||
# wakdo-web : Apache httpd (reverse FastCGI vers wakdo-app, expose via Traefik)
|
||||
# =======================================================================
|
||||
wakdo-web:
|
||||
build:
|
||||
context: ./docker/apache
|
||||
dockerfile: Dockerfile
|
||||
container_name: wakdo-web
|
||||
restart: unless-stopped
|
||||
|
||||
environment:
|
||||
# Noms de domaine injectes dans les vhosts (ServerName ${TRAEFIK_DOMAIN_*}).
|
||||
TRAEFIK_DOMAIN_KIOSK: ${TRAEFIK_DOMAIN_KIOSK}
|
||||
TRAEFIK_DOMAIN_ADMIN: ${TRAEFIK_DOMAIN_ADMIN}
|
||||
|
||||
APP_HOST_KIOSK: ${APP_HOST_KIOSK}
|
||||
APP_HOST_ADMIN: ${APP_HOST_ADMIN}
|
||||
ports:
|
||||
- "${HTTP_PORT:-8080}:80"
|
||||
volumes:
|
||||
# Meme bind-mount que wakdo-app : Apache sert les fichiers statiques
|
||||
# (HTML, CSS, JS, images) depuis ce dossier. PHP ne passe pas par
|
||||
# ce chemin ici, il passe par le proxy FastCGI vers wakdo-app.
|
||||
- ./src:/var/www/html
|
||||
- wakdo_uploads:/var/www/html/public/uploads
|
||||
|
||||
networks:
|
||||
- wakdo_internal
|
||||
- reverse_proxy
|
||||
|
||||
depends_on:
|
||||
wakdo-migrate:
|
||||
condition: service_completed_successfully
|
||||
|
|
@ -251,79 +124,26 @@ services:
|
|||
wakdo-db:
|
||||
condition: service_healthy
|
||||
|
||||
# === Labels Traefik : deux routers (kiosk + admin) sur le meme conteneur ===
|
||||
# Le Traefik de l'hote decouvre ces labels automatiquement (provider docker).
|
||||
# On ne configure PAS le certresolver ici : le Traefik hote le gere via
|
||||
# sa propre config (acme.json, resolver par defaut).
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=${REVERSE_PROXY_NETWORK}"
|
||||
|
||||
# --- Router kiosk (borne client) ---
|
||||
- "traefik.http.routers.wakdo-kiosk.rule=Host(`${TRAEFIK_DOMAIN_KIOSK}`)"
|
||||
- "traefik.http.routers.wakdo-kiosk.entrypoints=websecure"
|
||||
- "traefik.http.routers.wakdo-kiosk.tls=true"
|
||||
- "traefik.http.routers.wakdo-kiosk.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.wakdo-kiosk.service=wakdo-kiosk-svc"
|
||||
- "traefik.http.services.wakdo-kiosk-svc.loadbalancer.server.port=80"
|
||||
|
||||
# --- Router admin (back-office + API) ---
|
||||
- "traefik.http.routers.wakdo-admin.rule=Host(`${TRAEFIK_DOMAIN_ADMIN}`)"
|
||||
- "traefik.http.routers.wakdo-admin.entrypoints=websecure"
|
||||
- "traefik.http.routers.wakdo-admin.tls=true"
|
||||
- "traefik.http.routers.wakdo-admin.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.wakdo-admin.service=wakdo-admin-svc"
|
||||
- "traefik.http.services.wakdo-admin-svc.loadbalancer.server.port=80"
|
||||
|
||||
# --- Middleware : redirection HTTP -> HTTPS ---
|
||||
# Applique aux 2 hosts via un router "catch-all" sur entrypoints=web.
|
||||
- "traefik.http.routers.wakdo-kiosk-http.rule=Host(`${TRAEFIK_DOMAIN_KIOSK}`)"
|
||||
- "traefik.http.routers.wakdo-kiosk-http.entrypoints=web"
|
||||
- "traefik.http.routers.wakdo-kiosk-http.middlewares=wakdo-to-https"
|
||||
- "traefik.http.routers.wakdo-admin-http.rule=Host(`${TRAEFIK_DOMAIN_ADMIN}`)"
|
||||
- "traefik.http.routers.wakdo-admin-http.entrypoints=web"
|
||||
- "traefik.http.routers.wakdo-admin-http.middlewares=wakdo-to-https"
|
||||
- "traefik.http.middlewares.wakdo-to-https.redirectscheme.scheme=https"
|
||||
- "traefik.http.middlewares.wakdo-to-https.redirectscheme.permanent=true"
|
||||
|
||||
# =======================================================================
|
||||
# wakdo-cron : taches planifiees (backup, purge, agregations)
|
||||
# =======================================================================
|
||||
wakdo-cron:
|
||||
build:
|
||||
context: ./docker/cron
|
||||
dockerfile: Dockerfile
|
||||
container_name: wakdo-cron
|
||||
restart: unless-stopped
|
||||
# init: true -> Docker injecte tini comme PID 1. dcron exige un init
|
||||
# parent pour pouvoir setpgid() sur ses jobs (sinon "Operation not
|
||||
# permitted" en boucle car un PID 1 sans init ne peut pas changer les
|
||||
# groupes de processus). Cf. busybox-utils issue tracker.
|
||||
init: true
|
||||
|
||||
environment:
|
||||
# Credentials BDD pour mysqldump et les purges. Le user applicatif est en
|
||||
# moindre privilege (DML + SELECT/SHOW VIEW/TRIGGER/LOCK TABLES, jamais le
|
||||
# root password ; cf. db/init/10-scope-app-user.sh).
|
||||
DB_HOST: ${DB_HOST}
|
||||
DB_PORT: ${DB_PORT}
|
||||
DB_NAME: ${DB_NAME}
|
||||
DB_USER: ${DB_USER}
|
||||
DB_PASSWORD: ${DB_PASSWORD}
|
||||
# Retention des donnees (mlt.md 13.4/13.5). Defaut applique par les scripts
|
||||
# ET ici, pour rester coherent si la var manque du .env.
|
||||
AUDIT_LOG_RETENTION_DAYS: ${AUDIT_LOG_RETENTION_DAYS:-365}
|
||||
THROTTLE_PURGE_AFTER_HOURS: ${THROTTLE_PURGE_AFTER_HOURS:-24}
|
||||
TZ: ${CRON_TIMEZONE:-Europe/Paris}
|
||||
|
||||
volumes:
|
||||
# Bind-mount vers l'hote pour les dumps : inspectables par ls, scp-able
|
||||
# hors docker. Le dossier ./var/backups est gitignore.
|
||||
- ./var/backups:/backups
|
||||
|
||||
networks:
|
||||
- wakdo_internal
|
||||
|
||||
depends_on:
|
||||
wakdo-db:
|
||||
condition: service_healthy
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
# 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)
|
||||
# - APP_HOST_KIOSK -> /var/www/html/public/borne (borne client, Bloc 1)
|
||||
# - APP_HOST_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
|
||||
|
|
@ -39,8 +39,8 @@
|
|||
|
||||
# === 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}
|
||||
# Hostname injecte par la var d'env APP_HOST_KIOSK au runtime.
|
||||
ServerName ${APP_HOST_KIOSK}
|
||||
|
||||
DocumentRoot "/var/www/html/public/borne"
|
||||
|
||||
|
|
@ -89,7 +89,7 @@
|
|||
|
||||
# === Back-office + API REST (Bloc 2 - PHP from scratch + MVC) ===
|
||||
<VirtualHost *:80>
|
||||
ServerName ${TRAEFIK_DOMAIN_ADMIN}
|
||||
ServerName ${APP_HOST_ADMIN}
|
||||
|
||||
DocumentRoot "/var/www/html/public/admin"
|
||||
|
||||
|
|
@ -128,7 +128,7 @@
|
|||
</DirectoryMatch>
|
||||
|
||||
# CORS : l'API admin sous /api/* doit accepter les requetes venant
|
||||
# de la borne kiosk (TRAEFIK_DOMAIN_KIOSK). Wildcard interdit.
|
||||
# de la borne kiosk (APP_HOST_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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue