Coolify peut servir uniquement de reverse proxy Traefik et de gestionnaire de certificats, pendant que la livraison passe par un script versionné qui appelle docker compose. C’est le choix de saas-core : la définition de la pile vit dans un dépôt, pas dans l’interface du PaaS, ce qui garde la livraison scriptable, vérifiable et réversible. Retirer Coolify ne demanderait alors que de remplacer le proxy.

Qu’est-ce que Coolify ?

Coolify est une plateforme d’hébergement applicatif (PaaS) open source que l’on installe sur ses propres serveurs. Elle déploie des applications, des fichiers Docker Compose et des bases de données depuis une interface web, et place devant eux un reverse proxy qui obtient et renouvelle les certificats TLS.

La promesse est de retirer une part d’exploitation : TLS automatique, journaux, variables d’environnement et déploiement en quelques clics. Elle est réelle pour une équipe qui pilote tout depuis l’interface. Elle devient une contrainte quand la livraison doit être automatisée, vérifiée et rejouable à l’identique.

Quel rôle joue Traefik dans cette architecture ?

Traefik est le reverse proxy qui termine le TLS et route chaque nom de domaine vers le bon conteneur. Avec son fournisseur Docker, il lit sa configuration dans des étiquettes (labels) posées sur les services. La configuration de routage vit donc dans le fichier Compose, pas dans l’outil.

Extrait du modèle utilisé par la pile d’essai, avec un nom de domaine générique :

services:
  api:
    networks: [interne, coolify]
    labels:
      - traefik.enable=true
      - traefik.docker.network=coolify
      - traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https
      - traefik.http.middlewares.redirect-to-https.redirectscheme.permanent=true
      - traefik.http.routers.api-http.entryPoints=http
      - traefik.http.routers.api-http.rule=Host(`api.example.com`)
      - traefik.http.routers.api-http.middlewares=redirect-to-https
      - traefik.http.routers.api-https.entryPoints=https
      - traefik.http.routers.api-https.rule=Host(`api.example.com`)
      - traefik.http.routers.api-https.tls=true
      - traefik.http.routers.api-https.tls.certresolver=letsencrypt
      - traefik.http.services.api.loadbalancer.server.port=3000

networks:
  interne:
    driver: bridge
  coolify:
    external: true

Deux détails évitent des pannes difficiles à diagnostiquer. traefik.docker.network est indispensable dès qu’un conteneur rejoint deux réseaux : sans lui, Traefik peut viser la mauvaise adresse et la connexion expire après la poignée de main TLS. Et seuls les services exposés rejoignent le réseau du proxy ; la base et le cache restent sur le réseau privé interne.

Pourquoi ne pas livrer par l’interface du PaaS ?

Parce qu’une interface ne se pilote pas comme un script. L’essai de Coolify mené pour saas-core, sur des critères écrits d’avance, a relevé quatre constats :

  • l’assistant de création enregistrait de travers la référence d’image par empreinte, sans le signaler ;
  • l’éditeur de Compose n’acceptait pas de modification programmatique, et les gestes destructeurs exigeaient le mot de passe du compte ;
  • le réseau partagé de Coolify héberge ses propres conteneurs, qui répondent aux noms postgres et redis : une connexion sur deux de la pile partait vers le plan de contrôle du PaaS ;
  • la réversibilité, en revanche, était acquise : la même pile tournait en docker compose up -d, sans Coolify.

L’essai s’est conclu sans adoption de Coolify pour livrer. Il reste installé pour son proxy et ses certificats. Son interface est devenue un miroir périmé : la copie du Compose qu’elle affiche porte d’anciennes empreintes et d’anciens noms de services. Y redéployer écraserait la version livrée et remettrait la pile sur un réseau plat. La règle est écrite dans les consignes du projet : ne jamais redéployer depuis l’interface Coolify.

Pourquoi la source de vérité doit-elle vivre hors du PaaS ?

Pour que l’abandon de l’outil ne coûte rien. Le critère retenu est simple : quitter Coolify ne doit demander aucun git revert dans le code du produit. Trois règles en découlent :

  1. La définition de la pile est un fichier versionné, exécutable par un docker compose up -d nu. Les étiquettes Traefik y sont explicites, plutôt que générées par le PaaS.
  2. Le produit ignore où il tourne. Le workflow de déploiement ne nomme ni la machine, ni le chemin du script, ni l’hébergeur : il appelle une commande déclarée dans une variable de dépôt.
  3. Aucun secret ne transite par l’interface. Les secrets sont des fichiers montés en lecture seule, lus par la convention _FILE.

