Héberger AFFiNE soi-même, une alternative open source à Notion
Suivez ça sur votre propre serveur. Commencer gratuitement, sans carte
AFFiNE est un espace de travail open source qui mélange un éditeur de documents à la Notion, un tableau blanc infini et une base de données légère dans une seule application, avec votre contenu qui reste sur un serveur que vous contrôlez au lieu du nuage d’un fournisseur. C’est aussi un auto-hébergement réellement non trivial : une base de données Postgres avec l’extension pgvector, Redis, et une tâche de migration unique qui doit se terminer avant que l’application elle-même soit autorisée à démarrer. À la fin de ce guide, tout ça tournera sur votre propre serveur, derrière HTTPS, avec des sauvegardes et une mise à niveau répétable.
Ce qu’il vous faut
- Un serveur avec Docker et le plugin Compose. Un Hetzner CX23 (2 vCPU, 4 Go de RAM, 40 Go NVMe, 5,99 €/mois hors TVA) fait tourner AFFiNE confortablement pour un espace de travail personnel ou de petite équipe.
- Un domaine ou sous-domaine pointé vers l’adresse IP du serveur.
- Un accès SSH au serveur.
- Environ dix minutes, la plupart passées à attendre le DNS et la première migration.
Étape 1 : pointer le DNS vers le serveur et ouvrir le pare-feu
Créez un enregistrement A pour votre sous-domaine pointant vers l’IP publique du serveur, puis verrouillez l’hôte sur seulement ce dont vous avez besoin :
ufw allow OpenSSH
ufw allow 80
ufw allow 443
ufw --force enable
Étape 2 : générer un mot de passe Postgres
openssl rand -hex 24
mkdir -p /opt/affine
cat > /opt/affine/.env <<'EOF'
POSTGRES_PASSWORD=paste-the-generated-value-here
EOF
chmod 600 /opt/affine/.env
Étape 3 : écrire le fichier Compose
C’est la partie que la plupart des guides d’auto-hébergement d’AFFiNE ratent : le fichier Compose officiel exécute la migration de base de données comme son propre service de courte durée, pas comme une étape à l’intérieur du démarrage de l’application principale, et le conteneur de l’application doit attendre que cette tâche soit réellement terminée, pas juste démarrée, avant de démarrer.
services:
affine:
image: ghcr.io/toeverything/affine:0.26.7
restart: unless-stopped
depends_on:
redis:
condition: service_healthy
db:
condition: service_healthy
affine-migration:
condition: service_completed_successfully
environment:
DATABASE_URL: postgresql://affine:${POSTGRES_PASSWORD}@db:5432/affine
REDIS_SERVER_HOST: redis
AFFINE_SERVER_EXTERNAL_URL: https://notes.yourdomain.com
AFFINE_INDEXER_ENABLED: "false"
volumes:
- affine_storage:/root/.affine/storage
- affine_config:/root/.affine/config
expose:
- "3010"
affine-migration:
image: ghcr.io/toeverything/affine:0.26.7
command: ["node", "./scripts/self-host-predeploy.js"]
environment:
DATABASE_URL: postgresql://affine:${POSTGRES_PASSWORD}@db:5432/affine
REDIS_SERVER_HOST: redis
AFFINE_INDEXER_ENABLED: "false"
volumes:
- affine_storage:/root/.affine/storage
- affine_config:/root/.affine/config
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
db:
image: pgvector/pgvector:0.8.7-pg16
restart: unless-stopped
environment:
POSTGRES_USER: affine
POSTGRES_DB: affine
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- affine_db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U affine -d affine"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7.2.5-alpine
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- affine_redis_data:/data
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
depends_on:
- affine
volumes:
affine_storage:
affine_config:
affine_db_data:
affine_redis_data:
caddy_data:
caddy_config:
Ce guide fixe 0.26.7, la version stable actuelle au moment de l’écriture, pour chaque conteneur AFFiNE ; pgvector/pgvector:0.8.7-pg16 est Postgres 16 avec l’extension pgvector dont AFFiNE a besoin pour ses propres plongements (embeddings). affine-config est son propre volume séparé d’affine-storage volontairement : le script de pré-déploiement d’AFFiNE génère une vraie paire de clés de signature et de chiffrement au format PEM dans /root/.affine/config/private.key la première fois qu’il tourne, pas un simple secret que vous définissez vous-même, donc sans volume persistant pour ce répertoire, chaque redéploiement en générerait silencieusement une nouvelle et invaliderait la dernière. AFFINE_INDEXER_ENABLED est laissé à false ici, ce qui correspond au défaut propre d’AFFiNE ; activez-le une fois que vous voulez la recherche plein texte à travers les documents, ce n’est pas requis pour utiliser l’application.
Vérifiez que le fichier s’analyse correctement :
docker compose config
Étape 4 : ajouter Caddy et démarrer la pile
notes.yourdomain.com {
reverse_proxy affine:3010
}
docker compose up -d
docker compose up démarre db et redis en premier, attend que les deux vérifications de santé passent, puis exécute affine-migration jusqu’à ce que ce soit terminé avant qu’affine lui-même soit autorisé à démarrer. Observez le processus :
docker compose logs -f affine-migration
Vous devriez voir la sortie de la migration se terminer par une sortie propre, puis :
docker compose ps
curl -I https://notes.yourdomain.com
Vous devriez voir affine-migration listé comme Exited (0), chaque autre service Up ou healthy, et le curl retourner HTTP/2 200.
Étape 5 : créer votre espace de travail
Visitez https://notes.yourdomain.com dans un navigateur. Rien n’est pré-rempli, alors créez votre premier compte et votre premier espace de travail directement depuis l’interface web. À partir de là, invitez des coéquipiers et commencez à écrire des documents ou des tableaux blancs de la même façon que vous le feriez dans un Notion ou un Miro hébergé.
Étape 6 : sauvegarder Postgres et le volume de configuration
Les documents et le contenu des tableaux blancs vivent dans Postgres ; la paire de clés de signature de l’étape 3 vit dans le volume affine-config. Sauvegardez les deux :
cat > /opt/affine/backup.sh <<'EOF'
#!/bin/sh
cd /opt/affine
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
docker compose exec -T db pg_dump -U affine affine | gzip > backups/db-$TIMESTAMP.sql.gz
docker run --rm -v affine_affine_config:/config -v "$PWD/backups":/backup alpine \
tar czf /backup/config-$TIMESTAMP.tar.gz -C /config .
find backups -mtime +14 -delete
EOF
mkdir -p /opt/affine/backups
chmod +x /opt/affine/backup.sh
(crontab -l 2>/dev/null; echo "0 3 * * * /opt/affine/backup.sh") | crontab -
Lancez-le une fois à la main et vérifiez que ça a fonctionné : ./backup.sh && ls -lh backups. Vous devriez voir à la fois un fichier .sql.gz et un fichier .tar.gz avec l’horodatage du jour. Copiez les deux en dehors du serveur, perdre le volume de configuration sans sauvegarde veut dire que chaque client doit refaire confiance à une nouvelle clé de signature au prochain redémarrage, pas seulement une perte de données.
Étape 7 : mettre à niveau
docker compose pull
docker compose up -d
Compose relance affine-migration jusqu’à ce que ce soit terminé avant de recréer affine lui-même, le même ordre de dépendance qu’à l’étape 4, donc tout changement de schéma dans la nouvelle version est appliqué automatiquement. Vérifiez à nouveau docker compose logs -f affine-migration pour une sortie propre avant de considérer que la mise à niveau est passée.
Le raccourci : avec Railyard
Tout ce qui précède, Postgres avec pgvector, Redis, la tâche de migration qui doit se terminer avant que l’application démarre, le volume de configuration séparé pour la clé de signature, est aussi ce que le modèle de pile AFFiNE de Railyard configure pour vous. Connectez un serveur, puis choisissez AFFiNE dans le catalogue de modèles : app.railyard.run/deploy?stack=affine. Railyard branche automatiquement les chaînes de connexion de la base de données et de Redis et exécute le script de pré-déploiement comme étape de mise en production avant chaque déploiement, pas seulement le premier, et émet le certificat HTTPS pour votre domaine.
Voyez déployer avec un bouton et bases de données et sauvegardes pour savoir comment les modèles et les volumes persistants fonctionnent sur la plateforme de façon plus générale.
Dépannage
Le conteneur affine redémarre en boucle et ne sert jamais rien. Vérifiez docker compose ps, si affine-migration montre autre chose que Exited (0), l’application principale refuse correctement de démarrer sur des données erronées plutôt que de servir contre un schéma non migré ; lisez docker compose logs affine-migration pour l’échec réel.
Chaque client est déconnecté après un redémarrage, ou on lui demande de refaire confiance au serveur. Le volume affine-config a été perdu ou recréé, ce qui régénère la clé de signature. Confirmez que le volume est listé dans docker volume ls et n’a pas été accidentellement nettoyé ; restaurer la sauvegarde config-*.tar.gz de l’étape 6 dans un volume neuf est la seule façon de revenir en arrière.
Le HTTPS ne s’émet pas. docker compose logs caddy montrant des erreurs ACME répétées signifie presque toujours que l’enregistrement DNS de l’étape 1 ne s’est pas propagé ou pointe vers la mauvaise IP ; confirmez avec dig +short notes.yourdomain.com.
Connexion à Postgres refusée. Confirmez que DATABASE_URL utilise db comme nom d’hôte sur affine et affine-migration, pas localhost.
La recherche ne retourne aucun résultat à travers les documents. C’est attendu avec la configuration par défaut ; AFFINE_INDEXER_ENABLED est à false par défaut, ce qui correspond au propre fichier Compose de référence d’AFFiNE, activez-le à l’étape 3 si vous en avez besoin.
FAQ
Puis-je sauter le volume de configuration et simplement définir une clé statique moi-même ? Non, le script de pré-déploiement propre à AFFiNE génère la vraie paire de clés PEM dont il a besoin ; il n’y a pas de variable d’environnement documentée pour en fournir une directement, ce qui explique pourquoi le volume de l’étape 3 est requis plutôt qu’optionnel.
AFFiNE a-t-il besoin d’un fournisseur de courriel externe ? Pas pour utiliser l’application au quotidien ; le courriel compte si vous voulez des courriels d’invitation ou des processus de réinitialisation de mot de passe, qu’AFFiNE prend en charge en configuration séparée mais qui ne sont pas requis pour une installation fonctionnelle à un seul espace de travail.
L’édition collaborative en temps réel est-elle incluse ? Oui, plusieurs personnes qui éditent le même document ou tableau blanc en même temps est une fonctionnalité centrale, pas un ajout, appuyée sur les mêmes Postgres et Redis déjà en fonctionnement ici.
Puis-je déplacer les fichiers téléversés vers du stockage objet au lieu du volume local ? AFFiNE prend en charge un support de stockage compatible S3 pour les pièces jointes ; la configuration par défaut ici garde tout sur affine_storage pour la simplicité, ça vaut la peine d’y revenir une fois que les pièces jointes deviennent volumineuses.
Que se passe-t-il si je perds le serveur sans sauvegarde ? Les documents, les tableaux blancs et la clé de signature sont tous perdus ; il n’y a aucun chemin de récupération vers des données qui n’ont jamais été sauvegardées ailleurs, ce qui explique pourquoi l’étape 6 n’est pas optionnelle en pratique.
Une installation AFFiNE qui fonctionne, ce sont quatre services qui dépendent les uns des autres dans un ordre précis : Postgres et Redis en premier, la tâche de migration ensuite, l’application en dernier. Une fois cet ordre bien établi et les sauvegardes en cours d’exécution, il reste très peu à entretenir au-delà de la mise à niveau occasionnelle de l’étape 7.
Pourquoi les gens choisissent Railyard pour ça
- Votre propre compte infonuagique. Déployez sur DigitalOcean, Hetzner, Vultr, AWS, OVHcloud, ou n'importe quel serveur Linux par SSH, facturé directement par votre fournisseur, sans majoration.
- Un prix fixe par équipe. L'offre gratuite est gratuite pour toujours : un serveur plus un serveur de base de données, trois applications, sans carte. Pro coûte 19 $ par mois pour jusqu'à cinq serveurs et un nombre illimité d'applications. Team coûte 49 $ par mois avec des serveurs illimités. Aucuns frais par application, par déploiement, par siège ou par gigaoctet.
- Des gabarits en un clic. Déployez des applications open source, comme des bases de données, des outils d'analyse et des outils de développement, sur votre propre serveur en un clic.
- Des bases de données gérées. Postgres, MySQL et Redis, avec des sauvegardes planifiées, vérifiées par restauration, et la récupération à un point dans le temps.
- Déploiement par push. Déployez depuis GitHub, GitLab ou la CLI, avec HTTPS automatique et des mises en production sans interruption.