From 7472d72dc8f1311d6a501c2ee051f99271086d5a Mon Sep 17 00:00:00 2001 From: Imugiii Date: Tue, 23 Jun 2026 12:58:08 +0000 Subject: [PATCH] chore(devops): modeles versionnes docker-compose.prod.yml + .env de prod Le compose de prod et le .env ne sont pas versionnes (propres a l'hote) mais leur modele l'est desormais : docker-compose.prod.yml.example (entierement pilote par le .env, identique sur tout hote) et .env.prod.example (prod : APP_ENV=prod, HTTPS, mots de passe forts a renseigner). Permet un bootstrap par 'git pull + cp' sur le serveur, sans copier-coller fragile, et rend le deploiement reproductible. deployment.md mis a jour. --- .env.prod.example | 62 +++++++++++ docker-compose.prod.yml.example | 178 ++++++++++++++++++++++++++++++++ docs/architecture/deployment.md | 12 ++- 3 files changed, 250 insertions(+), 2 deletions(-) create mode 100644 .env.prod.example create mode 100644 docker-compose.prod.yml.example diff --git a/.env.prod.example b/.env.prod.example new file mode 100644 index 0000000..be11cac --- /dev/null +++ b/.env.prod.example @@ -0,0 +1,62 @@ +# Modele de configuration de PRODUCTION (derriere Traefik). +# +# cp .env.prod.example .env +# puis renseigner les lignes (domaines, mots de passe, reseau Traefik). +# +# Difference avec .env.example (dev) : APP_ENV=prod, APP_DEBUG=false, URLs en HTTPS, +# mots de passe forts, REVERSE_PROXY_NETWORK renseigne. + +APP_ENV=prod +APP_DEBUG=false +APP_TIMEZONE=Europe/Paris + +# Domaines publics (doivent resoudre en DNS vers l'hote de prod). +APP_HOST_KIOSK= +APP_HOST_ADMIN= +APP_URL_KIOSK=https:// +APP_URL_ADMIN=https:// + +# Base de donnees : mots de passe FORTS en prod (openssl rand -base64 24). +DB_HOST=wakdo-db +DB_PORT=3306 +DB_NAME=wakdo +DB_USER=wakdo +DB_PASSWORD= +DB_ROOT_PASSWORD= + +SESSION_LIFETIME_IDLE=14400 +SESSION_LIFETIME_ABSOLUTE=36000 +SESSION_NAME=WAKDO_SID + +# Doit correspondre EXACTEMENT a APP_URL_KIOSK (pas de wildcard). +CORS_ALLOWED_ORIGIN=https:// + +ARGON2_MEMORY_COST=65536 +ARGON2_TIME_COST=4 +ARGON2_THREADS=1 + +ACCOUNT_LOCKOUT_THRESHOLD=5 +ACCOUNT_LOCKOUT_BASE_SECONDS=60 +ACCOUNT_LOCKOUT_MAX_SECONDS=900 +IP_THROTTLE_WINDOW_SECONDS=900 +IP_THROTTLE_MAX_ATTEMPTS=20 + +STAFF_PIN_MIN_LENGTH=4 +STAFF_PIN_MAX_LENGTH=12 +PIN_THROTTLE_THRESHOLD=5 +PIN_THROTTLE_BASE_SECONDS=30 +PIN_THROTTLE_MAX_SECONDS=300 +PIN_THROTTLE_WINDOW_SECONDS=900 +PASSWORD_RESET_TTL=3600 + +AUDIT_LOG_RETENTION_DAYS=365 +THROTTLE_PURGE_AFTER_HOURS=24 +ORDER_RETENTION_DAYS=1095 + +UPLOAD_MAX_SIZE_MB=5 +UPLOAD_ALLOWED_MIME=image/jpeg,image/png,image/webp + +CRON_TIMEZONE=Europe/Paris + +# Nom du reseau Docker externe du Traefik de l'hote (doit exister avant le up). +REVERSE_PROXY_NETWORK= diff --git a/docker-compose.prod.yml.example b/docker-compose.prod.yml.example new file mode 100644 index 0000000..5243834 --- /dev/null +++ b/docker-compose.prod.yml.example @@ -0,0 +1,178 @@ +# Modele de compose de production (derriere un reverse proxy Traefik). +# +# Entierement pilote par le .env : le meme fichier marche sur n'importe quel hote, +# seules les valeurs du .env changent. Sur l'hote de prod : +# cp docker-compose.prod.yml.example docker-compose.prod.yml +# cp .env.prod.example .env # puis renseigner domaines + mots de passe +# docker compose -f docker-compose.prod.yml up -d --build +# +# Prerequis : le reseau externe ${REVERSE_PROXY_NETWORK} existe (cree par la stack +# Traefik de l'hote). Les entrypoints (websecure) et le certresolver (letsencrypt) +# doivent correspondre a la config Traefik de l'hote. + +name: wakdo + +networks: + wakdo_internal: + driver: bridge + reverse_proxy: + name: ${REVERSE_PROXY_NETWORK} + external: true + +volumes: + wakdo_db_data: + wakdo_uploads: + +services: + + wakdo-db: + image: mariadb:11.4 + container_name: wakdo-db + restart: unless-stopped + 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 + - ./db/init:/docker-entrypoint-initdb.d:ro + networks: + - wakdo_internal + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 10s + timeout: 5s + retries: 6 + start_period: 30s + + 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} + DB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} + volumes: + - ./db:/db:ro + networks: + - wakdo_internal + depends_on: + wakdo-db: + condition: service_healthy + entrypoint: ["bash", "/db/migrate-container.sh"] + + 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} + ARGON2_MEMORY_COST: ${ARGON2_MEMORY_COST} + ARGON2_TIME_COST: ${ARGON2_TIME_COST} + ARGON2_THREADS: ${ARGON2_THREADS} + 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} + STAFF_PIN_MIN_LENGTH: ${STAFF_PIN_MIN_LENGTH} + STAFF_PIN_MAX_LENGTH: ${STAFF_PIN_MAX_LENGTH} + 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} + PASSWORD_RESET_TTL: ${PASSWORD_RESET_TTL} + UPLOAD_MAX_SIZE_MB: ${UPLOAD_MAX_SIZE_MB} + UPLOAD_ALLOWED_MIME: ${UPLOAD_ALLOWED_MIME} + volumes: + - ./src:/var/www/html + - wakdo_uploads:/var/www/html/public/uploads + networks: + - wakdo_internal + depends_on: + wakdo-migrate: + condition: service_completed_successfully + wakdo-db: + condition: service_healthy + + wakdo-web: + build: + context: ./docker/apache + dockerfile: Dockerfile + container_name: wakdo-web + restart: unless-stopped + environment: + APP_HOST_KIOSK: ${APP_HOST_KIOSK} + APP_HOST_ADMIN: ${APP_HOST_ADMIN} + volumes: + - ./src:/var/www/html + - wakdo_uploads:/var/www/html/public/uploads + networks: + - wakdo_internal + - reverse_proxy + depends_on: + wakdo-migrate: + condition: service_completed_successfully + wakdo-app: + condition: service_started + wakdo-db: + condition: service_healthy + labels: + - "traefik.enable=true" + - "traefik.docker.network=${REVERSE_PROXY_NETWORK}" + - "traefik.http.routers.wakdo-kiosk.rule=Host(`${APP_HOST_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" + - "traefik.http.routers.wakdo-admin.rule=Host(`${APP_HOST_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" + + wakdo-cron: + build: + context: ./docker/cron + dockerfile: Dockerfile + container_name: wakdo-cron + restart: unless-stopped + init: true + environment: + DB_HOST: ${DB_HOST} + DB_PORT: ${DB_PORT} + DB_NAME: ${DB_NAME} + DB_USER: ${DB_USER} + DB_PASSWORD: ${DB_PASSWORD} + AUDIT_LOG_RETENTION_DAYS: ${AUDIT_LOG_RETENTION_DAYS:-365} + THROTTLE_PURGE_AFTER_HOURS: ${THROTTLE_PURGE_AFTER_HOURS:-24} + TZ: ${CRON_TIMEZONE:-Europe/Paris} + volumes: + - ./var/backups:/backups + networks: + - wakdo_internal + depends_on: + wakdo-db: + condition: service_healthy diff --git a/docs/architecture/deployment.md b/docs/architecture/deployment.md index 3c637ee..f155c31 100644 --- a/docs/architecture/deployment.md +++ b/docs/architecture/deployment.md @@ -42,8 +42,16 @@ GET /api/health renvoie le nouveau SHA ← preuve du deploiement ## Mise en place cote Vision (une fois) -Prerequis : Docker + docker compose, le depot clone (ex. `/srv/wakdo`), un `.env` de -prod renseigne et un `docker-compose.prod.yml` propre a l'hote. +Prerequis : Docker + docker compose, le depot clone (ex. `/srv/wakdo`). + +Le compose et le `.env` de prod ne sont pas versionnes (propres a l'hote) ; ils se +derivent des modeles fournis dans le depot : +```bash +cp docker-compose.prod.yml.example docker-compose.prod.yml +cp .env.prod.example .env # puis renseigner domaines + mots de passe + reseau Traefik +docker compose -f docker-compose.prod.yml up -d --build +``` +Le compose est entierement pilote par le `.env` : le meme fichier marche sur tout hote. 1. Creer un utilisateur dedie au deploiement, membre du groupe `docker` : ```bash -- 2.45.3