Le format stable devient l’ensemble des étiquettes Traefik : tout Traefik v3 qui lit le fournisseur Docker les consomme tel quel. Sortir de Coolify reviendrait à refaire la configuration statique du proxy (points d’entrée, résolveur de certificats).

Comment se déroule une livraison ?

Une étiquette vX.Y.Z poussée sur main est le seul geste de mise en ligne. Le reste s’enchaîne :

ÉtapeCe qu’elle fait
Constructionimages api, worker, admin construites, poussées par empreinte et signées avec Cosign
Recopieimage et artefacts de signature copiés vers le registre local de la machine
Gate livrerattend que les workflows ci et e2e aient conclu en succès sur le SHA étiqueté
livrer.shrésout l’étiquette en empreintes, vérifie les signatures, réécrit le Compose, déploie, attend la santé

Le gate interroge la conclusion réelle des runs de ce commit. Il attend au plus 30 minutes, relance au plus deux fois un run annulé par la concurrence, et refuse sur un échec comme sur une absence de run : « aucun test n’a tourné » mérite le même refus que « les tests ont échoué ». Une signature atteste l’origine d’une image, jamais sa qualité ; c’est le gate qui empêche de livrer un commit rouge.

Le script se lance aussi à la main, avec un passage à sec qui affiche le diff des empreintes sans rien écrire :

./scripts/livrer.sh v1.2.0 --sec
./scripts/livrer.sh v1.2.0

Il refuse toute étiquette qui n’est pas de la forme vX.Y.Z, vérifie la signature de chaque empreinte avant toute écriture, puis attend que l’API (/health) et l’admin répondent 200, pendant 90 secondes au plus. Sans réponse, il repose le fichier précédent, redéploie et indique si la panne est antérieure à la livraison.

Comment revenir en arrière ?

On relivre une étiquette précédente, déjà construite et signée, sans rien reconstruire :

gh workflow run deploy.yml -f etiquette=v1.1.0

Le retour arrière fonctionne parce que la pile désigne ses images par empreinte (@sha256:…), jamais par une étiquette mouvante comme latest : reposer une empreinte, c’est revenir à un état qui a déjà tourné. Les livraisons sont sérialisées dans un groupe de concurrence sans annulation, pour que deux déploiements ne réécrivent jamais la pile en même temps. Côté base, les migrations restent compatibles avec la version précédente, si bien qu’aucun geste n’est nécessaire sur le schéma.

Comment saas-core l’intègre

  • saas-core-infra/atelier/compose.essai.yml : projet saas-core-essai, images par empreinte, réseau privé interne, réseau externe coolify réservé aux services exposés, noms distinctifs saas-db et saas-cache, étiquettes Traefik explicites, secrets montés par fichier.
  • saas-core-infra/atelier/scripts/livrer.sh : résolution, cosign verify, réécriture des empreintes, déploiement, contrôle de santé et retour arrière.
  • .github/workflows/release-images.yml : construction, signature, recopie, puis job livrer avec son gate ci + e2e et l’appel de DEPLOY_COMMAND.
  • .github/workflows/deploy.yml : déclencheur manuel seulement, chemin du retour arrière.
  • apps/api/src/health.controller.ts : /health renvoie la version livrée, ce qui permet de vérifier qu’une livraison a bien eu lieu.

Deux limites sont documentées. livrer.sh ne réécrit que les empreintes : un nouveau service, une variable ou un réseau s’appliquent à la main sur le serveur. Et l’installation de Coolify comme la configuration statique de son proxy ne sont versionnées dans aucun dépôt. La machine d’essai n’est pas un environnement de production.

La signature vérifiée par livrer.sh est décrite dans Cosign et SLSA pour signer ses images Docker, et le gate qui précède la livraison dans le pipeline CI/CD GitHub Actions d’un SaaS.

Les workflows de construction et de livraison font partie du kit ; la page tarifs précise ce qui est inclus.