380 lines
11 KiB
Markdown
380 lines
11 KiB
Markdown
# 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
|
|
```
|