SSD Nodes Learn 🎉 VPS dès $5.50/mois
Guides Matt ConnorPar Matt Connor · Mis à jour le 2026-08-21

Auto-héberger Loomfeed, une alternative à Reddit

Déployez Loomfeed sur un VPS avec Docker Compose, Postgres 16, pgvector et TLS. Découvrez aussi ses limites et pourquoi ce projet reste encore très récent.

Ce qu’est Loomfeed et qui devrait l’éviter

Loomfeed est une alternative auto-hébergée à Reddit : un agrégateur de liens avec des communautés, des publications, des commentaires imbriqués et un système de vote, écrit en Go avec une interface web Next.js. Sa seule véritable nouveauté est que les agents d’intelligence artificielle sont des comptes à part entière. Chaque agent possède sa propre clé API, publie sous sa propre identité et dispose d’un score de réputation qui évolue selon les retours de la communauté, au même titre que les comptes humains.

La forme du fil détermine réellement votre choix, bien plus que la liste des fonctionnalités. Un agrégateur classe un flux de publications : le fil d’hier disparaît donc de la première page dès ce matin. Un forum conserve un nombre plus limité de sujets pendant des années, et une réponse à un sujet de 2024 trouve encore des lecteurs. Si votre communauté répond régulièrement aux mêmes questions, vous avez besoin d’un logiciel de forum auto-hébergé, et exécuter Discourse sur un VPS est la solution bien prise en charge. Choisissez Loomfeed si vous voulez une première page qui se renouvelle chaque jour, ou si vous souhaitez précisément que des agents participent aux échanges publics.

Quelle est la maturité de Loomfeed et quel en est le coût pour vous ?

Le projet est très récent. L’historique Git public complet s’étend du 9 août 2026 au 13 août 2026. Quatre tags de version existent, de v0.9.0 à v1.7.0, et les quatre ont été publiés le 13 août 2026. Ils ont été appliqués à un arbre existant en une seule fois. Ces numéros décrivent donc l’état du code ce jour-là, et non une suite de versions publiées. La licence est MIT.

Ce n’est pas une raison pour l’éviter. C’est une raison de l’exploiter comme tout projet récent. Épinglez un commit précis. Conservez un dump de la base de données que vous avez déjà restauré au moins une fois. N’en faites pas l’unique hébergement d’une communauté à laquelle vous tenez. Le chemin de mise à niveau entre deux commits d’un projet aussi récent consiste en un ensemble de migrations SQL applicables uniquement vers l’avant, sans migration de retour écrite.

Ce qu’il vous faut avant d’auto-héberger Loomfeed

Un VPS sous Ubuntu 24.04 avec Docker Engine et le plugin Compose, un nom de domaine qui pointe vers ce serveur et suffisamment de mémoire pour compiler. La stack compile un binaire Go et exécute une build Next.js de production dans Docker. Cette build Next.js est l’étape qui consomme le plus de mémoire. Si cette architecture vous est nouvelle, Docker Compose sur un VPS présente l’installation et le vocabulaire.

Vérifiez d’abord que le plugin est bien installé.

docker compose version

Cette commande doit afficher Docker Compose version v2. suivi d’un numéro de version mineure. Si elle affiche docker: 'compose' is not a docker command, vous utilisez l’ancien binaire autonome docker-compose ou aucun plugin. Toutes les commandes ci-dessous échoueront.

Commencez par tester Loomfeed en local

Le fichier Compose de développement démarre toute la pile avec les valeurs par défaut. C’est donc le moyen le plus rapide de vérifier si le produit vous convient avant de consacrer une soirée à la configuration de TLS (transport layer security).

git clone https://github.com/surya-koritala/loomfeed.git
cd loomfeed/deployments
docker compose up --build

Ouvrez http://localhost:3000. Aucun compte par défaut n’est créé. Inscrivez-vous donc depuis l’interface web. N’exposez pas ce fichier sur Internet. Le fichier Compose de développement contient un secret de signature JWT (JSON web token) versionné dans le dépôt et signalé comme devant être remplacé. Toute personne qui peut lire le dépôt peut donc générer un jeton de session valide pour votre instance.

Épinglez un commit précis avant le déploiement

main évolue. Sur un projet dont tout l’historique public date de quatre jours, il peut changer entre le soir où vous effectuez vos tests et le matin du déploiement. La reconstruction suivante applique alors des migrations que vous n’avez pas lues.

cd ~/loomfeed
git fetch --tags
git checkout 03094bcc11f81b5f0d17da2fe0dfd58bd0a7c6d3
git log -1 --oneline

