Déploiement en production

Avant de déployer

Vérifiez que vous avez complété les étapes suivantes :

  • Variables d’environnement configurées (voir Configuration)
  • Clés JWT générées
  • Domaines DNS configurés et propagés
  • Certificats TLS obtenus (Let’s Encrypt recommandé)
  • Volumes Docker persistants configurés
  • Sauvegardes planifiées

Fichier docker-compose de production

En production, utilisez la surcharge docker-compose.prod.yml :

# docker-compose.prod.yml
services:
  backend:
    restart: unless-stopped
    environment:
      APP_ENV: prod
      APP_DEBUG: "0"
    volumes:
      - uploads:/var/www/html/public/uploads
    expose:
      - "8000"

  admin:
    restart: unless-stopped
    expose:
      - "4173"

  frontend:
    restart: unless-stopped
    expose:
      - "3000"

  yjs:
    restart: unless-stopped
    expose:
      - "1234"

  db:
    restart: unless-stopped
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    restart: unless-stopped
    volumes:
      - redis_data:/data

volumes:
  uploads:
  postgres_data:
  redis_data:

Démarrer avec les deux fichiers :

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Healthchecks

Tous les services Fatplant exposent des endpoints de santé utilisés par Docker et le reverse-proxy :

ServiceEndpointMéthode
Backend/api/healthGET
Serveur Yjs/healthGET
Frontend/GET (code 200)

Docker Compose vérifie automatiquement la santé des services selon la configuration healthcheck :

# Vérifier l'état de tous les services
docker compose ps

# Inspecter les détails d'un service
docker inspect fatplant-backend-1 --format='{{.State.Health.Status}}'

Reverse-proxy Nginx complet

# /etc/nginx/sites-available/fatplant

# Redirection HTTP -> HTTPS
server {
    listen 80;
    server_name monmedia.fr admin.monmedia.fr api.monmedia.fr yjs.monmedia.fr;
    return 301 https://$host$request_uri;
}

# Backend API
server {
    listen 443 ssl http2;
    server_name api.monmedia.fr;

    ssl_certificate /etc/letsencrypt/live/monmedia.fr/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/monmedia.fr/privkey.pem;
    ssl_session_cache shared:SSL:10m;
    ssl_protocols TLSv1.2 TLSv1.3;

    client_max_body_size 64M;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_read_timeout 60s;
    }
}

# Administration
server {
    listen 443 ssl http2;
    server_name admin.monmedia.fr;
    ssl_certificate /etc/letsencrypt/live/monmedia.fr/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/monmedia.fr/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:4173;
        proxy_set_header Host $host;
    }
}

# Frontend public
server {
    listen 443 ssl http2;
    server_name monmedia.fr;
    ssl_certificate /etc/letsencrypt/live/monmedia.fr/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/monmedia.fr/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

# Serveur Yjs (WebSocket)
server {
    listen 443 ssl http2;
    server_name yjs.monmedia.fr;
    ssl_certificate /etc/letsencrypt/live/monmedia.fr/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/monmedia.fr/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:1234;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 86400s;
    }
}

Sauvegardes

Base de données PostgreSQL

#!/bin/bash
# /opt/scripts/backup-db.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR=/opt/backups/postgres

mkdir -p $BACKUP_DIR

docker compose -f /opt/fatplant/docker-compose.yml exec -T db 
    pg_dump -U fatplant fatplant 
    | gzip > $BACKUP_DIR/fatplant_$DATE.sql.gz

# Supprimer les sauvegardes de plus de 30 jours
find $BACKUP_DIR -name "*.sql.gz" -mtime +30 -delete

echo "Sauvegarde terminée : fatplant_$DATE.sql.gz"

Planifiez ce script avec cron :

# crontab -e
0 3 * * * /opt/scripts/backup-db.sh >> /var/log/fatplant-backup.log 2>&1

Fichiers uploadés

# Synchronisation des uploads vers un stockage distant (exemple avec rclone)
rclone sync /opt/fatplant/data/uploads remote:fatplant-backups/uploads

Logs

# Suivre les logs de tous les services
docker compose logs -f

# Logs d'un service spécifique
docker compose logs -f backend

# Exporter les logs dans un fichier
docker compose logs --no-color > /var/log/fatplant-$(date +%Y%m%d).log

Mise à jour en production

cd /opt/fatplant

# Sauvegarder avant la mise à jour
/opt/scripts/backup-db.sh

# Récupérer les nouvelles versions
git pull origin main
docker compose pull

# Appliquer les migrations et redémarrer
docker compose up -d
docker compose exec backend php bin/console doctrine:migrations:migrate --no-interaction

# Vérifier les logs
docker compose logs -f --tail=50

Supervision

Pour surveiller la disponibilité de votre instance Fatplant, configurez des sondes HTTP sur :

  • https://api.monmedia.fr/api/health
  • https://yjs.monmedia.fr/health
  • https://monmedia.fr/

Des outils comme Uptime Kuma, Prometheus + Grafana, ou Datadog peuvent être utilisés pour cette supervision.

Restez a la pointe de l'edition

Recevez les dernieres nouveautes, tutoriels et bonnes pratiques dans votre boite mail.

F
Fatplant

Le CMS open-source des redactions de presse en ligne. Du chemin de fer a la publication et au paywall : journaux, magazines et plateformes editoriales, self-hosted et sans vendor lock-in.

Open Source RGPD MIT License
Pourquoi « Fatplant » ?

« Fatplant », c'est « Flat Plan » qui a un peu bougé. Le flat plan — le chemin de fer en jargon de presse — c'est le plan page à page d'un journal avant impression : le squelette de l'édition, la pré-maquette en fil de fer. C'est exactement ce que dessine notre page builder. Un CMS qui part du chemin de fer ne pouvait pas s'appeler autrement. 🌱

© 2026 Fatplant SAS. Tous droits reserves.