Site personnel courcellematthi.eu
This commit is contained in:
@@ -0,0 +1,379 @@
|
||||
# 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
|
||||
`<link rel="canonical" href="https://www.courcellematthi.eu/">`. 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 `<loc>`
|
||||
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 <nom_du_conteneur> --format '{{json .NetworkSettings.Networks}}' | python3 -m json.tool
|
||||
```
|
||||
|
||||
Si `coolify` n'apparaît pas dans la liste :
|
||||
|
||||
```bash
|
||||
docker network connect coolify <nom_du_conteneur>
|
||||
```
|
||||
|
||||
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.<nom-généré-par-coolify>.loadbalancer.server.port=80
|
||||
```
|
||||
|
||||
Récupère le nom généré dans les étiquettes existantes du conteneur :
|
||||
|
||||
```bash
|
||||
docker inspect <conteneur> --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.<nom-du-routeur>.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
|
||||
<lastmod>2026-08-05</lastmod>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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 <nom_du_conteneur> # Conteneur du site
|
||||
docker logs -f coolify-proxy # Traefik
|
||||
```
|
||||
Reference in New Issue
Block a user