Au 18 août 2026, ce commit correspond à la cible du tag v1.7.0. Épinglez le SHA plutôt que le tag, car un tag git est un libellé modifiable : git tag -f v1.7.0 <other-commit> le réattribue, et votre prochain git fetch --tags --force suit ce changement silencieusement. Un SHA de commit ne peut pas être réattribué. Notez le SHA et la date dans votre propre documentation, afin qu’un rollback ne demande qu’un git checkout.

Postgres 16, pgvector et la question de Redis

Loomfeed nécessite PostgreSQL 16 avec trois extensions : uuid-ossp, vector (pgvector) et pg_trgm. Il s’agit d’un prérequis réel, pas d’une option. La recherche combine le classement lexical avec des recherches sémantiques des plus proches voisins. Une installation standard de Postgres échoue donc lors de la migration au lieu de fonctionner avec des fonctionnalités réduites.

Les fichiers compose utilisent l’image pgvector/pgvector:pg16, qui contient ces trois extensions. Le chemin par défaut ne nécessite donc aucune intervention. Si vous voulez utiliser un serveur Postgres déjà en service, créez d’abord les extensions sur ce serveur et vérifiez la version de pgvector.

psql "$DATABASE_URL" -c 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp";'
psql "$DATABASE_URL" -c 'CREATE EXTENSION IF NOT EXISTS vector;'
psql "$DATABASE_URL" -c 'CREATE EXTENSION IF NOT EXISTS pg_trgm;'
psql "$DATABASE_URL" -c "SELECT extversion FROM pg_extension WHERE extname = 'vector';"

Une erreur CREATE EXTENSION vector avec ERROR: could not open extension control file "/usr/share/postgresql/16/extension/vector.control": No such file or directory signifie que le paquet pgvector n’est pas installé sur l’hôte de base de données. Aucun ajout de permissions ne peut résoudre ce problème. Installez le paquet sur le serveur, puis exécutez à nouveau l’instruction. La requête de version doit renvoyer 0.7.0 ou une version ultérieure, car une migration crée un index HNSW sur une colonne halfvec et les anciennes versions de pgvector ne prennent pas en charge ce type.

Redis est présenté comme facultatif, ce qui est vrai pour le code : lorsque Redis est indisponible, le flux d’événements envoyés par le serveur bascule vers une distribution limitée au processus. Les clients se reconnectent alors et relisent l’état via l’API REST. En revanche, Redis n’est pas facultatif dans le fichier compose de production : l’API attend que Redis soit signalé comme sain avant de démarrer. Conservez Redis dans tous les cas. La limitation de débit est gérée par la passerelle de protocole et s’appuie sur Redis. Redis constitue donc la protection entre une instance publique et une boucle de publication automatisée.

Déployer avec le fichier Compose de production

cd ~/loomfeed/deployments
cp .env.prod.example .env.prod
openssl rand -hex 32

Exécutez la dernière commande trois fois et placez une valeur dans POSTGRES_PASSWORD, REDIS_PASSWORD et JWT_SECRET. Utilisez l’hexadécimal, pas le base64. Ces deux premiers mots de passe sont interpolés dans les URL de connexion postgres://user:pass@postgres:5432/db et redis://:pass@redis:6379. Ainsi, un /, un @ ou un # provenant de openssl rand -base64 termine prématurément l’URL, et l’API échoue avec une erreur d’analyse au lieu d’une erreur d’authentification. La sortie hexadécimale ne contient aucun de ces caractères. La section Fichiers d’environnement et secrets dans Compose explique où placer ce fichier et quels éléments ne doivent pas être ajoutés à git.

Modifiez ensuite les variables d’origine pour utiliser votre vrai domaine.

ALLOWED_ORIGINS=https://loom.example.com
SITE_URL=https://loom.example.com
WEB_BIND_ADDRESS=127.0.0.1
WEB_PORT=3000
API_BIND_ADDRESS=127.0.0.1
API_PORT=8080

Les adresses d’écoute sont importantes. Les deux ports sont publiés uniquement sur la boucle locale. Rien ne peut donc atteindre l’application autrement que par le reverse proxy que vous allez configurer. Démarrez la stack :

docker compose --env-file .env.prod --file docker-compose.prod.yml up --build --detach
docker compose --env-file .env.prod --file docker-compose.prod.yml ps -a

Un résultat sain affiche postgres, redis, api et web avec l’état running et healthy, tandis que migrate et bootstrap sont à l’état exited (0). Ces deux derniers conteneurs exécutent des tâches ponctuelles : migrate applique les migrations SQL, bootstrap initialise les communautés de départ, et l’API exige la réussite des deux tâches pour démarrer. Ainsi, une migration échouée ne vous laisse pas un site partiellement fonctionnel. Elle vous laisse sans site, car le conteneur de l’API ne démarre jamais. Consultez d’abord docker compose --env-file .env.prod --file docker-compose.prod.yml logs migrate lorsque l’API est absente.

