# # 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. # : 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 (chargees par make via `include .env` # et transmises ici par docker compose qui fait l'expansion automatique). # # Persistance : un `make down` preserve les named volumes. Seul `make clean` # (interactif) ou `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 : `make clean` (interactif, confirme) ou # `docker compose down -v` (destructif direct). 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} MARIADB_USER: ${DB_USER} MARIADB_PASSWORD: ${DB_PASSWORD} MARIADB_AUTO_UPGRADE: "1" TZ: ${APP_TIMEZONE:-Europe/Paris} volumes: - wakdo_db_data:/var/lib/mysql 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 timeout: 5s retries: 6 start_period: 30s # ======================================================================= # 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} APP_TIMEZONE: ${APP_TIMEZONE} APP_URL_KIOSK: ${APP_URL_KIOSK} APP_URL_ADMIN: ${APP_URL_ADMIN} DB_HOST: ${DB_HOST} DB_PORT: ${DB_PORT} DB_NAME: ${DB_NAME} DB_USER: ${DB_USER} DB_PASSWORD: ${DB_PASSWORD} SESSION_LIFETIME_IDLE: ${SESSION_LIFETIME_IDLE} SESSION_LIFETIME_ABSOLUTE: ${SESSION_LIFETIME_ABSOLUTE} SESSION_NAME: ${SESSION_NAME} CORS_ALLOWED_ORIGIN: ${CORS_ALLOWED_ORIGIN} PASSWORD_ALGO: ${PASSWORD_ALGO} # 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 `make down`. - wakdo_uploads:/var/www/html/public/uploads networks: - wakdo_internal depends_on: 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} 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-app: condition: service_started 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 (lecture seule via USER applicatif, # PAS le root password). Le user applicatif doit avoir SELECT + # LOCK TABLES + SHOW VIEW sur la BDD (migrations P2). DB_HOST: ${DB_HOST} DB_PORT: ${DB_PORT} DB_NAME: ${DB_NAME} DB_USER: ${DB_USER} DB_PASSWORD: ${DB_PASSWORD} 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