From 9b63dc8809569bb1d72e5001835cc37835d09e7a Mon Sep 17 00:00:00 2001 From: Azmog <80716388+AzmogEx@users.noreply.github.com> Date: Fri, 19 Jun 2026 09:15:35 +0200 Subject: [PATCH] feat(deploy): Dockerfiles de production + compose + guide Coolify MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - frontend/Dockerfile.prod : Next.js standalone (294 Mo), régénère les assets DSFR, injecte NEXT_PUBLIC_API_URL au build (npm ci --ignore-scripts pour le postinstall) - backend/Dockerfile.prod + docker/entrypoint.sh : API Laravel, migrations + seed idempotent + serveur, génération de secrets en fallback - docker-compose.prod.yml : Postgres + API + Front pour déploiement Coolify - next.config.ts : output standalone ; .dockerignore back/front - DEPLOIEMENT_COOLIFY.md : guide + variables d'environnement Images de prod testées (build + smoke-test DSFR OK). Co-Authored-By: Claude Opus 4.8 (1M context) --- DEPLOIEMENT_COOLIFY.md | 67 ++++++++++++++++++++++++++++++++++++ backend/.dockerignore | 12 ++++++- backend/Dockerfile.prod | 31 +++++++++++++++++ backend/docker/entrypoint.sh | 33 ++++++++++++++++++ docker-compose.prod.yml | 64 ++++++++++++++++++++++++++++++++++ frontend/.dockerignore | 11 +++++- frontend/Dockerfile.prod | 40 +++++++++++++++++++++ frontend/next.config.ts | 3 +- 8 files changed, 258 insertions(+), 3 deletions(-) create mode 100644 DEPLOIEMENT_COOLIFY.md create mode 100644 backend/Dockerfile.prod create mode 100644 backend/docker/entrypoint.sh create mode 100644 docker-compose.prod.yml create mode 100644 frontend/Dockerfile.prod diff --git a/DEPLOIEMENT_COOLIFY.md b/DEPLOIEMENT_COOLIFY.md new file mode 100644 index 0000000..94b9a00 --- /dev/null +++ b/DEPLOIEMENT_COOLIFY.md @@ -0,0 +1,67 @@ +# Déploiement CESIZen sur Coolify + +Stack de production : **PostgreSQL + API Laravel + Frontend Next.js (DSFR)**. +Le mobile (Expo) n'est pas concerné (ce n'est pas une appli web). + +Fichiers fournis : +- `backend/Dockerfile.prod` — API Laravel (PHP 8.4, migrations + seed au démarrage) +- `frontend/Dockerfile.prod` — Next.js en sortie *standalone* (image légère) +- `docker-compose.prod.yml` — orchestration des 3 services +- `backend/docker/entrypoint.sh` — démarrage de l'API + +--- + +## Option recommandée : « Docker Compose » dans Coolify + +1. **Créer une ressource** → *Docker Compose* → connecter le dépôt GitHub `AzmogEx/CesiZen`, branche `main`. +2. **Compose file** : `docker-compose.prod.yml`. +3. Renseigner les **variables d'environnement** (ci-dessous). +4. **Domaines** : dans Coolify, mapper + - le service **frontend** (port `3000`) sur ton domaine principal (ex. `cesizen.tondomaine.fr`) + - le service **backend** (port `8000`) sur un sous-domaine API (ex. `api.cesizen.tondomaine.fr`) +5. **Déployer**. + +> Le frontend lit l'API via `NEXT_PUBLIC_API_URL`, **injectée au build** → elle doit pointer vers l'**URL publique** du backend (pas l'URL interne Docker). + +--- + +## Variables d'environnement à définir dans Coolify + +| Variable | Exemple | Rôle | +|---|---|---| +| `POSTGRES_DB` | `cesizen` | Nom de la base | +| `POSTGRES_USER` | `cesizen` | Utilisateur base | +| `POSTGRES_PASSWORD` | *(mot de passe fort)* | **Obligatoire** | +| `APP_KEY` | `base64:...` | Clé Laravel — voir génération ci-dessous | +| `JWT_SECRET` | *(chaîne aléatoire)* | Secret JWT — voir génération | +| `JWT_TTL` | `60` | Durée de vie du token (min) | +| `APP_URL` | `https://api.cesizen.tondomaine.fr` | URL publique de l'API | +| `FRONTEND_URL` | `https://cesizen.tondomaine.fr` | Origine autorisée (CORS) | +| `NEXT_PUBLIC_API_URL` | `https://api.cesizen.tondomaine.fr/api/v1` | URL publique de l'API côté navigateur | + +### Générer les secrets (en local) +```bash +# APP_KEY +docker run --rm php:8.4-cli sh -c "php -r 'echo \"base64:\".base64_encode(random_bytes(32)).\"\n\";'" +# JWT_SECRET (64 caractères) +openssl rand -base64 48 | tr -d '\n=' +``` +> Définis-les **une fois** dans Coolify pour qu'ils restent stables entre déploiements. Si tu ne les fournis pas, l'entrypoint en génère au démarrage — mais ils changeront à chaque redéploiement (déconnexion de tous les utilisateurs). + +--- + +## Notes + +- **Migrations & seed** : exécutés automatiquement au démarrage du backend. Les seeders sont **idempotents** (pas de doublons même au redémarrage). La base est peuplée avec les comptes de démo (`admin@cesizen.fr` / `Admin123!`, `demo@cesizen.fr` / `Demo123!`) et le référentiel d'émotions. +- **Cache/queue/session** : en `database` (aucun Redis requis). Tu peux ajouter un service Redis et repasser ces variables sur `redis` si besoin. +- **HTTPS** : géré par le proxy de Coolify (Traefik). Pense à passer `APP_URL`/`FRONTEND_URL`/`NEXT_PUBLIC_API_URL` en `https://`. +- **Serveur PHP** : l'API tourne via `php artisan serve` (suffisant pour ce projet). Pour une charge réelle, on passerait à php-fpm+nginx ou FrankenPHP/Octane. + +--- + +## Déploiement par services séparés (alternative) + +Si tu préfères 3 ressources distinctes dans Coolify : +1. **PostgreSQL** : ressource « Database » Coolify (récupère host/user/password). +2. **Backend** : ressource « Dockerfile », dossier `backend`, fichier `Dockerfile.prod`, port `8000`, + les variables `APP_*`, `DB_*`, `JWT_*`, `FRONTEND_URL`. +3. **Frontend** : ressource « Dockerfile », dossier `frontend`, fichier `Dockerfile.prod`, port `3000`, **build arg** `NEXT_PUBLIC_API_URL`. diff --git a/backend/.dockerignore b/backend/.dockerignore index 4c08f08..eb8cb6c 100644 --- a/backend/.dockerignore +++ b/backend/.dockerignore @@ -1,4 +1,14 @@ vendor node_modules -storage/logs/*.log +.git +.gitignore .env +.env.backup +.env.production +storage/logs/*.log +storage/framework/cache/data/* +storage/framework/sessions/* +storage/framework/views/* +.phpunit.result.cache +Dockerfile* +.dockerignore diff --git a/backend/Dockerfile.prod b/backend/Dockerfile.prod new file mode 100644 index 0000000..a7390a4 --- /dev/null +++ b/backend/Dockerfile.prod @@ -0,0 +1,31 @@ +# Image de production de l'API Laravel (CESIZen). +FROM php:8.4-cli + +# Extensions PHP nécessaires (PostgreSQL, bcmath, redis) +RUN apt-get update && apt-get install -y \ + libpq-dev \ + unzip \ + git \ + && docker-php-ext-install pdo pdo_pgsql bcmath \ + && pecl install redis && docker-php-ext-enable redis \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer + +WORKDIR /app + +# Dépendances PHP de production uniquement (cache Docker) +COPY composer.json composer.lock ./ +RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist --no-interaction + +# Code source +COPY . . +RUN composer dump-autoload --optimize --no-dev + +# Script de démarrage (migrations + seed idempotent + serveur) +COPY docker/entrypoint.sh /usr/local/bin/entrypoint +RUN chmod +x /usr/local/bin/entrypoint \ + && chmod -R ug+rw storage bootstrap/cache + +EXPOSE 8000 +CMD ["entrypoint"] diff --git a/backend/docker/entrypoint.sh b/backend/docker/entrypoint.sh new file mode 100644 index 0000000..f0bf286 --- /dev/null +++ b/backend/docker/entrypoint.sh @@ -0,0 +1,33 @@ +#!/bin/sh +set -e + +cd /app + +# Laravel a besoin d'un fichier .env (les variables d'env du conteneur, +# définies par Coolify, ont la priorité sur ce fichier). +[ -f .env ] || cp .env.example .env + +# Clés générées au démarrage si absentes (À DÉFINIR EN PROD via Coolify : +# APP_KEY et JWT_SECRET, pour qu'elles restent stables entre déploiements). +if [ -z "$APP_KEY" ]; then + echo "[entrypoint] APP_KEY absent — génération (pensez à la fixer dans Coolify)." + php artisan key:generate --force +fi +if [ -z "$JWT_SECRET" ]; then + echo "[entrypoint] JWT_SECRET absent — génération (pensez à le fixer dans Coolify)." + php artisan jwt:secret --force +fi + +# Base de données : migrations + seed (les seeders sont idempotents). +echo "[entrypoint] Migrations..." +php artisan migrate --force +echo "[entrypoint] Seed (idempotent)..." +php artisan db:seed --force || true + +# Optimisation (les routes ne dépendent pas de l'env ; on évite config:cache +# pour ne pas figer des variables d'environnement par erreur). +php artisan route:cache || true +php artisan view:cache || true + +echo "[entrypoint] Démarrage de l'API sur le port 8000." +exec php artisan serve --host=0.0.0.0 --port=8000 diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..64c24f6 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,64 @@ +# Stack de production CESIZen — pour déploiement sur Coolify (type "Docker Compose"). +# Les valeurs ${...} sont à définir dans l'onglet "Environment Variables" de Coolify. +# Voir DEPLOIEMENT_COOLIFY.md pour la liste complète. + +services: + postgres: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_DB: ${POSTGRES_DB:-cesizen} + POSTGRES_USER: ${POSTGRES_USER:-cesizen} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + volumes: + - cesizen_pg_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-cesizen}"] + interval: 10s + timeout: 5s + retries: 5 + + backend: + build: + context: ./backend + dockerfile: Dockerfile.prod + restart: unless-stopped + environment: + APP_NAME: CESIZen + APP_ENV: production + APP_DEBUG: "false" + APP_KEY: ${APP_KEY} + APP_URL: ${APP_URL} + DB_CONNECTION: pgsql + DB_HOST: postgres + DB_PORT: 5432 + DB_DATABASE: ${POSTGRES_DB:-cesizen} + DB_USERNAME: ${POSTGRES_USER:-cesizen} + DB_PASSWORD: ${POSTGRES_PASSWORD} + JWT_SECRET: ${JWT_SECRET} + JWT_TTL: ${JWT_TTL:-60} + FRONTEND_URL: ${FRONTEND_URL} + CACHE_STORE: database + SESSION_DRIVER: database + QUEUE_CONNECTION: database + expose: + - "8000" + depends_on: + postgres: + condition: service_healthy + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile.prod + args: + # URL PUBLIQUE de l'API (injectée au build, visible côté navigateur) + NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL} + restart: unless-stopped + expose: + - "3000" + depends_on: + - backend + +volumes: + cesizen_pg_data: diff --git a/frontend/.dockerignore b/frontend/.dockerignore index 3cecc38..b49de72 100644 --- a/frontend/.dockerignore +++ b/frontend/.dockerignore @@ -1,3 +1,12 @@ node_modules .next -.env.local +out +build +public/dsfr +.git +.gitignore +npm-debug.log* +.env* +Dockerfile* +.dockerignore +coverage diff --git a/frontend/Dockerfile.prod b/frontend/Dockerfile.prod new file mode 100644 index 0000000..9993f33 --- /dev/null +++ b/frontend/Dockerfile.prod @@ -0,0 +1,40 @@ +# syntax=docker/dockerfile:1 +# Image de production du frontend Next.js (CESIZen) — sortie "standalone". + +# ─── Étape build ─────────────────────────────────────────────── +FROM node:20-alpine AS builder +WORKDIR /app + +# Dépendances. On ignore les scripts ici : le postinstall (copy-dsfr) a besoin +# de scripts/copy-dsfr.mjs, qui n'est copié qu'avec le code source plus bas. +COPY package.json package-lock.json ./ +RUN npm ci --ignore-scripts + +# Code source +COPY . . + +# Génère les assets DSFR dans public/dsfr (hors git) depuis node_modules +RUN node scripts/copy-dsfr.mjs + +# L'URL publique de l'API est injectée AU BUILD (variables NEXT_PUBLIC_*) +ARG NEXT_PUBLIC_API_URL +ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN npm run build + +# ─── Étape runtime ───────────────────────────────────────────── +FROM node:20-alpine AS runner +WORKDIR /app +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +ENV PORT=3000 +ENV HOSTNAME=0.0.0.0 + +# Assets statiques (dont public/dsfr) + sortie standalone +COPY --from=builder /app/public ./public +COPY --from=builder /app/.next/standalone ./ +COPY --from=builder /app/.next/static ./.next/static + +EXPOSE 3000 +CMD ["node", "server.js"] diff --git a/frontend/next.config.ts b/frontend/next.config.ts index e9ffa30..834b9d9 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -1,7 +1,8 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + // Sortie autonome pour une image Docker de production légère. + output: "standalone", }; export default nextConfig;