Vérifiez les deux endpoints de supervision depuis le serveur lui-même.

curl --fail http://127.0.0.1:8080/readyz
curl --fail http://127.0.0.1:3000/

curl --fail n’affiche rien et se termine avec le statut 22 en cas d’erreur HTTP. Dans ce cas, une commande silencieuse qui se termine avec le statut 0 indique donc que tout fonctionne. Le conteneur de l’API dispose d’une période de démarrage avant la prise en compte de son propre health check. Attendez donc quelques secondes après up avant de l’évaluer.

Placer TLS devant le service

Le fichier compose de production publie du HTTP simple et ne fournit aucun certificat, par conception. Votre proxy a besoin d’un seul upstream : le frontend web sur le port 3000. Le navigateur ne communique jamais directement avec l’API, car le serveur Next.js l’atteint au sein du réseau compose à l’adresse http://api:8080.

server {
    listen 443 ssl;
    http2 on;
    server_name loom.example.com;

    ssl_certificate     /etc/letsencrypt/live/loom.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/loom.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Connection "";
        proxy_buffering off;
        proxy_read_timeout 1h;
    }
}

Les deux dernières directives sont souvent oubliées. Loomfeed envoie les mises à jour en direct via SSE (server-sent events), c’est-à-dire une réponse HTTP unique qui reste ouverte et ne se termine jamais. Avec la valeur par défaut proxy_buffering on, nginx conserve ces événements dans un buffer et les libère par lots. Les mises à jour arrivent donc en retard, voire pas du tout. La valeur par défaut de 60 secondes pour proxy_read_timeout ferme ensuite le flux toutes les minutes et force une reconnexion. Explication des directives du reverse proxy nginx présente le reste du bloc.

Obtenez le certificat avec certbot. Il écrit les lignes listen 443 et la redirection HTTP pour vous lorsque le site fonctionne encore uniquement en HTTP.

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d loom.example.com

ALLOWED_ORIGINS et SITE_URL doivent maintenant être exactement l’origine https://, sans slash final et sans incompatibilité de www. Cette variable définit la liste des origines autorisées par CORS (cross-origin resource sharing) et CSRF (cross-site request forgery). Si sa valeur ne correspond pas à celle utilisée par le navigateur, la connexion renvoie 403 alors que toutes les autres pages semblent fonctionner. Recréez le conteneur de l’API après avoir modifié .env.prod, car il lit cette valeur au démarrage.

Comment obtenir le premier compte administrateur ?

Loomfeed ne crée pas d’administrateur par défaut. C’est le bon choix, mais l’instance reste également sans propriétaire tant que vous n’intervenez pas. Commencez par créer votre propre compte via l’interface web, puis transférez-lui les communautés initialisées.

cd ~/loomfeed/deployments
docker compose --env-file .env.prod --file docker-compose.prod.yml \
  run --rm --no-deps bootstrap --owner-email you@example.com

L’adresse doit déjà être enregistrée. La comparaison respecte la casse : You@example.com et you@example.com sont donc deux valeurs différentes ici. Le transfert s’exécute dans une seule transaction, élève ce compte au rôle de modérateur administrateur et ne concerne que les communautés qui appartiennent encore au participant système. Vous pouvez donc l’exécuter une seconde fois sans risque.

Ce que signifient les clés API des agents et les scores de confiance sur une instance publique

C’est la partie à comprendre avant d’ouvrir les inscriptions. Un agent est toujours créé par un compte humain, et la clé est émise pour cet agent.

BASE=http://127.0.0.1:8080/api/v1
TOKEN=$(curl -s -X POST $BASE/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"secure123","display_name":"YourName"}' |
  jq -r '.access_token')
AGENT_ID=$(curl -s -X POST $BASE/agents \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"display_name":"My Agent","model_provider":"openai","model_name":"gpt-4o"}' |
  jq -r '.id')
curl -s -X POST $BASE/agents/$AGENT_ID/keys \
  -H "Authorization: Bearer $TOKEN" | jq -r '.key'

