YYayaw
Documentation

Auto-hébergement

Runtime Docker portable pour exécuter Yayaw sans Vercel ni stockage Supabase.

Périmètre

La première cible auto-hébergée supportée est un déploiement Docker unique:

  • serveur Next.js standalone
  • Postgres
  • MinIO ou autre stockage objet compatible S3
  • worker base de données Page AI
  • Caddy ou autre proxy inverse qui préserve les en-têtes d'hôte d'origine

Ce chemin prouve la portabilité. Il ne remplace pas Stripe, OpenAI, Resend, l'accès au code GitHub, ni toutes les fonctions propres aux plateformes de déploiement. L'analytics peut utiliser Umami auto-hébergé ou un fournisseur externe comme PostHog.

Fichiers

  • Dockerfile
  • docker-compose.self-host.yml
  • .env.self-host.example
  • deploy/self-host/Caddyfile

next.config.ts utilise output: "standalone" afin que l'image de production puisse lancer le serveur Next.js tracé avec node server.js.

Démarrage Rapide

cp .env.self-host.example .env.self-host
perl -0777 -i -pe "s/^BETTER_AUTH_SECRET=$/BETTER_AUTH_SECRET=$(openssl rand -hex 32)/m" .env.self-host
database_secret=$(openssl rand -hex 32)
perl -0777 -i -pe "s/^POSTGRES_PASSWORD=$/POSTGRES_PASSWORD=$database_secret/m; s#^DATABASE_URL=$#DATABASE_URL=postgresql://yayaw:$database_secret@postgres:5432/yayaw#m" .env.self-host
storage_secret=$(openssl rand -hex 32)
perl -0777 -i -pe "s/^MINIO_ROOT_PASSWORD=$/MINIO_ROOT_PASSWORD=$storage_secret/m; s/^S3_SECRET_ACCESS_KEY=$/S3_SECRET_ACCESS_KEY=$storage_secret/m" .env.self-host
cms_preview_secret=$(openssl rand -hex 32)
cms_rate_limit_secret=$(openssl rand -hex 32)
cms_form_context_secret=$(openssl rand -hex 32)
storage_transfer_secret=$(openssl rand -hex 32)
perl -0777 -i -pe "s/^CMS_PREVIEW_SIGNING_SECRET=$/CMS_PREVIEW_SIGNING_SECRET=$cms_preview_secret/m; s/^CMS_PERSONALIZATION_RATE_LIMIT_SECRET=$/CMS_PERSONALIZATION_RATE_LIMIT_SECRET=$cms_rate_limit_secret/m; s/^CMS_PERSONALIZATION_FORM_CONTEXT_SECRET=$/CMS_PERSONALIZATION_FORM_CONTEXT_SECRET=$cms_form_context_secret/m; s/^STORAGE_TRANSFER_SIGNING_SECRET=$/STORAGE_TRANSFER_SIGNING_SECRET=$storage_transfer_secret/m" .env.self-host
docker compose --env-file .env.self-host -f docker-compose.self-host.yml --profile setup run --rm migrate
docker compose --env-file .env.self-host -f docker-compose.self-host.yml up --build

Définissez BETTER_AUTH_SECRET, POSTGRES_PASSWORD, DATABASE_URL, MINIO_ROOT_PASSWORD, S3_SECRET_ACCESS_KEY, CMS_PREVIEW_SIGNING_SECRET, CMS_PERSONALIZATION_RATE_LIMIT_SECRET, CMS_PERSONALIZATION_FORM_CONTEXT_SECRET et STORAGE_TRANSFER_SIGNING_SECRET avant le premier build. Utilisez un nouveau secret de base dans les deux valeurs de base, un identifiant de stockage distinct pour les deux valeurs de stockage, et quatre secrets CMS de signature/limitation distincts. Ils restent tous côté serveur. L'exemple ne fournit volontairement aucun identifiant partagé par défaut. Le Dockerfile refuse de construire avec un secret Better Auth vide parce que les routes Better Auth sont évaluées pendant next build.

Le profil setup lance drizzle-kit push --force pour synchroniser le schéma Drizzle courant dans la base auto-hébergée, puis exécute le seed idempotent pour les rôles, indicateurs de fonctionnalité, catalogue de facturation, thèmes et contenu CMS de départ. Il lance aussi bun run dynamic-data:repair-deployed-storage dans la même image worker pour vérifier les tables natives dynamic-data déployées et rejouer le plan de déploiement idempotent pour toute table générée manquante ou illisible.

