corentin_wakdo/docker-compose.yml
Imugiii ac8b6a6791 feat(docker): complete stack with compose and 4 services
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).
2026-04-24 15:59:19 +00:00

247 lines
9.4 KiB
YAML

#
# 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 (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.
# 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}
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
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