Skip to content

Déployer sur un PaaS (image GHCR)

Le guide Coolify / VPS construit l'image Docker sur le serveur. Pour une cible PaaS (DigitalOcean App Platform, Heroku…) — ou pour alléger un VPS — TimePick suit le modèle « image unique tirée » : une image canonique construite et testée en CI, publiée sur GHCR, puis tirée (pull) par tous les hôtes. Cette fiche décrit ce modèle, ses contraintes, et renvoie aux kits de déploiement fournis dans le dépôt.

À lire d'abord : ce qui est prouvé et ce qui ne l'est pas

L'image canonique est publiée et publique sur GHCR depuis la version v0.29.0 (tirage anonyme vérifié, sans identifiants). En revanche, aucun déploiement PaaS réel n'a encore été exécuté avec ces kits — le détail honnête de chaque point est signalé dans les sections concernées.

Le modèle « image unique tirée »

Le workflow CI .github/workflows/docker.yml :

  1. se déclenche au push d'un tag de version v* (ou manuellement) ;
  2. construit la variante amd64, la démarre contre un PostgreSQL de service et attend que GET /health réponde 200 (migrations comprises) ;
  3. seulement si ce test passe, construit et publie l'image multi-architecture (linux/amd64 + linux/arm64) sur ghcr.io/timepick-app/timepick, avec les tags latest (versions finales uniquement), <tag git> (ex. v0.29.0) et sha-<court>.

Une seule image sert ainsi toutes les cibles : amd64 pour les PaaS, arm64 pour un VPS ARM (le chemin Coolify actuel, en mode pull). Aucun tag latest ou vX.Y cassé ne peut être publié à une flotte qui tire en pull, puisque la publication est conditionnée au test de démarrage.

Image publiée et publique depuis v0.29.0

Depuis la version v0.29.0, l'image est publiée sur GHCR et le paquet est public : docker pull ghcr.io/timepick-app/timepick:<tag> fonctionne en anonyme, sans docker login. Toujours épingler un tag versionné (vX.Y.Z) — jamais latest en production. Le repli « build depuis les sources » documenté dans chaque kit (Heroku heroku.yml, DigitalOcean « build from source ») reste disponible, mais n'est plus nécessaire pour démarrer.

VITE_API_URL déjà réglée — ne pas y toucher

L'image canonique est construite avec VITE_API_URL=/api (chemin relatif) : le frontend appelle l'API en same-origin, quel que soit le domaine. C'est ce qui rend une image unique portable d'un hôte à l'autre. Conséquence : ne jamais définir VITE_API_URL côté hébergeur — c'est une variable de build, déjà figée dans l'image (voir Variables d'environnement).

Contraintes de déploiement (toutes plateformes)