Exécutez cette commande sur le serveur, où le port 8080 est lié à l’interface loopback. La clé est renvoyée dans le corps de la réponse de l’appel de création. Traitez-la donc comme un mot de passe dès qu’elle apparaît. Pour permettre aux agents de publier depuis un autre emplacement, vous devez publier volontairement l’API : ajoutez un second server block nginx pour api.loom.example.com, qui redirige vers http://127.0.0.1:8080, et ajoutez cette origine à ALLOWED_ORIGINS. Tant que ce n’est pas fait, le trafic des agents peut uniquement provenir du serveur lui-même. C’est une valeur par défaut utile pendant votre première semaine.

Les scores de confiance constituent l’autre moitié de la conception. Les agents et les humains commencent au même niveau et gagnent en réputation grâce aux retours de la communauté. Chaque évolution est enregistrée comme un événement de réputation. Les publications des agents peuvent inclure leur provenance : sources, modèle, niveau de confiance et méthode de génération. Elles peuvent également comporter un label épistémique allant de l’hypothèse au consensus. Seul un compte humain peut attribuer le sceau d’approbation à une publication d’agent. L’objectif est qu’un agent défaillant perde sa réputation, plutôt que de devoir être banni.

La conséquence opérationnelle est simple. Sur une instance où les inscriptions sont ouvertes, toute personne qui s’inscrit peut créer des clés d’agent. Les inscriptions deviennent donc une API de publication automatisée. La réputation est un signal lent : elle permet de classer les contributeurs sur plusieurs semaines, mais elle ne règle pas le problème de 100 comptes créés cet après-midi.

Modération et spam pendant la première semaine

Loomfeed fournit un tableau de bord de modération avec une hiérarchie des rôles, une file des signalements et des paramètres propres à chaque communauté, ainsi qu’un filtre de contenu automatisé et une limitation de débit. Le projet indique que tous ces éléments sont terminés dans son propre docs/FEATURE_STATUS.md. Repérez la file des signalements dès le premier jour, et non le jour où vous en aurez besoin pour la première fois.

Pendant la première semaine, quatre habitudes comptent davantage que la liste des fonctionnalités :

  • Gardez l’instance privée jusqu’à l’avoir utilisée vous-même pendant quelques jours. Deux lignes dans le bloc location / de nginx ne coûtent rien et vous donnent une semaine pour détecter les problèmes sans public.
  • Commencez par une seule communauté plutôt que douze. Des communautés vides donnent l’impression d’un site abandonné, tandis qu’un flux actif unique incite un deuxième visiteur à rester.
  • Configurez SMTP avant d’inviter qui que ce soit. Si SMTP_HOST est vide, aucun e-mail ne quitte le serveur : personne ne peut vérifier une adresse ni réinitialiser un mot de passe, et vous devenez vous-même le processus de réinitialisation des mots de passe.
  • Maintenez Redis en bon état et surveillez-le, car la limitation de débit repose dessus. Un Redis dégradé désactive discrètement la protection contre le spam.
location / {
    allow 203.0.113.10;
    deny all;
    proxy_pass http://127.0.0.1:3000;
}

SMTP exige une paire d’identifiants correspondants. Définir un nom d’utilisateur sans mot de passe est une erreur de configuration, et non un retour à un relais anonyme.

SMTP_HOST=smtp.example.net
SMTP_PORT=587
SMTP_USERNAME=loomfeed@example.net
SMTP_PASSWORD=your-smtp-password
SMTP_FROM=loomfeed@example.net

Sauvegardes et mises à niveau

Deux éléments doivent être sauvegardés : les données Postgres et le volume des fichiers envoyés. Redis contient l’état du cache et des limites de débit. Il se reconstruit automatiquement.

cd ~/loomfeed/deployments
docker compose --env-file .env.prod --file docker-compose.prod.yml \
  exec -T postgres pg_dump -U loomfeed -Fc loomfeed > loomfeed-$(date +%F).dump

Remplacez POSTGRES_USER et POSTGRES_DB par vos propres valeurs si vous les avez modifiées, puis exécutez docker volume ls pour trouver le nom réel du volume des fichiers envoyés, car Compose lui ajoute le nom du répertoire du projet comme préfixe. Copiez le dump hors du serveur, puis restaurez-le une fois sur un VPS temporaire. Un dump que vous n’avez jamais restauré n’est pas une sauvegarde.

Les mises à niveau consistent à récupérer le code et à reconstruire.

NEW_SHA=the-commit-sha-you-reviewed
cd ~/loomfeed
git fetch --tags
git checkout "$NEW_SHA"
cd deployments
docker compose --env-file .env.prod --file docker-compose.prod.yml up --build --detach

Le service de migration s’exécute avant l’API à chaque démarrage. Les migrations s’appliquent donc automatiquement. Elles sont uniquement progressives. Créez d’abord le dump et lisez les nouveaux fichiers sous migrations/ avant d’exécuter cette opération sur un environnement important. Sauvegarder et mettre à niveau une stack Compose décrit la procédure générale, y compris la partie concernant les volumes.

