Migration de mon application Stop Repeat de Lovable vers un Coolify self-hosted¶
Résumé
J'ai migré mon application Stop Repeat (gestionnaire de tâches familiales React/Vite) hébergée chez Lovable Cloud avec une base Supabase managée vers une infrastructure entièrement self-hosted sur ma VM Azure (Coolify + Traefik + Supabase complet). Cette note retrace toute la procédure : refactor du code, déploiement de Supabase self-hosted (15 conteneurs), mise en place de la CI/CD via GitHub App, configuration des edge functions, du SMTP, des cron jobs et de Google OAuth.
| Propriété | Valeur |
|---|---|
| Difficulté | Avancé |
| OS / Environnement | Debian 13, Coolify v4.0.0-beta.470, Traefik v3.6, Supabase self-hosted |
| Dernière mise à jour | 2026-04-25 |
Contexte¶
Mon app Stop Repeat est un SPA React + Vite + TypeScript permettant à des parents de créer des tâches récurrentes pour leurs enfants, avec gamification (récompenses, pénalités, streaks, badges) et notifications push. Le code est public sur TutoTech/family-flow.
Initialement, le projet était hébergé sur Lovable Cloud :
- Frontend servi par Lovable
- Base de données + Auth + Storage + 10 Edge Functions sur un projet Supabase managé (
fzstjebbxbejypgwamqx.supabase.co) - Authentification Google via un wrapper propriétaire
@lovable.dev/cloud-auth-js - Paiements Stripe + notifications push VAPID
J'avais plusieurs raisons de vouloir m'auto-héberger :
- Autonomie vis-à-vis de Lovable et Supabase Cloud
- Maîtrise de mes données et de l'infrastructure
- Coûts (Supabase Cloud devient payant au-delà des limites du free tier)
- CI/CD : déclencher un redéploiement automatique à chaque push GitHub
Je disposais déjà d'un serveur Coolify (v4) sur une VM Azure Debian 13 avec Traefik comme reverse proxy. J'ai donc décidé d'y déployer toute la stack.
J'ai gardé en parallèle le déploiement Lovable comme fallback de sécurité pendant toute la durée de la migration, en travaillant sur une branche dédiée coolify-deploy.
Prérequis¶
- Une VM avec au moins 8 Go de RAM (j'ai dû redimensionner la mienne en
Standard_B2as_v2Azure car Supabase self-hosted ajoute environ 2 Go d'utilisation) - Coolify déjà installé et fonctionnel avec Traefik comme reverse proxy
- Un domaine pointant vers le serveur (j'utilise
tutotech.org) avec la possibilité de créer des sous-domaines - Un fournisseur DNS supportant l'API DNS challenge pour Let's Encrypt (j'utilise Infomaniak)
- Une clé SSH pour pousser sur GitHub
- Le repo de l'app clonable localement
- Les secrets de l'app (VAPID, Stripe, OAuth Google, SMTP) à portée de main
Architecture cible¶
INTERNET
│
▼
[Traefik (coolify-proxy)]
────────┬──────────┬──────────┬──────────────┐
│ │ │ │
▼ ▼ ▼ ▼
stop-repeat supabase studio.supabase webhook
.tutotech.org .tutotech.org .tutotech.org .tutotech.org
│ │ │ │
▼ ▼ ▼ ▼
[nginx- [Kong [Studio UI] [Coolify
unprivileged] gateway] │ webhook]
│ │ │ │
[SPA Vite] ┌──┴───┐ │ │
▼ ▼ │ │
[GoTrue] [PostgREST] │ │
[Storage] [Realtime] │ │
[Edge functions] │ │
│ │ │
▼ ▼ │
[PostgreSQL 15 + pg_cron + pg_net] │
│
GitHub push → coolify-deploy ─────────────────┘
↓
Auto-build + redeploy
Procédure¶
J'ai découpé la migration en 7 phases que j'ai exécutées dans l'ordre.
Phase 1 : Préparation du code¶
Je devais d'abord adapter le code de l'app pour qu'il soit déployable hors de Lovable.
1.1 Cloner le repo et créer une branche dédiée¶
J'ai configuré ma clé SSH et cloné le repo, puis créé une branche coolify-deploy pour que main reste exclusivement utilisé par Lovable comme fallback :
1.2 Remplacer Lovable Auth par Supabase OAuth natif¶
Le wrapper @lovable.dev/cloud-auth-js était utilisé uniquement dans LoginForm.tsx et SignupForm.tsx pour le bouton « Continuer avec Google ». C'était une fine couche d'OAuth relais qui pouvait être remplacée par l'API native de Supabase.
J'ai supprimé le wrapper Lovable :
Puis dans src/components/auth/LoginForm.tsx et src/components/auth/SignupForm.tsx, j'ai remplacé l'import et l'appel :
import { lovable } from "@/integrations/lovable/index";
// ...
const { error } = await lovable.auth.signInWithOAuth("google", {
redirect_uri: window.location.origin
});
import { supabase } from "@/integrations/supabase/client";
// ...
const { error } = await supabase.auth.signInWithOAuth({
provider: "google",
options: { redirectTo: window.location.origin + "/" },
});
J'ai également retiré la dépendance @lovable.dev/cloud-auth-js du package.json.
Attention au redirect_to
À mon premier essai, j'avais mis redirectTo: window.location.origin + "/auth/callback" mais cette route n'existait pas dans React Router et le user tombait sur une 404 après le login Google. J'ai dû corriger en pointant vers / (la landing page) — le client Supabase parse automatiquement le hash de l'URL au chargement de la page et établit la session.
1.3 Créer un Dockerfile multi-stage¶
Le repo n'avait aucune containerisation. J'ai créé un Dockerfile multi-stage : étape builder avec Node 20 pour npm run build, puis étape runtime avec nginx-unprivileged:1.27-alpine.
# syntax=docker/dockerfile:1.7
# ---------- Build stage ----------
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --no-audit --no-fund
# Build-time Supabase config (injected by Coolify as ARG)
ARG VITE_SUPABASE_URL
ARG VITE_SUPABASE_PUBLISHABLE_KEY
ARG VITE_SUPABASE_PROJECT_ID
ENV VITE_SUPABASE_URL=$VITE_SUPABASE_URL
ENV VITE_SUPABASE_PUBLISHABLE_KEY=$VITE_SUPABASE_PUBLISHABLE_KEY
ENV VITE_SUPABASE_PROJECT_ID=$VITE_SUPABASE_PROJECT_ID
COPY . .
RUN npm run build
# ---------- Runtime stage ----------
FROM nginxinc/nginx-unprivileged:1.27-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 8080
J'ai accompagné ce Dockerfile d'un nginx.conf avec fallback SPA (essentiel pour React Router), gzip, cache assets et headers de sécurité :
server {
listen 8080;
server_name _;
root /usr/share/nginx/html;
index index.html;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
location ~* \.(js|css|woff2?|png|jpg|svg|ico)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
location / {
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
}
J'ai testé le build localement avec docker build et docker run — image fonctionnelle, page Stop Repeat servie correctement.
1.4 Commit et push¶
git add Dockerfile nginx.conf .dockerignore package.json src/components/auth/
git commit -m "Prépare le déploiement Coolify self-hosted"
git push -u origin coolify-deploy
Phase 2 : Déployer Supabase self-hosted¶
C'est la phase la plus lourde — Supabase self-hosted comporte 15 conteneurs : db (PostgreSQL 15), auth (GoTrue), rest (PostgREST), realtime, storage, imgproxy, kong, studio, meta, functions (Deno), analytics (Logflare), vector, supavisor (pooler), db-config, deno-cache.
2.1 Récupérer le compose officiel¶
J'ai téléchargé les fichiers officiels du repo Supabase :
mkdir -p /opt/docker/supabase
cd /opt/docker/supabase
curl -fsSL https://raw.githubusercontent.com/supabase/supabase/master/docker/docker-compose.yml -o docker-compose.yml
curl -fsSL https://raw.githubusercontent.com/supabase/supabase/master/docker/.env.example -o .env.example
# Récupérer aussi le dossier volumes/ qui contient kong.yml, init SQL, etc.
git clone --depth 1 --filter=blob:none --sparse https://github.com/supabase/supabase.git /tmp/supabase-src
cd /tmp/supabase-src && git sparse-checkout set docker/volumes
cp -r docker/volumes /opt/docker/supabase/volumes
2.2 Générer les secrets¶
Supabase fournit deux scripts de génération : generate-keys.sh (clés HS256 legacy) et add-new-auth-keys.sh (clés ES256 asymétriques).
cd /opt/docker/supabase
mkdir utils
curl -fsSL https://raw.githubusercontent.com/supabase/supabase/master/docker/utils/generate-keys.sh -o utils/generate-keys.sh
curl -fsSL https://raw.githubusercontent.com/supabase/supabase/master/docker/utils/add-new-auth-keys.sh -o utils/add-new-auth-keys.sh
# Génère JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY, POSTGRES_PASSWORD, etc.
sh utils/generate-keys.sh | tee /tmp/supabase-keys.txt
# Génère les nouvelles clés ES256 (nécessite Node + openssl)
docker run --rm -v "$(pwd):/work" -w /work node:20-alpine \
sh -c "apk add --no-cache openssl && sh utils/add-new-auth-keys.sh --update-env"
Ensuite j'ai consolidé toutes les variables (URLs, DB, Auth, Storage, Functions, Kong, etc.) dans le fichier .env.
Variables URL importantes
J'ai bien renseigné :
SUPABASE_PUBLIC_URL=https://supabase.tutotech.orgAPI_EXTERNAL_URL=https://supabase.tutotech.orgSITE_URL=https://stop-repeat.tutotech.orgADDITIONAL_REDIRECT_URLS=https://stop-repeat.tutotech.org/**
2.3 Adapter le compose pour Traefik¶
Je n'ai pas modifié le docker-compose.yml officiel, j'ai créé un docker-compose.override.yml qui :
- Retire l'exposition des ports 8000/8443 de Kong sur l'hôte (Traefik intercepte)
- Ajoute les labels Traefik sur Kong pour
supabase.tutotech.org(avec CORS) - Ajoute les labels Traefik sur Studio pour
studio.supabase.tutotech.org(avec IPWhiteList VPN only) - Connecte Kong et Studio au réseau
traefik-supabase
services:
kong:
ports: !reset []
networks:
- default
- traefik-supabase
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik-supabase"
- "traefik.http.routers.supabase-api.rule=Host(`supabase.tutotech.org`)"
- "traefik.http.routers.supabase-api.entrypoints=https"
- "traefik.http.routers.supabase-api.tls.certresolver=letsencrypt"
- "traefik.http.services.supabase-api.loadbalancer.server.port=8000"
- "traefik.http.routers.supabase-api.middlewares=supabase-cors"
- "traefik.http.middlewares.supabase-cors.headers.accesscontrolalloworiginlist=https://stop-repeat.tutotech.org"
# ...
studio:
networks:
- default
- traefik-supabase
labels:
- "traefik.enable=true"
- "traefik.http.routers.supabase-studio.rule=Host(`studio.supabase.tutotech.org`)"
- "traefik.http.routers.supabase-studio.middlewares=studio-vpn-only"
- "traefik.http.middlewares.studio-vpn-only.ipwhitelist.sourcerange=10.223.172.0/24,127.0.0.1/32"
# ...
networks:
traefik-supabase:
external: true
name: traefik-supabase
J'ai créé le réseau externe puis connecté coolify-proxy (Traefik) à ce réseau :
J'ai aussi ajouté traefik-supabase (et plus tard traefik-stop-repeat) au compose persistant de Coolify-proxy (/data/coolify/proxy/docker-compose.yml) pour que les réseaux soient maintenus aux restarts de Traefik.
2.4 Créer les enregistrements DNS¶
J'ai créé chez Infomaniak :
| Sous-domaine | Type | Cible | Usage |
|---|---|---|---|
supabase.tutotech.org |
A | IP publique du serveur | API Supabase (Kong) |
studio.supabase.tutotech.org |
A | IP publique du serveur | Studio (filtré VPN) |
stop-repeat.tutotech.org |
A | IP publique du serveur | Frontend |
webhook.tutotech.org |
A | IP publique du serveur | Webhooks GitHub |
2.5 Démarrer la stack¶
Au bout de 30 secondes, les 15 services étaient healthy. La RAM est passée de 2.6 Go à 4.4 Go utilisés (sur 7.7 Go disponibles).
2.6 Appliquer les 67 migrations SQL¶
Mon repo family-flow contenait 67 migrations SQL dans supabase/migrations/. Je les ai rejouées en ordre chronologique :
cd /home/nicolas-bodaine/git/family-flow/supabase/migrations
POSTGRES_PW=$(grep "^POSTGRES_PASSWORD=" /opt/docker/supabase/.env | cut -d= -f2)
for f in $(ls *.sql | sort); do
docker exec -i supabase-db env PGPASSWORD="$POSTGRES_PW" \
psql -U postgres -d postgres < "$f"
done
J'ai obtenu 66 succès et 1 erreur sur une migration qui tentait d'insérer des données liées à un user (FK vers auth.users). Ce n'était pas bloquant — la DB est vierge à ce stade. 19 tables publiques ont été créées (families, profiles, task_templates, task_instances, rewards, child_stats, etc.).
Phase 3 : Déploiement du frontend avec CI/CD GitHub App¶
3.1 Créer l'application Coolify via API¶
J'ai utilisé l'API Coolify pour créer l'application (j'avais déjà créé une GitHub App « coolify-tutotech-org-github-app-1 » sous mon organisation TutoTech) :
COOLIFY_TOKEN="..."
SERVER_UUID="..."
PROJECT_TUTOTECH="..."
GITHUB_APP_UUID="..." # uuid de coolify-tutotech-org-github-app-1
curl -X POST "http://localhost:8000/api/v1/applications/private-github-app" \
-H "Authorization: Bearer $COOLIFY_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"project_uuid\": \"$PROJECT_TUTOTECH\",
\"server_uuid\": \"$SERVER_UUID\",
\"github_app_uuid\": \"$GITHUB_APP_UUID\",
\"environment_name\": \"production\",
\"git_repository\": \"TutoTech/family-flow\",
\"git_branch\": \"coolify-deploy\",
\"build_pack\": \"dockerfile\",
\"ports_exposes\": \"8080\",
\"name\": \"stop-repeat\",
\"domains\": \"https://stop-repeat.tutotech.org\",
\"instant_deploy\": false
}"
3.2 Configurer les variables d'environnement build-time¶
Les variables VITE_* doivent être disponibles au build (pas au runtime) pour que Vite les inline dans le bundle JS. Je les ai ajoutées via l'API :
APP_UUID="..."
ANON_KEY=$(grep "^ANON_KEY=" /opt/docker/supabase/.env | cut -d= -f2)
curl -X POST "http://localhost:8000/api/v1/applications/$APP_UUID/envs" \
-H "Authorization: Bearer $COOLIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"key": "VITE_SUPABASE_URL", "value": "https://supabase.tutotech.org"}'
curl -X POST ".../envs" -d "{\"key\": \"VITE_SUPABASE_PUBLISHABLE_KEY\", \"value\": \"$ANON_KEY\"}"
curl -X POST ".../envs" -d '{"key": "VITE_SUPABASE_PROJECT_ID", "value": "stop-repeat-selfhosted"}'
3.3 Premier déploiement¶
J'ai déclenché un déploiement manuel via l'API et il a réussi en ~17 secondes (build Docker + nginx).
curl "http://localhost:8000/api/v1/deploy?uuid=$APP_UUID&force=false" \
-H "Authorization: Bearer $COOLIFY_TOKEN"
L'app était accessible sur https://stop-repeat.tutotech.org avec un certificat Let's Encrypt valide (DNS challenge Infomaniak via la config Traefik existante).
3.4 Activer le webhook CI/CD¶
C'est ici que j'ai rencontré un piège : mon domaine coolify.tutotech.org pointe vers l'IP VPN privée (10.223.172.1) pour des raisons de sécurité. GitHub ne pouvait donc pas atteindre l'endpoint webhook depuis Internet.
Solution : un domaine public dédié pour les webhooks
J'ai créé un sous-domaine webhook.tutotech.org pointant vers l'IP publique, et configuré une route Traefik dédiée qui n'expose que le path /webhooks/ de Coolify.
J'ai créé un fichier de config dynamique Traefik :
http:
routers:
coolify-webhook:
rule: "Host(`webhook.tutotech.org`) && PathPrefix(`/webhooks/`)"
entryPoints:
- https
tls:
certResolver: letsencrypt
service: coolify-dashboard
services:
coolify-dashboard:
loadBalancer:
servers:
- url: "http://coolify:8080"
Puis dans la GitHub App (paramètres de l'organisation TutoTech), j'ai mis à jour l'URL du webhook vers :
Ne pas mettre /manage à la fin
Mon premier essai avec /webhooks/source/github/events/manage ne fonctionnait pas (cette route n'existe pas dans Coolify). La bonne URL est juste /webhooks/source/github/events.
À mon push suivant, le webhook a déclenché le déploiement en ~5 secondes. La CI/CD était opérationnelle.
Phase 4 : Edge Functions et secrets¶
4.1 Copier les 10 fonctions¶
Le service supabase-edge-functions du compose monte le dossier /opt/docker/supabase/volumes/functions/ vers /home/deno/functions/. J'ai copié mes 10 functions :
for fn in /home/nicolas-bodaine/git/family-flow/supabase/functions/*/; do
cp -r "$fn" /opt/docker/supabase/volumes/functions/
done
docker restart supabase-edge-functions
Les functions deviennent immédiatement accessibles sur :
4.2 Injecter les secrets via l'override¶
Les fonctions ont besoin de secrets pour fonctionner (Stripe, VAPID). Je les ai ajoutés au .env puis injectés dans le service functions via l'override :
services:
functions:
environment:
VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY}
VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY}
VAPID_EMAIL: ${VAPID_EMAIL}
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY}
STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET}
Réutiliser les VAPID de Lovable
J'ai récupéré les mêmes VAPID keys depuis le dashboard Supabase Lovable plutôt que d'en générer de nouvelles. Ainsi, les enfants déjà inscrits aux notifications push n'auront pas à re-autoriser au moment de la migration des données.
4.3 Configurer le SMTP pour les emails de confirmation¶
Au premier signup test, j'ai obtenu une erreur « Error sending confirmation email ». La cause : le .env pointait vers supabase-mail:2500 (le service mock du compose), inexistant.
J'ai créé un mot de passe d'application sur Infomaniak pour le compte admin@tutotech.org, puis configuré :
SMTP_HOST=mail.infomaniak.com
SMTP_PORT=465
SMTP_USER=admin@tutotech.org
SMTP_PASS=<mot-de-passe-application>
SMTP_SENDER_NAME=Stop Repeat
SMTP_ADMIN_EMAIL=admin@tutotech.org
Après docker compose up -d auth, le test de signup envoyait bien l'email de confirmation.
Phase 5 : pg_cron jobs¶
L'app a besoin de 3 tâches planifiées :
daily-task-reset: régénère les instances de tâches du jour pour toutes les familles (5h UTC)task-reminders: envoie les rappels push (toutes les 15 min)cleanup-evidence-photos: supprime les photos preuve expirées (toutes les heures)
Les extensions pg_cron et pg_net étaient déjà installées dans l'image supabase/postgres. J'ai créé les jobs :
-- Job 1 : daily-task-reset (5h UTC)
SELECT cron.schedule(
'daily-task-reset',
'0 5 * * *',
$$
SELECT net.http_post(
url := 'https://supabase.tutotech.org/functions/v1/daily-task-reset',
headers := '{"Content-Type": "application/json", "Authorization": "Bearer <ANON_KEY>"}'::jsonb,
body := '{}'::jsonb
);
$$
);
-- Job 2 : task-reminders (toutes les 15 min)
SELECT cron.schedule(
'task-reminders',
'*/15 * * * *',
$$ SELECT net.http_post(url := 'https://supabase.tutotech.org/functions/v1/task-reminders', ...); $$
);
-- Job 3 : cleanup-evidence-photos (toutes les heures, fonction SQL native)
SELECT cron.schedule(
'cleanup-evidence-photos',
'0 * * * *',
$$ SELECT public.cleanup_expired_evidence_photos(); $$
);
J'ai vérifié leur bon fonctionnement en déclenchant un appel manuel via pg_net :
SELECT net.http_post(url := 'https://supabase.tutotech.org/functions/v1/task-reminders', ...);
SELECT id, status_code, content::text FROM net._http_response ORDER BY id DESC LIMIT 1;
-- Retour : status_code=200, content={"reminders_sent":0} ✓
Phase 6 : Google OAuth provider¶
Pour activer le bouton « Continuer avec Google », j'ai créé un OAuth 2.0 Client ID sur Google Cloud Console :
- OAuth consent screen → External, app name « Stop Repeat », support email
admin@tutotech.org - Credentials → Create OAuth client ID → Web application
- Authorized JavaScript origins :
https://stop-repeat.tutotech.org - Authorized redirect URIs :
https://supabase.tutotech.org/auth/v1/callback
J'ai injecté le Client ID et Client Secret dans .env puis dans le service auth via l'override :
services:
auth:
environment:
GOTRUE_EXTERNAL_GOOGLE_ENABLED: ${GOOGLE_ENABLED}
GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
GOTRUE_EXTERNAL_GOOGLE_SECRET: ${GOOGLE_SECRET}
GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI: ${API_EXTERNAL_URL}/auth/v1/callback
Après docker compose up -d auth, GoTrue exposait "google": true dans son endpoint /auth/v1/settings, et un GET /auth/v1/authorize?provider=google retournait bien un 302 vers accounts.google.com.
Phase 7 : Tests fonctionnels¶
J'ai validé le bout-en-bout :
- Signup parent avec email/mot de passe → email de confirmation reçu sur
admin@tutotech.org→ confirmation OK - Création d'un enfant via la edge function
create-child-account→ OK - OAuth Google : après correction du
redirectTo(de/auth/callbackvers/), le compte Google est correctement créé dansauth.usersavec son nom (récupéré depuis Google) et l'utilisateur est redirigé vers la landing page avec sa session active - CI/CD GitHub App : un push sur
coolify-deploydéclenche un build Coolify en moins de 5 secondes, et le déploiement aboutit en ~17 secondes
Vérification¶
Quelques commandes utiles pour vérifier que tout fonctionne :
# Tous les conteneurs Supabase healthy
docker ps --filter "name=supabase" --format "{{.Names}}\t{{.Status}}"
# Test API REST (avec apikey)
ANON=$(grep "^ANON_KEY=" /opt/docker/supabase/.env | cut -d= -f2)
curl -s "https://supabase.tutotech.org/rest/v1/" -H "apikey: $ANON" | jq '.swagger'
# Test Auth settings
curl -s "https://supabase.tutotech.org/auth/v1/settings" -H "apikey: $ANON" | jq '.external'
# Test edge function
curl -s "https://supabase.tutotech.org/functions/v1/push-vapid-key" -H "apikey: $ANON"
# Test Google OAuth (doit renvoyer 302 vers accounts.google.com)
curl -sI "https://supabase.tutotech.org/auth/v1/authorize?provider=google&redirect_to=https://stop-repeat.tutotech.org/" -H "apikey: $ANON"
# Lister les cron jobs
docker exec supabase-db psql -U postgres -d postgres -c "SELECT jobid, jobname, schedule FROM cron.job;"
# Tester la CI/CD
git commit --allow-empty -m "test ci/cd" && git push origin coolify-deploy
# → Vérifier dans l'UI Coolify qu'un build se lance
Résultat attendu
Tous les endpoints répondent avec un code HTTP 2xx (ou 302 pour OAuth, ou 401 si auth requise), les certificats sont émis par Let's Encrypt, et un push GitHub déclenche bien un nouveau déploiement automatique.
Pièges rencontrés et solutions¶
| Piège | Symptôme | Solution |
|---|---|---|
| Coolify domain en VPN-only | GitHub ne peut pas envoyer de webhook | Créer un sous-domaine public webhook.* exposant uniquement /webhooks/* |
| Mauvaise URL webhook | Le push ne déclenche rien | Utiliser /webhooks/source/github/events (sans /manage) |
acme.json permissions |
Cert auto-signé Traefik au lieu de Let's Encrypt | chmod 600 /data/coolify/proxy/acme.json (Coolify l'écrase parfois en 770) |
Route /auth/callback inexistante |
404 après login Google | Pointer redirectTo vers / (Supabase parse le hash automatiquement) |
SMTP supabase-mail introuvable |
« Error sending confirmation email » | Configurer un vrai SMTP (j'utilise Infomaniak avec mot de passe d'application) |
| RAM serrée | OOM kill possible avec 4 Go | Redimensionner la VM Azure à B2as_v2 (8 Go) |
is_build_time non accepté par l'API |
Erreur de validation à l'ajout d'env | Ne pas l'envoyer — Coolify détecte automatiquement les VITE_* comme build-time |
Ce qui reste à faire¶
Prochaine étape : migration des données de production
Cette note couvre le déploiement de l'infrastructure self-hosted. Il me reste à migrer les données de production depuis Lovable :
auth.users(avec les hashes bcrypt préservés pour que les users n'aient pas à réinitialiser leur mot de passe)- Toutes les données métier (familles, tâches, récompenses, statistiques, etc.)
- Le bucket Storage
task-evidence(photos de preuve) - Les identités OAuth Google liées aux comptes existants
Cette étape sera documentée dans un complément à cette note.
Aide-mémoire — Sous-domaines¶
| Sous-domaine | Usage | Accès |
|---|---|---|
stop-repeat.tutotech.org |
Frontend SPA | Public |
supabase.tutotech.org |
API Supabase (Kong gateway : REST, Auth, Storage, Realtime, Functions) | Public |
studio.supabase.tutotech.org |
Dashboard Studio (admin DB) | VPN uniquement |
coolify.tutotech.org |
UI Coolify | VPN uniquement |
webhook.tutotech.org |
Endpoint webhooks GitHub (uniquement /webhooks/*) |
Public |
Aide-mémoire — Commandes utiles¶
| Commande / Action | Description |
|---|---|
docker compose up -d (depuis /opt/docker/supabase) |
Démarrer la stack Supabase |
docker compose logs -f auth |
Suivre les logs de GoTrue |
docker exec supabase-db psql -U postgres -d postgres |
Console SQL avec super-user |
SELECT * FROM cron.job; |
Lister les jobs planifiés |
SELECT * FROM net._http_response ORDER BY id DESC LIMIT 5; |
Voir les dernières réponses pg_net |
git push origin coolify-deploy |
Déclencher un redéploiement automatique |
curl /api/v1/deploy?uuid=... (API Coolify) |
Forcer un redéploiement manuel |
Ressources¶
- Supabase Self-Hosting Docker — Documentation officielle
- Coolify Documentation — Documentation officielle
- Traefik v3 — Routing — Configuration des routers, services, middlewares
- GoTrue / Auth API — API d'authentification
- pg_cron — Planification de jobs PostgreSQL
- Let's Encrypt DNS Challenge — Émission de certificats wildcard via DNS