feat(deploy): Dockerfiles de production + compose + guide Coolify
- 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) <noreply@anthropic.com>
This commit is contained in:
@@ -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`.
|
||||||
+11
-1
@@ -1,4 +1,14 @@
|
|||||||
vendor
|
vendor
|
||||||
node_modules
|
node_modules
|
||||||
storage/logs/*.log
|
.git
|
||||||
|
.gitignore
|
||||||
.env
|
.env
|
||||||
|
.env.backup
|
||||||
|
.env.production
|
||||||
|
storage/logs/*.log
|
||||||
|
storage/framework/cache/data/*
|
||||||
|
storage/framework/sessions/*
|
||||||
|
storage/framework/views/*
|
||||||
|
.phpunit.result.cache
|
||||||
|
Dockerfile*
|
||||||
|
.dockerignore
|
||||||
|
|||||||
@@ -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"]
|
||||||
@@ -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
|
||||||
@@ -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:
|
||||||
+10
-1
@@ -1,3 +1,12 @@
|
|||||||
node_modules
|
node_modules
|
||||||
.next
|
.next
|
||||||
.env.local
|
out
|
||||||
|
build
|
||||||
|
public/dsfr
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
npm-debug.log*
|
||||||
|
.env*
|
||||||
|
Dockerfile*
|
||||||
|
.dockerignore
|
||||||
|
coverage
|
||||||
|
|||||||
@@ -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"]
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
/* config options here */
|
// Sortie autonome pour une image Docker de production légère.
|
||||||
|
output: "standalone",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
Reference in New Issue
Block a user