# Déploiement sur Coolify Guide complet pour mettre `courcellematthi.eu` en ligne depuis Coolify. --- ## Résumé | | | |---|---| | Type d'application | Dockerfile (recommandé) ou Docker Compose | | Port interne | 80 | | Image finale | ~50 Mo | | Build | Aucune compilation — copie de fichiers statiques | | TLS | Géré par Traefik, **pas** par le conteneur | --- ## Étape 1 — Préparer le dépôt Git Coolify déploie depuis un dépôt. Sur ton Gitea auto-hébergé : ```bash cd courcellematthi git init git add . git commit -m "Site personnel — version initiale" git branch -M main git remote add origin https://git.ghostinthemachine.fr/matthieu/courcellematthi.git git push -u origin main ``` Vérifie que `.htaccess` et `.well-known/` sont bien poussés — Git les suit normalement, mais un `.gitignore` trop large les fait parfois disparaître : ```bash git ls-files | grep -E "htaccess|well-known" ``` --- ## Étape 2 — Configurer le DNS Avant de créer l'application, les enregistrements doivent pointer vers le serveur, sinon Let's Encrypt échouera à émettre le certificat. | Type | Nom | Valeur | |---|---|---| | A | `www` | IP de ton serveur | | A | `@` | IP de ton serveur | Si le domaine est chez Cloudflare, **désactive le proxy (nuage orange → gris)** pendant l'émission du certificat. Le proxy Cloudflare intercepte le challenge HTTP-01 et fait échouer la validation ACME. Tu pourras le réactiver ensuite. Vérification : ```bash dig +short www.courcellematthi.eu dig +short courcellematthi.eu ``` --- ## Étape 3 — Créer l'application dans Coolify **Interface Coolify → Projet → + New → Application → Public/Private Repository** Renseigne : - **Repository** : l'URL de ton dépôt Gitea - **Branch** : `main` - **Build Pack** : `Dockerfile` - **Dockerfile Location** : `/Dockerfile` - **Base Directory** : `/` - **Port Exposes** : `80` > **Ne choisis pas Nixpacks.** Il tenterait de détecter un projet Node, > échouerait faute de `package.json`, ou pire, installerait une chaîne de > build inutile. Le Dockerfile fourni est explicite et reproductible. --- ## Étape 4 — Domaines Dans **Configuration → General → Domains**, saisis : ``` https://www.courcellematthi.eu ``` Coolify génère alors automatiquement les étiquettes Traefik et demande le certificat Let's Encrypt. ### Redirection du domaine nu Ajoute `https://courcellematthi.eu` comme second domaine, puis active **Redirect to primary domain** (ou l'option *www ↔ non-www* selon ta version de Coolify). Ce point n'est pas cosmétique : le HTML déclare ``. Si le domaine nu répond aussi en 200 au lieu de rediriger, les moteurs voient deux URL pour le même contenu et diluent le référencement. **Si tu préfères le domaine nu**, il faut modifier trois endroits pour rester cohérent : 1. `index.html` — la balise `canonical`, les `og:url`, les `hreflang`, et les URL absolues du JSON-LD 2. `sitemap.xml` — la balise `` 3. `robots.txt` — la ligne `Sitemap:` --- ## Étape 5 — Déployer Bouton **Deploy**. Le build prend une trentaine de secondes : il n'y a rien à compiler, seulement à copier. Suis les journaux dans l'onglet **Logs**. Une fin de build normale ressemble à : ``` naming to docker.io/library/xxxxx done Container started ``` --- ## Étape 6 — Vérifications post-déploiement ```bash # Le site répond curl -I https://www.courcellematthi.eu # La sonde de santé curl https://www.courcellematthi.eu/healthz # -> ok # Le domaine nu redirige (301 attendu, pas 200) curl -I https://courcellematthi.eu # Les en-têtes de sécurité sont présents curl -sI https://www.courcellematthi.eu | grep -iE "content-security|x-content-type|referrer|permissions" # La compression fonctionne curl -sI -H "Accept-Encoding: gzip" https://www.courcellematthi.eu | grep -i encoding # Les fichiers de service sont servis curl -s https://www.courcellematthi.eu/robots.txt | head -3 curl -s https://www.courcellematthi.eu/llms.txt | head -3 curl -sI https://www.courcellematthi.eu/.well-known/security.txt ``` --- ## Les deux pannes classiques sur Coolify Ce sont les deux mêmes que tu as déjà rencontrées sur d'autres déploiements. ### 1. Conteneur absent du réseau `coolify` **Symptôme** : Traefik renvoie 404, alors que le conteneur tourne et que ses journaux sont vides. La requête ne l'atteint jamais. **Diagnostic** : ```bash docker inspect --format '{{json .NetworkSettings.Networks}}' | python3 -m json.tool ``` Si `coolify` n'apparaît pas dans la liste : ```bash docker network connect coolify ``` En mode Docker Compose, le fichier fourni déclare déjà le réseau. En mode Dockerfile, Coolify le fait normalement seul — mais pas toujours après une mise à jour de Coolify. ### 2. Port du load balancer non déclaré **Symptôme** : `502 Bad Gateway`, ou `service not found` dans le tableau de bord Traefik. Traefik ne devine le port que si le conteneur n'en expose qu'un seul, et cette détection échoue régulièrement. Il faut donc le déclarer explicitement. **En mode Docker Compose**, l'étiquette est déjà présente : ```yaml - traefik.http.services.courcellematthi.loadbalancer.server.port=80 ``` **En mode Dockerfile**, va dans **Configuration → Advanced → Custom Labels** et ajoute : ``` traefik.http.services..loadbalancer.server.port=80 ``` Récupère le nom généré dans les étiquettes existantes du conteneur : ```bash docker inspect --format '{{json .Config.Labels}}' | grep -o 'traefik[^"]*' ``` --- ## HSTS — à traiter en dernier, et prudemment Le HSTS n'est **pas** émis par le conteneur. C'est un choix délibéré : le conteneur est redéployé souvent, et un réglage irréversible n'a rien à faire dans une couche jetable. C'est Traefik qui doit l'émettre, une fois le HTTPS confirmé fonctionnel. > **Pourquoi cette prudence.** `Strict-Transport-Security` avec > `max-age=63072000` indique au navigateur de refuser toute connexion HTTP à ce > domaine pendant deux ans. Si le certificat casse ensuite, le site devient > inaccessible — et **tu ne peux rien faire côté serveur** pour l'annuler : la > directive est mémorisée par le navigateur du visiteur. ### Procédure recommandée **Phase 1 — validation.** Configure `max-age=300` (cinq minutes). En mode Docker Compose, c'est déjà la valeur du fichier fourni. En mode Dockerfile, ajoute dans **Custom Labels** : ``` traefik.http.middlewares.hsts-test.headers.stsSeconds=300 traefik.http.middlewares.hsts-test.headers.forceSTSHeader=true traefik.http.routers..middlewares=hsts-test ``` Vérifie sur plusieurs jours que le certificat se renouvelle bien. **Phase 2 — activation.** Passe `stsSeconds` à `63072000`. N'inscris le domaine sur la liste de préchargement HSTS (hstspreload.org) que si tu es certain de ne jamais revenir en arrière : la radiation prend des mois. --- ## Redéploiement ### Automatique Dans **Configuration → General**, active **Auto Deploy**. Coolify crée un webhook dans Gitea : chaque `git push` sur `main` déclenche un déploiement. C'est pertinent ici — un site statique sans test à faire tourner ne risque rien à se redéployer automatiquement. ### Manuel Bouton **Redeploy**. ### Après une modification de contenu Pense à mettre à jour la date dans `sitemap.xml` : ```xml 2026-08-05 ``` --- ## Test local avant déploiement Si tu veux vérifier l'image sur ta machine : ```bash docker build -t courcellematthi . docker run --rm -p 8080:80 courcellematthi # puis http://localhost:8080 ``` Contrôles utiles : ```bash # Poids de l'image docker images courcellematthi # Le dossier deploy/ ne doit PAS être dans l'image docker run --rm courcellematthi ls /usr/share/nginx/html # La configuration Nginx est syntaxiquement correcte docker run --rm courcellematthi nginx -t ``` --- ## Une fois en ligne 1. **Google Search Console** — ajoute la propriété et soumets `https://www.courcellematthi.eu/sitemap.xml` 2. **Test des résultats enrichis** — vérifie le JSON-LD sur `search.google.com/test/rich-results` 3. **Débogueur LinkedIn** — force le rafraîchissement de l'aperçu de partage sur `linkedin.com/post-inspector` (LinkedIn met les aperçus en cache longtemps : sans cette étape, un ancien aperçu peut persister des semaines) 4. **securityheaders.com** — la note attendue est A ou A+ 5. **PageSpeed Insights** — contrôle les Core Web Vitals --- ## Si quelque chose ne va pas | Symptôme | Cause probable | |---|---| | `failed to read dockerfile: open Dockerfile: no such file or directory` avec `transferring dockerfile: 2B` | `Dockerfile` listé dans `.dockerignore` — voir ci-dessous | | `failed to read dockerfile` sans la ligne `2B` | Fichier réellement absent du dépôt, ou mauvais **Dockerfile Location** dans Coolify | | 404 Traefik, conteneur sain | Conteneur hors du réseau `coolify` | | 502 Bad Gateway | Étiquette `loadbalancer.server.port` absente | | Certificat non émis | DNS non propagé, ou proxy Cloudflare actif | | Page blanche, CSS absent | Chemins `/assets/...` — vérifier que la racine servie est bien `/usr/share/nginx/html` | | Polices non chargées | CSP trop stricte — `fonts.googleapis.com` et `fonts.gstatic.com` doivent être autorisés | | Ancienne version affichée | Cache navigateur sur `index.html` — vérifier l'en-tête `Cache-Control: must-revalidate` | | Boucle de redirection | Redirection HTTPS dupliquée : Traefik **et** Nginx. Le `deploy/nginx.conf` fourni n'en contient pas — vérifier qu'on n'a pas utilisé `nginx.conf.example` par erreur | ### Le Dockerfile filtré par `.dockerignore` Journal caractéristique : ``` #1 transferring dockerfile: 2B done ERROR: failed to solve: failed to read dockerfile: open Dockerfile: no such file or directory ``` La ligne **`2B`** est le signal décisif : BuildKit a bien reçu un contexte, mais le Dockerfile qu'il en extrait est vide. Le fichier existe donc dans le dépôt — c'est `.dockerignore` qui l'a rendu invisible. BuildKit, utilisé par Coolify et par Docker depuis la version 23, applique les exclusions **avant** de lire le Dockerfile. Le builder classique tolérait ce cas ; BuildKit non. Vérification : ```bash grep -nE '^\s*(Dockerfile|\.dockerignore)\s*$' .dockerignore ``` Si l'une des deux lignes ressort, supprime-la et repousse. Ces fichiers n'ont pas besoin d'y figurer : le `RUN rm` du Dockerfile les retire déjà de l'image finale, donc ils ne sont jamais servis au public. ### Vérifier que le fichier est bien dans le dépôt Si la ligne `2B` **n'apparaît pas**, le fichier est réellement absent : ```bash git ls-files | grep -E '^Dockerfile$' ``` Sans résultat, il n'a pas été commité : ```bash git add Dockerfile .dockerignore deploy/ git commit -m "Ajout des fichiers de déploiement" git push ``` Contrôle également, dans **Configuration → Build** de Coolify : - **Base Directory** : `/` - **Dockerfile Location** : `/Dockerfile` Si le dépôt contient le site dans un sous-dossier, ces deux chemins doivent être ajustés en conséquence. Journaux : ```bash docker logs -f # Conteneur du site docker logs -f coolify-proxy # Traefik ```