Ouvrez http://localhost:8080. L'exemple lie les ports de l'API S3 et de la console MinIO à l'interface loopback uniquement sur http://localhost:9000 et http://localhost:9001. Ne publiez jamais directement ces ports sur un hôte distant ; utilisez un tunnel SSH pour un accès opérateur temporaire. Les URL publiques de ressources sont servies via Caddy sur l'origine de l'app par défaut.

Environnement Runtime

Valeurs auto-hébergées importantes:

NEXT_PUBLIC_BASE_URL=http://localhost:8080
BETTER_AUTH_SECRET=<generate-a-long-random-secret>
POSTGRES_PASSWORD=<generated-database-secret>
DATABASE_URL=postgresql://yayaw:<generated-database-secret>@postgres:5432/yayaw
STORAGE_PROVIDER=s3
STORAGE_MEDIA_BUCKET=media
STORAGE_MEDIA_STAGING_BUCKET=media-staging
STORAGE_CMS_FORM_BUCKET=cms-form-submissions
STORAGE_PUBLIC_BASE_URL=http://localhost:8080
CMS_MEDIA_STAGED_UPLOADS_ENABLED=false
STORAGE_TRANSFER_SIGNING_SECRET=<generated-storage-transfer-secret>
CMS_PERSONALIZATION_RATE_LIMIT_SECRET=<generated-rate-limit-secret>
CMS_PERSONALIZATION_FORM_CONTEXT_SECRET=<generated-form-context-secret>
CMS_PERSONALIZATION_CLIENT_IP_HEADER=x-real-ip
S3_ENDPOINT=http://minio:9000
S3_SIGNED_PUBLIC_ENDPOINT=
S3_REGION=us-east-1
S3_ACCESS_KEY_ID=yayaw
MINIO_ROOT_PASSWORD=<same-generated-storage-secret>
S3_SECRET_ACCESS_KEY=<same-generated-storage-secret>
S3_FORCE_PATH_STYLE=true
CMS_PREVIEW_SIGNING_SECRET=<generated-preview-secret>
CMS_LEGACY_PROTOTYPE_WRITES_ENABLED=true
CMS_PROTOTYPE_RUNTIME_V2_WRITES_ENABLED=false
PUBLIC_DOMAIN_PROVIDER=manual-dns
PAGE_AI_QUEUE_DRIVER=db-worker
DEPLOYMENT_PROVIDER=static
DEPLOYMENT_URL=http://localhost:8080
NEXT_PUBLIC_ANALYTICS_PROVIDER=none
NEXT_PUBLIC_ANALYTICS_CAPTURE_MODE=hybrid
NEXT_PUBLIC_UMAMI_HOST_URL=
NEXT_PUBLIC_UMAMI_WEBSITE_ID=
UMAMI_API_URL=
UMAMI_WEBSITE_ID=
UMAMI_API_TOKEN=
UMAMI_API_KEY=
UMAMI_USERNAME=
UMAMI_PASSWORD=
RESEND_API_KEY=
EMAIL_SENDER=team@yayaw.app
EMAIL_SUPPORT=support@yayaw.app
EMAIL_USERNAME=Yayaw Team

Les valeurs EMAIL_* permettent aux e-mails transactionnels de fonctionner avant la connexion d'un admin. Après setup, Admin > Site Settings > Email peut les surcharger au runtime.

Les valeurs NEXT_PUBLIC_* sont intégrées au bundle navigateur au moment du build. Les commandes Compose ci-dessus passent .env.self-host comme fichier d'environnement de build pour que ces valeurs atteignent les arguments Docker. Reconstruisez une image lorsqu'elles changent. BETTER_AUTH_SECRET est aussi fourni au build Docker afin que Next.js puisse évaluer les routes Better Auth pendant next build; gardez-le aussi présent au runtime.

Les autres valeurs uniquement serveur peuvent changer dans le magasin de secrets de l'orchestrateur ou .env.self-host, puis par redémarrage des conteneurs concernés.

Les builds Docker fixent NEXT_BUILD_WORKERS à 1 par défaut pour garder la génération statique Next.js stable sur de petites machines. Augmentez cette valeur comme variable ou argument de build uniquement après vérification de la marge CPU et mémoire.

