03-comment-je-travaille/guides/deployment.md

Déploiement (Ubuntu + Apache)

Guide de déploiement pour un VPS OVH avec Ubuntu + Apache + Symfony.

Voir aussi :

  • guides/ssh-reference.md (aliases SSH — accĂšs poseidon et VPS OVH)
  • guides/dsn-ovh.md (DNS OVH, wildcard, vhosts)
  • guides/hsts.md (HSTS)

Prérequis

  • Ubuntu 24.10+
  • AccĂšs root ou sudo
  • Nom de domaine configurĂ©

Prérequis spécifiques (fonctionnalités à venir)

Ces prĂ©requis concernent des fonctionnalitĂ©s en cours de spĂ©cification (doc web /docs et IA). À appliquer au moment de leur dĂ©ploiement, pas avant. ProcĂ©dure IA dĂ©taillĂ©e : §11.

  • Documentation web (/docs) : le contenu telaria-doc est inclus via un git submodule → exĂ©cuter git submodule update --init --recursive au dĂ©ploiement / en CI. Voir specs/docs-web.md.
  • Index vectoriel IA (sqlite-vec) : extension SQLite non incluse par dĂ©faut → l'installer sur le serveur (binaire prĂ©-compilĂ© ou compilation) et vĂ©rifier que le build PHP autorise le chargement d'extensions ; Ă  dĂ©faut, rĂ©aliser l'indexation via l'outillage (CLI/Python) et faire lire l'index par l'application. Voir specs/ia-coeur.md.
  • Microservice d'embeddings (Python) : dĂ©ployable sĂ©parĂ© (FastAPI + sentence-transformers), Ă  lancer en service (ex. systemd). Voir tutos/ia/microservice-embeddings-python.md.

1) Sécuriser le serveur

Mise Ă  jour :

sudo apt update && sudo apt upgrade -y

Firewall (UFW) :

sudo apt install ufw
sudo ufw allow 'Apache Full'
sudo ufw allow ssh
sudo ufw allow 22/tcp    # ou votre port SSH
sudo ufw enable
sudo ufw status verbose

Fail2ban (SSH) :

sudo apt install fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
[sshd]
enabled = true
sudo systemctl restart fail2ban

2) Installer la stack

Apache + PHP + MySQL :

sudo apt install -y apache2 php libapache2-mod-php mysql-server php-mysql
sudo apt install -y php-pdo php-mysql php-zip php-gd php-mbstring \
  php-curl php-xml php-pear php-bcmath php-imagick php-simplexml \
  php-dom php-intl

Activer les modules Apache :

sudo a2enmod ssl
sudo a2enmod headers
sudo a2enmod rewrite
sudo a2enmod http2
sudo a2enmod proxy_fcgi
sudo a2enmod setenvif

PHP-FPM (recommandé) :

sudo a2dismod php8.4
sudo a2dismod mpm_prefork
sudo a2enmod mpm_event
sudo apt install php8.5-fpm
sudo a2enconf php8.5-fpm
sudo systemctl restart apache2
sudo systemctl restart php8.5-fpm

3) Apache : vhost HTTPS

Exemple minimal (adapter le domaine) :

<VirtualHost *:80>
  ServerName telaria.dev
  RewriteEngine On
  RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</VirtualHost>

<VirtualHost *:443>
  ServerName telaria.dev
  Protocols h2 http/1.1
  DocumentRoot /var/www/codexia/public

  <Directory /var/www/codexia/public>
    AllowOverride None
    Require all granted
    FallbackResource /index.php
  </Directory>

  <FilesMatch \\.php$>
    SetHandler proxy:unix:/var/run/php/php8.5-fpm.sock|fcgi://telaria.dev
  </FilesMatch>

  SSLEngine on
  SSLCertificateFile /etc/letsencrypt/live/telaria.dev/fullchain.pem
  SSLCertificateKeyFile /etc/letsencrypt/live/telaria.dev/privkey.pem

  ErrorLog ${APACHE_LOG_DIR}/telaria.dev-error.log
  CustomLog ${APACHE_LOG_DIR}/telaria.dev-access.log combined
</VirtualHost>

Activer :

sudo a2ensite telaria.dev.conf
sudo systemctl reload apache2

4) Certificat SSL (OVH DNS-01)

Objectif : un certificat wildcard pour telaria.dev + *.telaria.dev.

Installer le plugin OVH :

sudo apt install python3-certbot-dns-ovh