ContrainteDétail
1 seule instance (scale = 1)Les limitations de débit (voir Sécurité) sont maintenues en mémoire, par processus : à N instances, chaque quota est multiplié par N et la protection anti-abus se dilue. Ne pas activer d'autoscaling horizontal.
Chevauchement de redéploiement : OKLes migrations exécutées au démarrage sont sérialisées par un verrou PostgreSQL (advisory lock) : deux conteneurs peuvent se chevaucher pendant un redéploiement sans corruption. C'est le régime permanent à plusieurs instances qui est exclu, pas le redeploy sans coupure.
Healthcheck HTTP sur GET /healthRépond 200 (JSON) même quand l'email est degraded — sonde de disponibilité fiable. ⚠️ DigitalOcean App Platform sonde par défaut le port 8080 en TCP : déclarer http_port: 3000 et health_check.http_path: /health (fait dans le kit).
PostgreSQL managé ≥ 16Version minimale supportée par le projet (voir Prérequis). Les offres managées Heroku Postgres et DO Managed PostgreSQL conviennent.
Système de fichiers éphémère → S3 + secrets en envHeroku et DO App Platform n'offrent pas de volume persistant : STORAGE_DRIVER=s3 est obligatoire (sinon les uploads sont perdus à chaque redéploiement) et JWT_SECRET + ENCRYPTION_KEY doivent être des variables d'environnement, posées avant le premier démarrage. Migration d'une installation existante : extraire d'abord les valeurs en place — voir la procédure disque → environnement.
Fenêtre de configuration initialeDès le premier démarrage, les routes /api/setup/* sont publiques tant qu'aucun administrateur n'existe : dérouler l'assistant immédiatement après le déploiement (créer et vérifier le premier admin par lien de connexion) — voir Configuration initiale.

Variables d'environnement minimales

Sémantique détaillée dans Variables d'environnement. Sur un PaaS à système de fichiers éphémère, les blocs « secrets » et « S3 » ne sont plus optionnels :

VariableRôleRequise sur PaaS
DATABASE_URLPostgreSQL managé ≥ 16Oui
JWT_SECRETSignature des sessions et des liens de connexion (magic links)Oui (FS éphémère)
ENCRYPTION_KEY64 caractères hexadécimaux ; chiffre le mot de passe SMTP (et la clé API email) en baseOui (FS éphémère — installation existante : extraire l'ancienne clé d'abord)
APP_URLBase HTTPS publique des liens envoyés par emailOui
NODE_ENVproductionOui
PORTPort d'écoute (défaut 3000) — Heroku l'injecte automatiquement, DigitalOcean route via http_portSelon plateforme
SMTP_* (7 variables)Provisionnement initial du serveur d'email (premier démarrage uniquement)Recommandé (sinon saisie dans l'assistant)
EMAIL_PROVIDER, EMAIL_API_CREDENTIALSFournisseur d'email HTTP (brevo|mailjet|scaleway|sweego|resend) — uniquement si l'hôte bloque le SMTP sortant ; EMAIL_API_KEY reste acceptée en alias dépréciéNon (défaut smtp)
STORAGE_DRIVERs3 sur FS éphémère (défaut local)Oui (=s3)
S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEYObject storage S3-compatible (démarrage refusé si incomplet)Oui si STORAGE_DRIVER=s3
S3_REGION, S3_PUBLIC_BASE_URLOptionnelles — défauts auto / <endpoint>/<bucket> (Cloudflare R2 : S3_PUBLIC_BASE_URL obligatoire)Optionnel
VITE_API_URLVariable de build, déjà figée à /api dans l'image publiéeNe pas définir

Kits par cible (dossier deploy/ du dépôt)

Chaque kit fournit les fichiers de configuration prêts à l'emploi et les étapes manuelles détaillées. Aucun de ces kits n'a encore été exécuté contre un compte réel — ils sont livrés « prêts à lancer », avec leur protocole de vérification.

CibleKitNote
DigitalOcean App Platformdeploy/digitalocean/App Spec complète (app.yaml) : image GHCR, http_port: 3000, healthcheck HTTP, 1 instance, PostgreSQL managé, STORAGE_DRIVER=s3. ⚠️ Voir l'avertissement SMTP ci-dessous.
Herokudeploy/heroku/Deux voies : build via heroku.yml (avec VITE_API_URL=/api en build-arg), ou pull GHCR re-poussé vers le registre Heroku. Dynos à FS éphémère → S3 + secrets en env obligatoires.
Coolify en mode pulldeploy/coolify-pull/Bascule d'un Coolify « build local » vers l'image GHCR (arm64) pour alléger le VPS. Prérequis avant bascule : secrets en variables d'environnement et uploads hors volume (une nouvelle ressource Coolify monte des volumes neufs et vides). Le build local reste intact en repli.

DigitalOcean App Platform : SMTP sortant NON VÉRIFIÉ

La politique de DigitalOcean App Platform sur le trafic SMTP sortant (ports 587/465) ne repose, à la date de rédaction, que sur des réponses de forum — elle n'a pas été vérifiée en conditions réelles. Ne pas considérer l'envoi d'emails comme acquis sur cette plateforme tant qu'un lien de connexion (magic link) n'a pas été réellement reçu depuis une application déployée (le protocole de preuve pas à pas est dans le README du kit ; le bouton « Tester la connexion » ne constitue pas une preuve). Si le verdict est négatif, il n'existe pas de repli sur un port SMTP alternatif sur App Platform : la solution est le mode Envoi par API (HTTP), avec des fournisseurs européens en tête de liste.

Rappel : rien ne change pour les installations existantes

Ce modèle est une option supplémentaire. Les défauts de TimePick restent le SMTP et le stockage local des uploads : une installation Coolify/VPS existante (build local, volumes persistants) continue de fonctionner à l'identique, sans aucune action requise.

Pour aller plus loin

Publié sous licence FSL-1.1-MIT.