Les pages de documentation sont rendues dynamiquement dans les builds auto-hébergés au lieu d'être prégénérées pendant la construction d'image. Cela évite aux petits hôtes Coolify ou Docker de consacrer l'essentiel du budget de déploiement aux pages docs, tout en les servant normalement au runtime.

YAYAW_APP_IMAGE et YAYAW_WORKER_IMAGE contrôlent les tags d'image Compose utilisés par l'app et les cibles worker Bun. Le service migrate utilise volontairement le même tag d'image worker que le worker long-lived, afin que Docker et Coolify réutilisent cette cible de build entre services.

docker-compose.coolify.yml est optimisé pour le chemin Coolify managé par Yayaw et attend des images app et worker déjà présentes sur le serveur Coolify. GitHub Actions construit ces images pour linux/arm64, les charge sur la VM Mac mini, définit YAYAW_APP_IMAGE et YAYAW_WORKER_IMAGE via l'API Coolify, puis déclenche le déploiement Compose. Utilisez docker-compose.self-host.yml pour les installations locales ou clients portables qui doivent construire les images directement depuis le code source.

Dans Coolify, ces variables de tags d'image doivent être disponibles au build time comme au runtime, car Compose résout l'interpolation image: avant de démarrer les services.

Proxy Inverse

Le proxy doit préserver:

  • Host
  • X-Forwarded-Host
  • X-Forwarded-Proto
  • en-têtes de transmission de l'IP client

Les pages publiques d'organisation routent par hostname; perdre le Host d'origine casse la résolution des domaines personnalisés. Gardez un buffering compatible avec les réponses streaming et l'optimisation d'images Next.js.

Lorsque STORAGE_PUBLIC_BASE_URL pointe vers l'origine app ou CDN, routez tous les préfixes de buckets publics vers MinIO avant le repli vers l'app. Le fichier Caddy fourni gère les buckets publics intégrés:

handle /media/* {
	reverse_proxy minio:9000
}

handle /organization-logos/* {
	reverse_proxy minio:9000
}

Si STORAGE_PUBLIC_BASE_URL pointe directement vers une origine dédiée de stockage objet ou CDN, configurez cette origine pour servir les mêmes chemins /<bucket>/<key>.

Stockage

La pile Compose démarre MinIO et crée les buckets publics media et organization-logos, ainsi que les buckets privés media-staging et cms-form-submissions. Les médias téléversés et les miniatures utilisent des opérations compatibles S3 ; les pages publiées utilisent les URL publiques stockées. Les uploads en staging et les photos client restent privés. Le Caddy fourni écrase x-real-ip, utilisez donc cette valeur pour CMS_PERSONALIZATION_CLIENT_IP_HEADER.

L'exemple démarre dans l'état sûr compatible reader : les écritures prototype legacy restent disponibles, tandis que les uploads en staging et les écritures du runtime exact sont désactivés. N'activez pas les écritures exactes pendant le déploiement initial de l'image. Une fois la pile saine, vérifiez les buckets privés depuis le worker actif :

docker compose --env-file .env.self-host -f docker-compose.self-host.yml exec \
  -e CMS_STORAGE_PREFLIGHT_ALLOW_BUCKET_CREATE=false \
  worker bun run storage:cms-preflight

Promouvez les mêmes images déjà construites avec deux redémarrages explicites :

  1. Gelez les writers avec CMS_LEGACY_PROTOTYPE_WRITES_ENABLED=false, CMS_MEDIA_STAGED_UPLOADS_ENABLED=true et CMS_PROTOTYPE_RUNTIME_V2_WRITES_ENABLED=false.
  2. Quand tous les conteneurs app et worker sont sains dans cet état, activez les écritures exactes avec uniquement CMS_PROTOTYPE_RUNTIME_V2_WRITES_ENABLED=true.

Après chaque modification, exécutez docker compose --env-file .env.self-host -f docker-compose.self-host.yml up -d --no-build --force-recreate app worker. Ne définissez jamais les deux flags de writer à true et ne réactivez jamais les écritures legacy après l'activation du runtime exact.

Les URL média Supabase existantes ne sont pas migrées par la pile auto-hébergée. Gardez l'ancien bucket joignable ou prévoyez une migration média séparée.

Domaines Publics

Les domaines auto-hébergés utilisent une vérification DNS manuelle:

  1. Ajoutez le hostname depuis les réglages d'organisation ou MCP.
  2. Publiez le challenge TXT affiché par le dashboard.
  3. Pointez le domaine vers l'ingress avec les enregistrements CNAME ou A configurés.
  4. Relancez le check ou la vérification du domaine.
  5. Configurez TLS au niveau du proxy.

Utilisez PUBLIC_DOMAIN_CNAME_TARGET et PUBLIC_DOMAIN_IPV4_TARGETS pour afficher les indications de routage à l'opérateur. Utilisez APP_MANAGED_HOSTS et RESERVED_PUBLIC_DOMAIN_SUFFIXES pour empêcher les clients de réclamer des hôtes possédés par l'app ou réservés.

Worker

Lancez un ou plusieurs workers Page AI avec:

bun run worker:page-ai

Le worker interroge Postgres pour les runs Page AI durables. Vercel Queues reste disponible sur Vercel, mais le défaut de production auto-hébergé est db-worker.

Exploitation

  • Lancez le profil setup avant le premier démarrage et après les changements de schéma afin de synchroniser la base et réparer les seeds.
  • Persistez les volumes Postgres, MinIO, Caddy et .next/cache.
  • Sauvegardez Postgres et le stockage objet ensemble pour garder les lignes média et les objets synchronisés.
  • Gardez une seule NEXT_SERVER_ACTIONS_ENCRYPTION_KEY base64 entre les builds susceptibles de se superposer. Quand DEPLOYMENT_GIT_COMMIT_SHA est fourni, le build Docker l'utilise comme identifiant de déploiement Next.js et SHA immuable de readiness; sinon la baseline Compose portable utilise le fallback stable self-host.
  • Choisissez un fournisseur d'analytics avec NEXT_PUBLIC_ANALYTICS_PROVIDER. Valeurs supportées: posthog, umami, none; Umami requiert NEXT_PUBLIC_UMAMI_* pour le suivi et les variables API UMAMI_* pour les données dashboard.
  • Choisissez un mode de capture analytics avec NEXT_PUBLIC_ANALYTICS_CAPTURE_MODE. Utilisez server pour éviter tout script analytics navigateur; utilisez hybrid pour combiner analytics produit navigateur et conversions facturation côté serveur.
  • Gardez les risques fournisseurs Stripe, OpenAI, Resend et GitHub documentés tant que leurs contrats fournisseurs n'existent pas.

Vérifications Production

Après un déploiement production, vérifiez la configuration depuis le conteneur app en cours d'exécution plutôt que seulement depuis l'UI de l'orchestrateur. Signaux auto-hébergés importants:

  • STORAGE_PROVIDER=s3
  • STORAGE_PUBLIC_BASE_URL pointe vers l'origine app, CDN ou stockage objet qui sert les chemins /<bucket>/<key>
  • S3_ENDPOINT est joignable depuis les conteneurs app et worker
  • NEXT_PUBLIC_ANALYTICS_PROVIDER=umami lorsque vous utilisez Umami auto-hébergé
  • NEXT_PUBLIC_ANALYTICS_CAPTURE_MODE=server lorsqu'aucun script analytics navigateur ne doit être rendu

Pour les médias, téléversez un petit objet avec les mêmes identifiants S3 que l'app, puis récupérez son URL publique. L'URL publique doit utiliser STORAGE_PUBLIC_BASE_URL, par exemple /media/<key> quand Caddy route le préfixe de bucket vers MinIO. Si ce test stockage réussit mais que le téléversement dashboard échoue, inspectez l'organisation active, les permissions média, le quota de plan, le type de fichier et la taille avant de changer de fournisseur de stockage.

Pour une configuration sans analytics navigateur, récupérez une page publique et confirmez que le HTML ne contient pas l'URL du script Umami ni data-website-id. Les analytics dashboard ont encore besoin d'identifiants API Umami via UMAMI_API_TOKEN, UMAMI_API_KEY ou UMAMI_USERNAME/UMAMI_PASSWORD; la capture événementielle côté serveur utilise /api/send et ne requiert pas le script navigateur.

Validation

docker compose --env-file .env.self-host -f docker-compose.self-host.yml config
bun test src/config/storage.config.test.ts src/lib/storage.test.ts
bun test src/lib/server/services/organization-domains/organization-public-domains.test.ts
bun test src/lib/server/services/pages/page-ai-runner.test.ts