Créer les credentials OVH :

# /etc/letsencrypt/ovh.ini
dns_ovh_endpoint = ovh-eu
# dns_ovh_application_key = ...
# dns_ovh_application_secret = ...
# dns_ovh_consumer_key = ...
sudo chmod 600 /etc/letsencrypt/ovh.ini

Demander le certificat :

sudo certbot certonly --dns-ovh --dns-ovh-credentials /etc/letsencrypt/ovh.ini \
  -d telaria.dev -d "*.telaria.dev"

Renouvellement :

sudo certbot renew --dry-run

Note : la validation DNS-01 fonctionne sans port 80, mais garder le port 80 est recommandé pour les redirections HTTP -> HTTPS.

5) Déploiement de l'application

Permissions :

sudo chown -R ubuntu:www-data /var/www/codexia
sudo find /var/www/codexia -type d -exec chmod 770 {} \;
sudo find /var/www/codexia -type f -exec chmod 660 {} \;
sudo find /var/www/codexia -type d -exec chmod g+s {} \;

Dépendances :

cd /var/www/codexia
composer install --no-dev --optimize-autoloader

Configuration :

cp .env.dist .env.local
nano .env.local
APP_ENV=prod
APP_DEBUG=false
APP_SECRET=your_generated_secret_here
DATABASE_URL="mysql://codexia_user:your_password@127.0.0.1:3306/codexia_prod?serverVersion=8.4&charset=utf8mb4"
MAILER_DSN=sendmail://default

Migrations + cache :

php bin/console doctrine:migrations:migrate --no-interaction
php bin/console cache:clear
php bin/console cache:warmup

⚠ Recharger PHP-FPM aprĂšs toute mise Ă  jour de code/schĂ©ma — sinon l'OPcache sert l'ancien code :

sudo systemctl reload php8.5-fpm

En prod, opcache.validate_timestamps=0 (recommandĂ© pour la perf) impose ce rechargement. Sans lui, le CLI et les tests peuvent ĂȘtre verts alors que le web (FPM) plante — vĂ©cu rĂ©el : SQLSTATE[42S22] Unknown column 't0.username' aprĂšs le drop de la colonne. Ordre sĂ»r : git pull → migrate → cache:clear → reload php8.5-fpm.

6) Diagnostics rapides

sudo apachectl -t -D DUMP_VHOSTS
sudo apache2ctl -t
curl -I --http2 https://telaria.dev

7) Sécurisation des Headers HTTP

Pour renforcer la sĂ©curitĂ© du serveur, appliquer une politique d'en-tĂȘtes HTTP standardisĂ©e cĂŽtĂ© Apache. Suivre le guide dĂ©diĂ© : Headers de SĂ©curitĂ© (Apache).

8) OCSP Stapling (optimisation TLS)

Objectif: réduire la latence cÎté client en fournissant la réponse OCSP depuis le serveur.

Configuration (dans le vhost 443 ou un fichier SSL global):

SSLUseStapling On
SSLStaplingCache shmcb:${APACHE_LOG_DIR}/stapling_cache(128000)
SSLStaplingReturnResponderErrors off
SSLStaplingResponderTimeout 5

Vérification:

openssl s_client -connect votre-domaine:443 -servername votre-domaine -status < /dev/null | grep -A3 "OCSP Response Status"
# Attendu: OCSP Response Status: successful

9) Compression HTTP (Brotli + Gzip)

Activer Brotli (niveau 5) et conserver Gzip en fallback. Voir la procédure détaillée: Performances HTTP.

Rappel minimal de configuration globale (ex: conf-available/compression.conf):

<IfModule mod_brotli.c>
    AddOutputFilterByType BROTLI_COMPRESS \
        text/html text/plain text/css application/javascript application/json image/svg+xml
    BrotliCompressionQuality 5
</IfModule>

<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE \
        text/html text/plain text/css application/javascript application/json image/svg+xml
</IfModule>

Tests rapides:

curl -sI -H "Accept-Encoding: br" https://votre-domaine | grep -i content-encoding   # br
curl -sI -H "Accept-Encoding: gzip" https://votre-domaine | grep -i content-encoding # gzip

10) Cache navigateur (assets statiques)

Pour les assets versionnés (.css/.js/.svg), ajouter un cache long:

<IfModule mod_headers.c>
  <FilesMatch "\.(css|js|svg)$">
    Header set Cache-Control "public, max-age=31536000, immutable"
  </FilesMatch>