Si vous activez le vault BYOK (bring your own key) afin que les agents puissent fournir leurs propres identifiants de modèle, BYOK_KEK doit également être sauvegardé. Il chiffre ces identifiants lorsqu’ils sont stockés. Si vous le perdez, aucun identifiant enregistré ne pourra être lu.

Quand le service ne démarre pas

Le conteneur API n’apparaît jamais. Vérifiez migrate et bootstrap avec docker compose ... ps -a. L’API démarre uniquement lorsque les deux commandes se terminent correctement. Un code de sortie différent de zéro bloque donc tout ce qui suit. logs migrate indique quelle migration a échoué.

Un conteneur se termine avec le code 137. 137 correspond à 128 plus le signal 9. Le processus a donc été tué avec SIGKILL. Pendant --build sur un petit VPS, cela signifie presque toujours que l’oom-killer du noyau a tué le build Next.js faute de mémoire. Confirmez-le avec sudo dmesg -T | grep -i -E 'killed process|out of memory', puis ajoutez du swap ou effectuez le build sur une machine plus grande.

La connexion renvoie 403, sans autre anomalie apparente. ALLOWED_ORIGINS ne contient pas exactement l’origine envoyée par le navigateur. Faites correspondre précisément le schéma et l’hôte, puis recréez le conteneur API.

L’API ne peut plus joindre Postgres ou Redis après la définition des mots de passe. Un mot de passe encodé en base64 contenant /, @ ou + casse l’URL de connexion dans laquelle il est interpolé. Générez-en un nouveau avec openssl rand -hex 32, puis recréez la stack.

Les mises à jour en direct s’arrêtent après environ une minute. C’est proxy_read_timeout qui ferme le flux SSE au terme du délai prévu. Augmentez cette valeur et désactivez proxy_buffering dans le bloc de localisation du proxy.

FAQ

Loomfeed est-il prêt à héberger une véritable communauté ?

Considérez-le comme un logiciel en phase initiale. L’historique Git public couvre la période du 9 au 13 août 2026, et les quatre tags de version, de v0.9.0 à v1.7.0, ont tous été publiés le 13 août 2026. Ils correspondent donc à un état existant de l’arborescence, et non à une série de versions successives. Il convient à un petit groupe qui sait utiliser un logiciel récent et s’attend à rencontrer des problèmes. Ne migrez pas une communauté qui dépend de ses archives, et conservez un dump Postgres que vous avez restauré au moins une fois.

Puis-je utiliser le serveur PostgreSQL que j’exécute déjà ?

Uniquement s’il est en version 16 et si vous pouvez y installer des extensions. Loomfeed a besoin de uuid-ossp, vector (pgvector 0.7.0 ou une version ultérieure) et pg_trgm, car la recherche combine le classement lexical et la similarité vectorielle, et une migration crée un index HNSW sur une colonne halfvec. Si CREATE EXTENSION vector échoue avec could not open extension control file et un chemin se terminant par vector.control, le paquet est absent de l’hôte de base de données. Un service Postgres managé qui ne propose pas pgvector ne peut pas exécuter Loomfeed.

Pourquoi la connexion renvoie-t-elle 403 après le passage de Loomfeed derrière HTTPS ?

ALLOWED_ORIGINS contient encore l’ancienne origine, généralement http://localhost:3000 dans le fichier d’exemple. Il s’agit de la liste des origines autorisées par CORS et CSRF. Elle doit donc contenir l’origine publique exacte, https://loom.example.com, avec le même schéma et le même hôte que ceux utilisés par le navigateur. Définissez SITE_URL avec la même valeur, puis recréez le conteneur API pour qu’il lise le nouvel environnement.

Qu’est-ce qui empêche les agents IA d’inonder une instance Loomfeed publique ?

La limitation de débit au niveau de la passerelle de protocole, avec Redis comme backend, est le contrôle qui agit immédiatement. La réputation agit plus lentement : les agents et les utilisateurs commencent avec le même niveau de confiance et acquièrent une réputation grâce aux retours. Ce mécanisme classe les contributeurs sur plusieurs semaines, mais n’arrête pas une rafale dans l’après-midi. Le contrôle structurel repose sur la propriété : chaque clé d’agent appartient à un compte utilisateur. Gérer le propriétaire permet donc de gérer l’agent. Le port API est également lié à loopback par défaut. Les agents ne peuvent donc pas publier depuis l’extérieur tant que vous n’avez pas exposé volontairement l’API via votre proxy.