Faire tourner EmDash sans Cloudflare : Node, PostgreSQL, S3
EmDash tourne sur n'importe quel hébergement Node.js 22+, avec SQLite ou PostgreSQL. Ce que la configuration demande, et ce qu'on perd en partant.
EmDash est né sur Cloudflare, mais rien ne l’y attache : il tourne sur n’importe quel hébergement Node.js 22 ou supérieur, avec SQLite, libSQL ou PostgreSQL comme base, et un stockage compatible S3 pour les médias. La configuration tient en une quinzaine de lignes. Ce qui demande de l’attention, c’est ailleurs : les droits du rôle PostgreSQL, le cache qui n’est plus partagé, et une fonctionnalité qui reste liée à D1.
Ce dossier est vérifié contre la documentation officielle au 23 août 2026, sur la ligne de version 0.34.x. Les options et les valeurs par défaut évoluent d’une version à l’autre : revérifiez-les sur la vôtre.
Ce que la configuration Node demande vraiment
Trois éléments changent par rapport à un déploiement Cloudflare : l’adaptateur Astro, l’adaptateur de base et l’adaptateur de stockage. Le reste de la configuration est identique.
// astro.config.mjs
import { defineConfig } from "astro/config";
import node from "@astrojs/node";
import emdash, { local } from "emdash/astro";
import { sqlite } from "emdash/db";
export default defineConfig({
output: "server",
adapter: node({ mode: "standalone" }),
integrations: [
emdash({
database: sqlite({ url: "file:./data/emdash.db" }),
storage: local({
directory: "./data/uploads",
baseUrl: "/_emdash/api/media/file",
}),
}),
],
});
Après npm run build, le serveur se lance avec node ./dist/server/entry.mjs et écoute sur le port 4321 par défaut. Deux comportements se déclenchent à la première requête après un déploiement, et il vaut mieux les connaître : les migrations du cœur s’appliquent, et si la base est vide et que l’assistant de configuration n’a jamais été mené à son terme, le fichier de contenu initial s’applique aussi. Sur une base existante, rien n’est écrasé.
Le fichier de contenu initial est lu au moment de la construction et incorporé au paquet : il n’a pas besoin d’être copié dans l’image d’exécution. C’est une différence de raisonnement par rapport à un CMS classique, où le contenu de démarrage est un script qu’on lance après le déploiement.
Choisir sa base : trois options, trois contraintes
| Base | Pour qui | La contrainte à connaître |
|---|---|---|
| SQLite | Un seul serveur, mise en route immédiate | Exige un système de fichiers persistant : inutilisable tel quel sur un hébergement à stockage éphémère |
| libSQL | Base distante sans passer par D1 | Jeton d’authentification à l’exécution, et un second jeton pour les migrations |
| PostgreSQL | Production Node avec une base relationnelle complète | Le rôle doit posséder les objets, pas seulement pouvoir les modifier |
SQLite reste l’option la plus simple, et sur un serveur unique elle est parfaitement sérieuse. Le pilote utilisé installe un binaire préconstruit téléchargé depuis les publications GitHub du projet ; sur un réseau restreint, cette installation retombe sur une compilation depuis les sources et échoue faute d’outillage dans les images Node minimales. L’erreur est reconnaissable — elle se plaint de ne pas trouver Python — et se corrige en installant python3, make et g++ dans l’étape de construction de votre image.
PostgreSQL demande le paquet pg en dépendance, et accepte soit une chaîne de connexion, soit des paramètres séparés. La taille du bassin de connexions est réglable, avec un maximum à 10 par défaut, ce qui est bas pour un site chargé.
Le piège PostgreSQL : la propriété des objets
C’est le point qui coûte le plus cher à découvrir en production, et il n’a rien d’exotique. EmDash crée et modifie ses propres tables pendant toute la vie du site : les migrations du cœur créent et altèrent des tables système, chaque type de contenu crée sa table, et ajouter ou retirer un champ altère la table de sa collection.
Le rôle configuré a donc besoin d’une autorité sur le schéma qui ne s’arrête pas à l’installation. Concrètement :
CONNECTsur la base,USAGEetCREATEsur le schéma actif ;- la propriété de chaque table et de chaque fonction EmDash, directement ou par héritage ;
- les droits de lecture et d’écriture sur ces tables.
Il n’a pas besoin d’être superutilisateur, ni de pouvoir créer des bases, des rôles ou des extensions. Mais PostgreSQL ne propose pas de droit ALTER ni DROP sur une table : ces opérations appartiennent au propriétaire de l’objet. Accorder ALL sur une table à un autre rôle n’en fait pas un propriétaire.
D’où la règle qui évite l’incident : utilisez un seul rôle canonique, non expirant, pour la connexion principale d’EmDash. Changer le mot de passe de ce rôle est sans danger. Changer l’utilisateur dans la chaîne de connexion, en revanche, ne transfère pas les objets existants : le nouveau rôle lira et écrira des lignes sans problème, puis échouera à la migration suivante ou au premier changement de type de contenu, avec un message disant qu’il doit être propriétaire de la table. Vérifiez la connexion avant le déploiement avec un simple SELECT current_database(), current_user, current_schema();.
Stockage et cache : ce qui change hors de Cloudflare
Pour les médias, le stockage local convient au développement et à un serveur unique avec un volume persistant. En production, la documentation recommande un stockage compatible S3, qui fonctionne aussi bien avec MinIO auto-hébergé qu’avec R2 par son interface compatible S3. Les paramètres sont ceux qu’on attend : point d’accès, dépôt, clés, et une URL publique facultative si un réseau de diffusion sert les fichiers.
Le cache objet, lui, change de nature. Sur Cloudflare, EmDash s’appuie sur KV, partagé par tous les isolats : une valeur mise en cache par une requête sert à la suivante, où qu’elle arrive. Sur Node, l’adaptateur est un cache en mémoire du processus, avec un millier d’entrées et une durée de vie d’une heure par défaut.
import emdash, { memoryCache } from "emdash/astro";
emdash({
database: sqlite({ url: "file:./data.db" }),
objectCache: memoryCache(),
});
La conséquence est directe et souvent ignorée : si vous faites tourner plusieurs processus derrière un répartiteur de charge, chacun a son propre cache. Le taux de succès s’effondre par rapport à un cache partagé, et deux requêtes successives peuvent voir des états différents pendant la durée de vie d’une entrée. Sur un serveur unique et un processus unique, le cache mémoire est au contraire très bien adapté : il n’y a aucun service externe à maintenir.
Le cache objet couvre les lectures d’une page typique — requêtes de contenu, réglages du site, menus, termes de taxonomie. Il ne couvre ni les requêtes de l’API d’administration, ni les fichiers médias, ni les réponses HTML complètes.
Ce qu’on perd, et qu’il faut assumer
Trois pertes concrètes, à mettre en face de l’indépendance gagnée.
La restauration à un instant donné de la base. Sur D1, elle est toujours active, sans configuration, sur 30 jours au plan payant. Hors de D1, ce filet n’existe plus : il faut le remplacer par une politique de copies explicite, testée. C’est le sujet du guide de sauvegarde et de restauration d’un site EmDash, et c’est le premier chantier d’une installation auto-hébergée.
Les extensions en bac à sable. Le pont du bac à sable d’extensions parle directement à une liaison D1, indépendamment de l’adaptateur de base configuré. La documentation le dit explicitement pour un déploiement Hyperdrive : les extensions en bac à sable n’y sont pas disponibles. Si votre site en dépend, vérifiez ce point sur votre configuration avant de migrer, car il ne se contourne pas côté application. Les extensions qui tournent dans le processus, elles, ne sont pas concernées — la distinction entre les deux formats est expliquée dans notre guide sur la sécurité des extensions.
Tout ce que la plateforme faisait pour vous. Sur un serveur, le certificat TLS, le proxy inverse, la supervision, la rotation des journaux, la reprise après redémarrage et les mises à jour du système sont à votre charge. Ce n’est pas une difficulté technique, c’est du travail récurrent. À l’inverse, le battement de maintenance planifié — publication programmée, sauvegardes automatiques — est assuré par un ordonnanceur intégré côté Node, là où Cloudflare exige un déclencheur cron déclaré.
Docker, et le déploiement en pratique
L’image type est une construction en deux étapes sur node:22-alpine : on installe, on construit, puis on ne garde que dist, node_modules et le fichier de paquet. Le conteneur expose 4321 et lance le point d’entrée du serveur. Un volume nommé sur /app/data garde la base et les téléversements entre deux déploiements — c’est la ligne qu’on oublie et qui fait perdre le contenu au premier redémarrage.
# compose.yaml
services:
emdash:
build: .
ports: ["4321:4321"]
volumes: ["emdash-data:/app/data"]
restart: unless-stopped
volumes:
emdash-data:
Une variable d’environnement mérite d’être posée dès le premier déploiement : la clé de chiffrement des secrets d’extensions, générée par la commande dédiée d’EmDash. Elle est fournie par vous et n’est jamais stockée en base — seul le texte chiffré l’est. La perdre revient à perdre tous les secrets chiffrés avec elle, donc elle se range dans un gestionnaire de secrets, jamais dans le dépôt.
Le moyen terme : rester sur Workers avec sa propre base
Il existe une voie intermédiaire, pour qui veut garder le réseau de Cloudflare sans confier ses données à D1 : l’adaptateur Hyperdrive fait tourner EmDash sur Workers au-dessus d’un PostgreSQL existant, en accélérant et en mutualisant la connexion.
Un point de configuration y est obligatoire et contre-intuitif : le cache de requêtes de Hyperdrive est activé par défaut et doit être désactivé sur la liaison principale. EmDash a son propre cache et s’appuie sur une cohérence lecture-après-écriture — l’assistant de configuration écrit une ligne puis la relit immédiatement. Avec le cache actif, la configuration initiale se corrompt de façon spectaculaire, et les rédacteurs voient du contenu périmé. Cette voie garde par ailleurs les contraintes de propriété PostgreSQL décrites plus haut, et perd les extensions en bac à sable.
Pour le tableau complet des services que le déploiement Cloudflare met en jeu, notre dossier sur l’écosystème Cloudflare d’EmDash détaille chaque brique ; la procédure de déploiement, elle, est dans le tutoriel de déploiement sur Cloudflare Workers.
Questions fréquentes
EmDash fonctionne-t-il vraiment hors de Cloudflare ?
Oui, sur tout hébergement Node.js 22 ou supérieur, avec l’adaptateur Node d’Astro en mode autonome. La base peut être SQLite, libSQL ou PostgreSQL, et les médias un stockage compatible S3 ou le système de fichiers local. La documentation officielle traite ce déploiement comme une cible de premier rang, pas comme un contournement.
PostgreSQL ou SQLite pour un site auto-hébergé ?
SQLite suffit sur un serveur unique avec un disque persistant, et supprime un service à maintenir. PostgreSQL s’impose si vous voulez plusieurs processus applicatifs, une base gérée par un fournisseur, ou des sauvegardes gérées par l’infrastructure. Le prix à payer est la rigueur sur la propriété des objets par un rôle unique et non expirant.
Qu’est-ce qu’on perd exactement en quittant Cloudflare ?
Trois choses : la restauration à un instant donné de la base offerte par D1, les extensions exécutées en bac à sable, dont le pont dépend d’une liaison D1, et le cache partagé entre isolats — sur Node, le cache est propre à chaque processus. Le reste des fonctions du CMS est identique.
Le cache mémoire suffit-il en production ?
Sur un seul processus, oui : il évite la plupart des lectures répétées en base et ne demande aucun service externe. Dès qu’il y a plusieurs processus derrière un répartiteur, chacun tient son propre cache, le taux de succès chute et deux requêtes voisines peuvent voir des états différents. Dans ce cas, réduisez la durée de vie des entrées ou revoyez l’architecture.