</IfModule>

Vérification 304:

ETAG=$(curl -sI https://votre-domaine/favicon.svg | awk -F': ' '/^etag:/ {print $2}')
curl -I -H "If-None-Match: $ETAG" https://votre-domaine/favicon.svg   # 304 si inchangé

11) DĂ©ploiement de la fonctionnalitĂ© IA (cƓur RAG)

DĂ©ploiement du bundle telaria/rag-bundle (Telaria\Rag, consommĂ© par telaria en ^0.1). Le cƓur fait du retrieval (pas de gĂ©nĂ©ration → aucune clĂ© Claude ici). Spec : specs/ia-coeur.md.

11.1 AccÚs Composer au dépÎt privé telaria-rag

  • Deploy key GitHub (lecture) sur <owner>/telaria-rag + la clĂ© SSH correspondante sur le VPS (~/.ssh), testĂ©e par ssh -T git@github.com.
  • Dans composer.json (repository VCS SSH), ajouter "no-api": true → sinon l'API GitHub renvoie 404 sans token (la clĂ© SSH du VPS suffit alors).
  • (vĂ©cu) Garder bump-after-update dĂ©sactivĂ© pour Ă©viter la divergence composer.json VPS↔git quand composer n'est pas lancĂ© en local.
  • Puis : composer update telaria/rag-bundle.

11.2 Variables d'environnement (.env.local)

Variable RÎle Défaut
RAG_EMBEDDING_URL URL du microservice embeddings http://127.0.0.1:8001
RAG_DB_PATH index SQLite %kernel.project_dir%/var/rag/index.sqlite
RAG_SQLITE_VEC_PATH chemin du .so sqlite-vec (sinon tente vec0)

Les autres réglages (model, dimension, chunk, retrieval.k, source_root
) sont dans config/packages/telaria_rag.yaml. Aucune clé Claude en L0.

11.3 Extension sqlite-vec

  • RĂ©cupĂ©rer la release loadable (github.com/asg017/sqlite-vec) : sqlite-vec-<ver>-loadable-linux-x86_64.tar.gz → vec0.so (~58 Ko), p. ex. dans /usr/local/lib/sqlite-vec/.
  • Pointer RAG_SQLITE_VEC_PATH dessus. Le bundle charge via Pdo\Sqlite::loadExtension() (PHP 8.5) — validĂ© sur le VPS ✅.
  • ⚠ Rappel (ia-coeur.md §3.4) : la recherche kNN exige l'extension chargĂ©e en PHP. Si PHP ne pouvait pas la charger (open_basedir, build), il faudrait dĂ©porter la recherche hors PHP — le repli « indexation outillĂ©e » seul ne suffit pas.

11.4 ⚠ Droits sur var/rag/ (le piĂšge)

app:rag:ingest (lancĂ© en ubuntu) crĂ©e l'index ; Apache/PHP-FPM lit en www-data. SQLite Ă©crit aussi -wal/-shm → le dossier doit ĂȘtre inscriptible par le lecteur. Deux stratĂ©gies :

  • lancer l'ingest en www-data : sudo -u www-data php bin/console app:rag:ingest ; ou
  • recette VPS validĂ©e (groupe partagĂ© + setgid pour hĂ©ritage) :
    sudo chgrp -R www-data var/rag
    sudo chmod -R g+rwX var/rag
    sudo chmod g+s var/rag   # setgid : les nouveaux fichiers (-wal/-shm) héritent du groupe
    

11.5 Corpus (premier déploiement)

git clone <telaria-doc> docs/ Ă  la racine (= rag.source_root = %kernel.project_dir%/docs). Le submodule pinnĂ© + treeview (specs/docs-web.md) est un chantier distinct → ne pas bloquer ici.

11.6 Microservice telaria-embeddings (Python, systemd)

Sans lui, app:rag:ingest et la recherche Ă©chouent. Mode « par dĂ©faut » (page /admin/rag dĂ©gradĂ©e, stats vides) → validable sans. Mode « nominal » → service up sur :8001, URL en RAG_EMBEDDING_URL.

ProcĂ©dure d'installation (tag courant : v0.1.2, Python 3.13.2 — versionnĂ© en .python-version) :

sudo apt install python3 python3-venv python3-pip
sudo mkdir -p /opt/telaria-embeddings && sudo chown ubuntu: /opt/telaria-embeddings
git clone git@github.com:<owner>/telaria-embeddings.git /opt/telaria-embeddings
cd /opt/telaria-embeddings && git checkout v0.1.2
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
# requirements.txt pin dĂ©jĂ  torch==2.12.0+cpu via --extra-index-url → install propre CPU-only.
# requirements.lock : à régénérer SUR LA CIBLE si besoin (wheels Linux/3.13 spécifiques), avec
# l'index CPU explicite pour rester propre :
#   .venv/bin/pip install --index-url https://download.pytorch.org/whl/cpu \
#                         --extra-index-url https://pypi.org/simple -r requirements.txt
.venv/bin/pip freeze > requirements.lock
sudo cp deploy/telaria-embeddings.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now telaria-embeddings
curl -s http://127.0.0.1:8001/health   # {"status":"ok","model":...,"dim":768}

Unité systemd versionnée (deploy/telaria-embeddings.service, fournie par le dépÎt) :

  • bind 127.0.0.1:8001 (jamais exposĂ© directement — passe par Apache si besoin)
  • HF_HOME=/var/lib/telaria-embeddings/hf (sur disque, pas /tmp)
  • durcissement : ProtectSystem=strict, ReadWritePaths=/var/lib/telaria-embeddings, NoNewPrivileges=yes
  • TimeoutStartSec=600 — le 1er dĂ©marrage tĂ©lĂ©charge ~1,1 Go (modĂšle Hugging Face), prĂ©voir rĂ©seau + disque
  • empreinte runtime : ~440 Mo RAM une fois le modĂšle chargĂ©

Alternative conteneur (dev local, ou prod sur hÎte avec Docker) : image ghcr.io/<owner>/telaria-embeddings:v0.1.2 (ou :latest) publiée par CI GitHub Actions à chaque tag. Bind interne 0.0.0.0:8001, à publier sur 127.0.0.1:8001 cÎté hÎte. Cache HF dans /home/app/.cache/huggingface (user non-root app). Pour le VPS, le mode systemd reste recommandé (durcissement, simplicité, pas de runtime Docker requis).

Contrat strict /embed : type ∈ {"query","passage"}, texts non vide — toute autre entrĂ©e → HTTP 422 (le bundle propage l'erreur).

11.7 Ordre sûr (réutilise le reload FPM du §5)

composer update telaria/rag-bundle
php bin/console cache:clear && sudo systemctl reload php8.5-fpm
# vĂ©rifier le mode dĂ©gradĂ© : /admin/rag → 200 + banniĂšre « index indisponible »
# puis : installer vec0.so + RAG_SQLITE_VEC_PATH ; git clone <telaria-doc> docs/
php bin/console app:rag:stats
# nominal (microservice up sur :8001) :
php bin/console app:rag:ingest        # --full = réindexation complÚte
php bin/console app:rag:search "comment réinitialiser mon mot de passe ?"

Ordres de grandeur attendus aprĂšs ingest complet de telaria-doc : ~180 documents / ~2400 chunks, top-1 pertinent avec cosinus > 0.8 sur requĂȘtes en langage naturel.

11.8 PiĂšges connus

  • BOM UTF-8 en tĂȘte d'un .php est fatal sous PHP 8.5 (« strict_types must be the very first statement »). Enregistrer les .php sans BOM.
  • OPcache (opcache.validate_timestamps=0 en prod) sert l'ancien code aprĂšs git pull → toujours cache:clear puis sudo systemctl reload php8.5-fpm (cf. §5). SymptĂŽme vĂ©cu : SQLSTATE[42S22] Unknown column 't0.username' cĂŽtĂ© web alors que CLI/tests verts.
  • PDO::PARAM_INT sur vec0 : bug d'intĂ©gration Only integers are allowed for primary key values lors des bindings rowid/k — corrigĂ© en telaria/rag-bundle v0.1.3. VĂ©rifier la contrainte composer (^0.1 couvre v0.1.3+).
  • requirements.lock Python : Ă  gĂ©nĂ©rer sur le VPS (pip freeze), pas sur un poste Windows/macOS (wheels Linux-spĂ©cifiques pour torch notamment).
  • bump-after-update Composer : Ă  laisser dĂ©sactivĂ© pour Ă©viter la divergence composer.json VPS↔git quand composer n'est pas lancĂ© en local.

Assistant documentaire

Posez une question sur la documentation. Les rĂ©ponses citent leurs sources — un clic ouvre le document Ă  gauche.

Loading…
Loading the web debug toolbar…
Attempt #