SSD Nodes Learn Hosting plans →
Guides Matt ConnorPar Matt Connor

Auto-héberger AFFiNE avec Docker Compose

Déployez AFFiNE sur un VPS avec Docker Compose : quatre conteneurs, tags d’image figés, données, sauvegardes et ce que permettent réellement 2 Go de RAM.

Ce que vous obtenez en auto-hébergeant AFFiNE

L’auto-hébergement d’AFFiNE vous fournit un espace de travail de type Notion sur un serveur que vous contrôlez. Il s’exécute dans quatre conteneurs : l’application, une tâche de migration exécutée une seule fois, Postgres et Redis. La collaboration en temps réel est incluse, jusqu’aux 10 comptes autorisés par défaut dans un espace de travail auto-hébergé. L’installation se résume à un fichier compose et à un fichier de configuration JSON. Les points à traiter sont les tags d’image, l’organisation des disques, la limite mémoire et le proxy placé en amont.

AFFiNE regroupe un éditeur de documents et un canvas infini dans le même espace de travail. Une même page peut donc être consultée comme un document ou étendue comme un tableau blanc. Si vous hésitez encore sur le service à installer, consultez d’abord la comparaison des alternatives auto-hébergées à Notion. Ce guide part du principe que votre choix est fait et explique comment exécuter AFFiNE correctement, sans refaire la comparaison.

Tout le contenu de ce guide a été vérifié à partir de la documentation officielle d’auto-hébergement d’AFFiNE et des fichiers de release publiés le 8 août 2026. La dernière release stable à cette date était la 0.27.3, publiée le 23 juillet 2026. Les affirmations ci-dessous qui comportent une date ultérieure ont été revérifiées à partir de leurs sources primaires le 12 octobre 2026, date à laquelle la dernière release stable était la 0.27.4, publiée le 18 août 2026.

Ce que font réellement les quatre conteneurs

affine regroupe le serveur et le client web dans une seule image. Il écoute sur le port 3010.

affine_migration est une tâche exécutée une seule fois. Elle lance node ./scripts/self-host-predeploy.js, applique les migrations de la base de données, puis se termine. L’application déclare condition: service_completed_successfully pour cette tâche. Une migration qui se termine avec un code différent de zéro empêche donc complètement le démarrage de affine. Si l’interface web ne démarre pas, consultez d’abord le journal de cette tâche.

postgres contient vos documents, utilisateurs, espaces de travail et permissions. L’image fournie est pgvector/pgvector:pg16, c’est-à-dire PostgreSQL 16 standard avec l’extension pgvector compilée. pgvector ajoute à PostgreSQL un type de colonne vector. Cette représentation numérique sert à stocker les embeddings afin de pouvoir rechercher du texte par similarité de sens.

redis est une dépendance obligatoire : le serveur et la tâche de migration attendent tous deux que son health check soit validé avant de démarrer. Remarquez ce que le fichier compose fourni ne donne pas à Redis : un volume. Rien de ce qu’il contient ne survit à un docker compose down. Cela indique clairement qu’il ne contient aucune donnée vous appartenant et qu’il n’a pas besoin d’être sauvegardé.

Pourquoi l’image Postgres est pgvector et non postgres standard

Cette exigence vient du schéma d’AFFiNE, et non d’une préférence. Dans la version v0.27.4, schema.prisma déclare toujours extensions = [pgvector(map: "vector")], mais ne contient aucune colonne d’embedding. Les quatre modèles contenant des colonnes embedding typées vector(1024) dans la version v0.27.3 sont absents de la version v0.27.4. Conservez l’image pgvector/pgvector:pg16 fournie : l’extension est toujours déclarée, et postgres:16 standard ne l’inclut pas.

Le template officiel v0.27.4 utilise l’image pgvector ; le template v0.21.0 utilisait encore postgres:16. Sur une installation plus ancienne, modifier la ligne de l’image ne suffit pas pour effectuer la mise à niveau. Consultez donc la page consacrée à la mise à niveau dans la documentation d’auto-hébergement d’AFFiNE avant de récupérer quoi que ce soit.

Un dernier point concernant ce tag. pg16 signifie Postgres 16, et une version majeure de Postgres ne se modifie pas comme un simple numéro. Si vous la remplacez par pg17 sur un répertoire de données existant, Postgres refuse de démarrer. Le journal contient alors une ligne similaire à The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17 dans docker compose logs postgres. Le passage à une nouvelle version majeure nécessite l’une des trois méthodes mentionnées dans les notes de version de PostgreSQL 17 : effectuer un dump puis une restauration dans un nouveau répertoire de données, utiliser pg_upgrade ou mettre en place une réplication logique. Pour une installation Compose unique, le dump et la restauration sont généralement la méthode la plus simple, car pg_upgrade nécessite que les binaires de l’ancien et du nouveau serveur soient disponibles côte à côte.

Combien de CPU et de RAM faut-il à AFFiNE auto-hébergé

La page des prérequis d’AFFiNE demande au moins 4 cœurs CPU et 2 GB de RAM. Elle recommande 4 GB de mémoire lorsque vos documents dépassent 10,000 mots. La même page indique où la mémoire est utilisée : le système de synchronisation et la fusion des documents. Elle donne un chiffre à retenir : la fusion d’un document comportant 10,000 modifications peut atteindre 1 GB de mémoire.

Comparez maintenant ces besoins à un plan de 2 GB utilisé par deux personnes. La moyenne ne pose pas de problème. Postgres et le processus Node restent sous la limite, avec une marge disponible. Le problème vient des pics. Une seule fusion importante peut porter l’utilisation mémoire du serveur à environ 1 GB. Sur une machine de 2 GB sans swap, ce pic, ajouté à Postgres et aux autres processus résidents, peut épuiser la mémoire. Le tueur out-of-memory (OOM) du kernel tue alors un processus pour libérer de la mémoire. Le serveur AFFiNE étant le processus le plus volumineux, il est la victime la plus probable. Toutefois, la documentation de Docker précise que n’importe quel processus peut être choisi, y compris Docker lui-même.

Lorsque le serveur AFFiNE est tué, votre collègue ne voit pas d’erreur. La page se recharge, car restart: unless-stopped redémarre le conteneur en quelques secondes. Ne vous contentez pas de le supposer : vérifiez-le :

docker inspect affine_server --format '{{.State.OOMKilled}} {{.RestartCount}}'
sudo dmesg -T | grep -i -E 'out of memory|killed process'

true dans la sortie de la première commande, ou une ligne Killed process indiquant node dans la sortie de la seconde, signifie que vous avez épuisé la mémoire et non découvert un bug. Corrigez le problème des deux côtés. Commencez par ajouter du swap. Le pic pourra ainsi être déporté vers cet espace avant que le tueur OOM n’intervienne :

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -h

free -h doit maintenant indiquer un total de swap de 2.0Gi. Le swap ne rend pas AFFiNE plus rapide, et ce n’est pas son objectif. Il fournit un espace temporaire pour absorber un pic. Le pic a ainsi beaucoup plus de chances de provoquer un ralentissement d’une seconde que l’arrêt du conteneur. Le swap est une réserve, pas une garantie. L’autre partie de la correction consiste à empêcher Postgres d’étendre son cache dans l’espace dont l’application a besoin pendant les fusions. C’est le rôle des limites mémoire sur un service Compose. Si les arrêts continuent après ces deux changements, le serveur a besoin des 4 GB demandés par la page des prérequis d’AFFiNE pour les documents volumineux.

Le stockage est beaucoup plus facile à prévoir. Voici les chiffres publiés par AFFiNE sur cette même page :

GraphiquePublished AFFiNE storage figures, August 2026
Les données de ce graphique
[
  {
    "label": "Server install",
    "gb": 1.5
  },
  {
    "label": "Postgres per 1,000 docs",
    "gb": 0.1
  },
  {
    "label": "Blob store per 1,000 uploads",
    "gb": 10
  }
]

L’installation du serveur occupe 1.5 GB. Un millier de documents d’environ un millier de mots chacun ajoutent 0.1 GB de données Postgres, ce qui est presque négligeable. Un millier de fichiers téléversés ajoutent 10 GB, et c’est là l’essentiel. Il s’agit de chiffres de planification publiés, pas de mesures effectuées sur une instance en fonctionnement. Considérez-les donc comme une tendance, et non comme une garantie. Ce qui compte, c’est cette tendance : votre base de données reste peu volumineuse, tandis que vos fichiers téléversés déterminent l’espace disque nécessaire.

Rédiger vous-même le fichier Compose, avec des tags figés

L’installation documentée télécharge un fichier prêt à l’emploi avec curl -L -o docker-compose.yml https://github.com/toeverything/AFFiNE/releases/latest/download/docker-compose.yml. Cela fonctionne. Un détail mérite toutefois votre attention avant de vous y fier : au 8 août 2026, le fichier joint à la release 0.27.3 lisait encore ses chemins dans un fichier .env, avec ${UPLOAD_LOCATION}, ${CONFIG_LOCATION} et ${DB_DATA_LOCATION}, tandis que la page de référence de la documentation présentait une structure plus récente qui place le stockage et les données PostgreSQL sous ./data, conserve la configuration sous ./config et ne nécessite aucun .env. La release 0.27.4 a déplacé le fichier joint vers cette structure et, au 12 octobre 2026, les deux correspondent. Si vous avez installé l’ancienne version du fichier, vos chemins proviennent toujours de .env. La page de mise à niveau d’AFFiNE avertit que remplacer le fichier Compose sans reporter ces paramètres peut démarrer Postgres avec un répertoire de données vide. Rédiger vous-même le fichier évite entièrement ce problème. Vous devez de toute façon le modifier pour figer les images et définir un mot de passe de base de données.

mkdir -p ~/affine/config ~/affine/data
cd ~/affine
printf 'DB_PASSWORD=%s\n' "$(openssl rand -hex 24)" > .env
chmod 600 .env

Compose lit automatiquement .env dans le répertoire du projet et remplace ${DB_PASSWORD} pour vous. Le mot de passe n’apparaît donc jamais dans le fichier que vous pourriez coller dans un thread de support. Conservez cette habitude pour toutes vos stacks. Le raisonnement est expliqué dans ne pas mettre les secrets dans le fichier Compose.

Écrivez maintenant ~/affine/docker-compose.yml :

name: affine
services:
  affine:
    image: ghcr.io/toeverything/affine:stable
    container_name: affine_server
    ports:
      - '127.0.0.1:3010:3010'
    depends_on:
      redis:
        condition: service_healthy
      postgres:
        condition: service_healthy
      affine_migration:
        condition: service_completed_successfully
    volumes:
      - ./data/storage:/root/.affine/storage
      - ./config:/root/.affine/config
    environment:
      - REDIS_SERVER_HOST=redis
      - DATABASE_URL=postgresql://affine:${DB_PASSWORD}@postgres:5432/affine
      - AFFINE_INDEXER_ENABLED=false
    restart: unless-stopped

  affine_migration:
    image: ghcr.io/toeverything/affine:stable
    container_name: affine_migration_job
    command: ['sh', '-c', 'node ./scripts/self-host-predeploy.js']
    volumes:
      - ./data/storage:/root/.affine/storage
      - ./config:/root/.affine/config
    environment:
      - REDIS_SERVER_HOST=redis
      - DATABASE_URL=postgresql://affine:${DB_PASSWORD}@postgres:5432/affine
      - AFFINE_INDEXER_ENABLED=false
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

  redis:
    image: redis:8-alpine
    container_name: affine_redis
    healthcheck:
      test: ['CMD', 'redis-cli', '--raw', 'incr', 'ping']
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  postgres:
    image: pgvector/pgvector:pg16
    container_name: affine_postgres
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: affine
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: affine
      POSTGRES_INITDB_ARGS: '--data-checksums'
    healthcheck:
      test: ['CMD', 'pg_isready', '-U', 'affine', '-d', 'affine']
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

Voici quatre différences par rapport au fichier fourni en amont. Chacune a une raison.

  • 127.0.0.1:3010:3010 publie le port uniquement sur l’adresse loopback. Les versions de Docker antérieures à 28.0.0 permettent aux hôtes du même segment L2 d’atteindre les ports publiés sur localhost. Le routage direct configuré peut aussi exposer le port du conteneur malgré une publication sur loopback. Utilisez Docker 28.0.0 ou une version ultérieure et laissez le routage direct désactivé pendant l’installation. Le '3010:3010' fourni en amont écoute sur toutes les interfaces. Sur la plupart des images VPS, cela inclut l’interface publique.
  • POSTGRES_HOST_AUTH_METHOD: trust a été supprimé et un mot de passe est défini à la place. L’authentification trust accepte toute connexion à cette base de données en tant qu’utilisateur affine, sans mot de passe. Elle est limitée au réseau privé Compose. Cela convient jusqu’au jour où vous ajoutez un autre conteneur à ce réseau ou publiez 5432 pour effectuer un diagnostic.
  • redis:8-alpine remplace un redis non versionné, qui résout vers latest. En août 2026, il s’agit de Redis 8. Le tag figé conserve donc la version majeure testée et empêche l’arrivée de Redis 9 lors d’un docker compose pull sans rapport.
  • pgvector/pgvector:pg16 reste exactement défini comme en amont, pour la raison indiquée précédemment.

POSTGRES_PASSWORD est lu uniquement lorsque Postgres crée son répertoire de données pour la première fois. Sur une instance qui existe déjà, définissez le mot de passe avec docker compose exec postgres psql -U affine -c "ALTER USER affine WITH PASSWORD 'yourpassword'", puis mettez DATABASE_URL à jour pour qu’il corresponde.

La configuration se trouve dans config/config.json

AFFiNE lit ses paramètres dans config/config.json, qui correspond au répertoire monté dans /root/.affine/config. Ce fichier n’est pas créé automatiquement. Vous devez donc le créer avant le premier démarrage. Ouvrez ~/affine/config/config.json dans un éditeur et donnez-lui le contenu suivant, en remplaçant l’exemple par votre propre domaine :

{
  "$schema": "https://github.com/toeverything/affine/releases/latest/download/config.schema.json",
  "server": {
    "name": "Team workspace",
    "externalUrl": "https://affine.example.com"
  },
  "copilot": {
    "enabled": false,
    "byok": {
      "enabled": false
    }
  }
}

server.externalUrl doit être l’adresse que vos utilisateurs ouvrent réellement dans un navigateur. AFFiNE utilise cette valeur pour générer les liens de partage et les invitations aux workspaces. Si elle reste définie sur http://localhost:3010, une invitation que vous envoyez pointe vers la machine du destinataire et échoue sur cette machine. Définissez-la sur l’adresse HTTPS publique avant le premier démarrage. Le fichier et le panneau d’administration utiliseront ainsi la même valeur. Les paramètres que vous modifiez ensuite dans le panneau d’administration sont enregistrés dans la base de données et ne sont pas réécrits dans ce fichier.

copilot contrôle les fonctionnalités d’IA. copilot.byok.enabled active l’utilisation de votre propre clé et permet au propriétaire d’un workspace de saisir la clé de son fournisseur de modèles dans les paramètres du workspace. L’auto-hébergement d’AFFiNE n’inclut pas d’abonnement à un service d’IA. Laissez les deux paramètres sur false si vous ne souhaitez pas utiliser ces fonctionnalités.

Démarrez la stack :

docker compose up -d
docker compose ps --all

docker compose ps --all doit indiquer que affine_postgres et affine_redis sont sains, que affine_server est en cours d’exécution et que affine_migration_job possède l’état exited (0). L’option --all est importante : sans elle, docker compose ps affiche uniquement les conteneurs en cours d’exécution. Un job de migration terminé correctement n’apparaît donc pas du tout. Tout autre code de sortie du job de migration doit être examiné. Son journal indique l’étape qui s’est arrêtée :

docker compose logs affine_migration

Épinglez l’image avant de l’oublier

stable est un tag mobile. Le workflow de publication d’AFFiNE associe plusieurs tags à chaque build stable. Deux d’entre eux nous intéressent ici : stable, réassigné à chaque publication, et stable- suivi du hash court Git, qui ne change pas. Si vous conservez stable, un docker compose pull exécuté six mois plus tard récupérera une autre image et appliquera ses migrations à votre base de données à un moment que vous n’avez pas choisi. Épinglez l’image exacte que vous avez testée :

docker compose pull
docker image inspect ghcr.io/toeverything/affine:stable --format '{{index .RepoDigests 0}}'

Cette commande affiche une ligne comme ghcr.io/toeverything/affine@sha256: suivie d’un long hash. Collez la chaîne complète dans la ligne image: de **affine et affine_migration. Ces deux valeurs doivent toujours être identiques, car il s’agit de la même image utilisée dans deux rôles. Une différence signifie que la base de données est migrée vers un schéma, puis utilisée avec un autre. Une mise à niveau devient alors une modification volontaire plutôt qu’une surprise : changez le digest, effectuez une sauvegarde, docker compose pull, docker compose up -d.

Créez le compte administrateur avant toute autre personne

Ouvrez /admin sur une instance neuve. AFFiNE vous redirige vers une page de création de compte, car le serveur n’a encore aucun administrateur. Ce parcours ne demande ni code d’invitation ni jeton de configuration. La première personne qui termine l’inscription devient l’administrateur de votre serveur. Le port doit donc rester fermé jusqu’à votre inscription.

C’est pourquoi le fichier Compose ci-dessus lie le service à 127.0.0.1. Accédez-y depuis votre propre machine au moyen d’un tunnel SSH :

ssh -L 3010:127.0.0.1:3010 you@your-server-ip

Laissez ce tunnel actif et ouvrez http://127.0.0.1:3010/admin dans votre navigateur local. Inscrivez-vous et connectez-vous, puis fermez le tunnel. Vous pouvez seulement maintenant publier l’instance sur un nom public. Le même problème de concurrence existe dans d’autres applications auto-hébergées. Il est plus grave lorsque la première connexion crée une passkey liée au nom d’hôte. C’est pourquoi TLS et le domaine définitif doivent être configurés avant la création du premier compte lorsque vous auto-hébergez openGym.

Où AFFiNE stocke vos données

Trois chemins contiennent toutes les données. Ils se trouvent tous dans le répertoire que vous avez créé.

  • ./data/postgres est le répertoire de données Postgres. Il contient les documents, les utilisateurs, les espaces de travail, les permissions et les paramètres du serveur enregistrés depuis le panneau d’administration.
  • ./data/storage est monté sur /root/.affine/storage dans le conteneur et contient tous les fichiers importés.
  • ./config est monté sur /root/.affine/config et contient config.json ainsi que le private.key généré par la tâche de migration.

Le projet utilise ici des bind mounts plutôt que des volumes nommés. Ce choix est volontaire : vous pouvez archiver et copier ces chemins avec les commandes usuelles, sans demander à Docker où ils sont stockés. En contrepartie, la gestion du propriétaire des fichiers sur l’hôte vous incombe. C’est le compromis présenté dans bind mounts et volumes nommés.

Comment sauvegarder AFFiNE

Deux éléments doivent être sauvegardés, et ils ne se sauvegardent pas de la même manière. La base de données est un serveur actif. Copier ses fichiers pendant son fonctionnement produit une copie corrompue. Il faut donc créer un dump :

mkdir -p ~/affine/backup
cd ~/affine
docker compose exec -T postgres pg_dump --format c --username affine affine \
  > backup/affine-$(date +%F).dump
ls -lh backup/

Le dump s’exécute dans le conteneur via son socket local. Il ne demande donc pas le mot de passe. Vérifiez la taille dans la sortie de ls. Un fichier de quelques centaines d’octets indique que le dump a échoué alors que le shell a quand même créé le fichier. C’est un problème que l’on découvre six mois plus tard. -T est également important : sans cette option, Compose peut allouer un terminal et corrompre le flux binaire.

Les fichiers importés sont de simples fichiers. Archivez-les donc avec tar :

tar czf backup/storage-$(date +%F).tgz -C data storage
tar czf backup/config-$(date +%F).tgz config docker-compose.yml .env

Conservez l’intégralité du répertoire config, du fichier Compose et de .env dans votre sauvegarde, car le dump ne les contient pas. La tâche de migration crée également config/private.key. Copier uniquement config.json exclut donc ce fichier. Les paramètres enregistrés dans le panneau d’administration sont stockés dans la base de données. Le dump ci-dessus les contient donc déjà. Depuis le 12 octobre 2026, la page de sauvegarde d’AFFiNE permet également d’exporter les paramètres du serveur modifiés depuis le panneau d’administration. Copiez les trois fichiers hors du serveur. Une sauvegarde stockée sur le même disque que les données qu’elle protège n’est pas une sauvegarde. Cette séparation entre un dump de la base de données et une archive tar du répertoire des fichiers importés est le modèle à appliquer à chaque autre conteneur stateful que vous exécutez. C’est également ce qui protège l’historique des conversations et les pièces jointes lorsque vous auto-hébergez Chatwoot comme outil de support.

Restauration et piège dans les étapes publiées

Lisez les étapes officielles de restauration avant d’en avoir besoin, et lisez-les attentivement. Dans leur version publiée en août 2026, elles copiaient un fichier nommé affine.backup dans le conteneur, puis lançaient la restauration depuis ./pg.backup. Il s’agit de deux noms différents. Elles supprimaient également un répertoire ./postgres, alors que le fichier compose actuel conserve ses données dans ./data/postgres. Depuis le 12 octobre 2026, la page utilise affine.backup partout et indique de conserver l’ancien répertoire Postgres jusqu’à la vérification de la restauration. Le piège a donc été corrigé en amont, mais une copie des étapes enregistrée avant cette date le contient toujours. Dans tous les cas, utilisez les chemins réellement employés plutôt que ceux d’un extrait. Voici la séquence correspondant à l’arborescence de ce guide :

cd ~/affine
docker compose down
sudo mv data/postgres data/postgres.old
docker compose up -d postgres
docker compose cp backup/affine-2026-08-08.dump postgres:/tmp/affine.dump
docker compose exec postgres pg_restore --format c --username affine \
  --dbname affine --verbose /tmp/affine.dump
docker compose up -d

Notez l’utilisation de mv plutôt que de rm. Restaurer par-dessus une base de données dont vous n’avez pas conservé de copie peut transformer une mauvaise commande en perte totale de données. Mettre l’ancien répertoire de côté ne coûte rien. Restaurez également les fichiers importés avec tar xzf backup/storage-2026-08-08.tgz -C data, sinon chaque document affichera des pièces jointes manquantes. Connectez-vous ensuite et ouvrez un document contenant une image. C’est le test à effectuer. Une restauration que vous n’avez pas ouverte dans un navigateur est un fichier, pas une sauvegarde.

Mettre AFFiNE derrière un proxy déjà en service

AFFiNE utilise WebSocket, et ce fonctionnement est obligatoire. La documentation est claire : WebSocket est à la base de la synchronisation et de la collaboration dans AFFiNE. Un proxy qui ne met pas ces connexions à niveau vous donne un espace de travail dans lequel les modifications cessent discrètement d’être synchronisées. La page se charge, la connexion fonctionne, mais une modification effectuée dans un navigateur n’atteint jamais l’autre. Dans les outils de développement de votre navigateur, ouvrez l’onglet Network et filtrez sur WS. Une connexion qui s’ouvre et se ferme en boucle indique que le proxy ne transmet pas la mise à niveau.

Si vous utilisez déjà Traefik pour d’autres conteneurs, AFFiNE s’y ajoute comme un service normal. Supprimez le bloc ports: du service affine, puis ajoutez :

    networks:
      - default
      - proxy
    labels:
      - 'traefik.enable=true'
      - 'traefik.docker.network=proxy'
      - 'traefik.http.routers.affine.rule=Host(`affine.example.com`)'
      - 'traefik.http.routers.affine.entrypoints=websecure'
      - 'traefik.http.routers.affine.tls.certresolver=letsencrypt'
      - 'traefik.http.services.affine.loadbalancer.server.port=3010'

Et en bas du fichier, à côté de services: :

networks:
  proxy:
    external: true

Le nom du certificate resolver doit correspondre à celui défini dans la configuration de Traefik. loadbalancer.server.port désigne le port du conteneur 3010, jamais un port de l’hôte. Traefik proxyfie les connexions WebSocket sans configuration supplémentaire. Vous n’avez donc rien d’autre à ajouter. Si le reste de votre stack est déjà derrière Authentik pour l’authentification unique, un middleware forward auth sur ce router contrôlera l’accès navigateur à AFFiNE. Désactivez-le toutefois jusqu’à ce que vous ayez testé l’application desktop, qui ne dispose d’aucune session navigateur et échouera simplement à se synchroniser. La configuration de plusieurs applications derrière une même instance est présentée dans un Traefik unique devant plusieurs applications.

Avec nginx, vous devez demander explicitement la mise à niveau :

location / {
    proxy_pass http://127.0.0.1:3010;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    client_max_body_size 100m;
}

client_max_body_size vaut 1 MB par défaut dans nginx. Sans cette ligne, tout upload de plus d’une petite photo échoue avec un statut 413, et rien n’apparaît dans les journaux d’AFFiNE, car la requête n’est jamais arrivée. Caddy nécessite une seule ligne, reverse_proxy http://127.0.0.1:3010, et gère lui-même les certificats et les mises à niveau WebSocket.

Ce que la version auto-hébergée ne fournit pas

Soyez lucide sur ce point avant de faire passer une équipe sur cette version.

La collaboration en temps réel est disponible. C’est la fonctionnalité au cœur des recommandations de dimensionnement, car la documentation d’AFFiNE attribue la consommation mémoire au système de synchronisation et à la fusion des documents. La modification hors ligne explique pourquoi de nombreuses personnes recherchent un outil local-first. L’application de bureau peut ajouter votre serveur auto-hébergé à sa liste d’espaces de travail et s’y connecter. Testez précisément le fonctionnement hors ligne dont votre équipe dépend avant de vous engager : modifiez un document dans l’application de bureau avec le réseau désactivé, reconnectez-vous, puis vérifiez le résultat sur un deuxième appareil. Une liste de fonctionnalités ne constitue pas une preuve, y compris celle-ci.

La recherche plein texte côté serveur est désactivée dans le fichier compose fourni. AFFINE_INDEXER_ENABLED=false y est défini sur le serveur et sur le job de migration. Pour l’activer, vous devez ajouter un conteneur Manticore Search. Cela fait un cinquième service et augmente la consommation mémoire. Sur une machine dotée de 2 GB de RAM, c’est le changement qui vous fait dépasser la limite. La recherche dans le client continue de fonctionner pour l’espace de travail ouvert.

Deux limites sont à connaître avant d’inviter d’autres personnes. Un espace de travail auto-hébergé autorise au maximum 10 utilisateurs. Au-delà, il faut une licence Team d’AFFiNE. La documentation décrit le stockage illimité des blobs et la taille illimitée des blobs pour les instances auto-hébergées comme des fonctionnalités prévues, mais pas encore totalement implémentées, d’après une vérification effectuée en October 2026. Ces limites n’ont aucune importance pour un foyer ou une petite équipe. Elles deviennent importantes si vous prévoyez de faire passer quarante personnes sur cette instance.

Mises à niveau

Lisez d’abord les notes de version, en particulier pour une mise à niveau mineure telle que 0.26 vers 0.27, car des changements incompatibles peuvent être introduits. Sauvegardez la base de données et le répertoire de stockage avant toute intervention, car la tâche de migration modifie le schéma au prochain démarrage et il n’existe aucune procédure d’annulation. Modifiez ensuite le digest épinglé, exécutez docker compose pull puis docker compose up -d, et surveillez docker compose logs -f affine_migration jusqu’à son arrêt normal. docker image prune supprime ensuite les anciennes couches. Pour les installations très anciennes, retenez également ceci : à partir de la version 0.23.0, le nom de l’image est passé de affine-graphql à affine. Un fichier Compose plus ancien doit donc faire réécrire ses lignes image avant qu’un pull puisse trouver l’image. À partir de la version 0.27.4, le modèle Compose officiel a déplacé les chemins de Postgres, des téléversements et de la configuration, et a modifié la connexion à la base de données par défaut. Comparez donc le nouveau fichier avec le vôtre au lieu de le remplacer sur une installation fonctionnelle.

FAQ

Pourquoi le conteneur AFFiNE ne démarre-t-il jamais ?

Le service affine déclare condition: service_completed_successfully pour le job affine_migration. Si la migration se termine avec un code différent de 0, le serveur ne démarre jamais et aucune interface web n’apparaît. Exécutez docker compose logs affine_migration pour voir quelle étape s’est arrêtée. Vérifiez qu’un fichier Compose modifié manuellement utilise toujours pgvector/pgvector:pg16. L’image Postgres standard n’inclut pas l’extension pgvector, alors que le schéma d’AFFiNE la déclare encore. Pourtant, v0.27.4 ne définit plus les quatre modèles présents dans v0.27.3.

De combien de RAM AFFiNE auto-hébergé a-t-il besoin ?

La page des prérequis d’AFFiNE demande au moins 4 cœurs CPU et 2 GB de RAM, et recommande 4 GB lorsque les documents dépassent 10,000 mots. Elle indique aussi que la fusion d’un document comportant 10,000 modifications peut atteindre 1 GB. Sur un serveur de 2 GB, c’est ce pic qui provoque l’arrêt, pas la charge au repos. Le kernel out-of-memory killer arrête un processus, probablement le serveur AFFiNE, qui est le plus gourmand. restart: unless-stopped le redémarre alors, et les utilisateurs voient un rechargement de la page plutôt qu’une erreur. Confirmez-le avec docker inspect affine_server --format '{{.State.OOMKilled}}' et sudo dmesg -T | grep -i 'out of memory'. Ajoutez ensuite un fichier swap de 2 GB pour absorber les pics courts. Passez à 4 GB de RAM si les arrêts continuent.

Où AFFiNE stocke-t-il mes données et que dois-je sauvegarder ?

Trois chemins sous votre répertoire Compose contiennent toutes les données : ./data/postgres pour la base de données, ./data/storage pour les fichiers téléversés et ./config pour config.json et le private.key généré. Sauvegardez la base de données avec docker compose exec -T postgres pg_dump --format c --username affine affine > affine.dump plutôt qu’en copiant les fichiers. Un serveur Postgres en fonctionnement ne peut pas être copié de manière sûre. Ce dump contient aussi les paramètres enregistrés dans le panneau d’administration, car AFFiNE les stocke dans la base de données. Archivez ./data/storage avec tar pour les téléversements. Conservez aussi l’intégralité du répertoire config, le fichier Compose et .env, car ces fichiers ne font pas partie de la base de données.

La collaboration en temps réel fonctionne-t-elle avec un AFFiNE auto-hébergé ?

Oui. Aucune activation n’est nécessaire. La seule exigence concerne votre reverse proxy, car la synchronisation utilise des connexions WebSocket. Avec nginx, cela signifie proxy_http_version 1.1 ainsi que les en-têtes Upgrade et Connection: upgrade. Traefik et Caddy transmettent ces connexions sans configuration supplémentaire. Si le proxy ne les met pas à niveau, l’espace de travail se charge et la connexion fonctionne normalement, mais les modifications effectuées dans un navigateur n’apparaissent jamais dans un autre.

Puis-je utiliser AFFiNE avec une image Postgres standard ?

Utilisez pgvector/pgvector:pg16. Il s’agit de Postgres 16 avec l’extension pgvector compilée. AFFiNE v0.27.4 déclare encore extensions = [pgvector(map: "vector")] dans schema.prisma, mais ne définit plus les quatre modèles comportant les colonnes d’embeddings vector(1024) présentes dans v0.27.3. Postgres standard n’inclut pas l’extension déclarée. Si vous connectez AFFiNE à un serveur Postgres externe, installez-y pgvector et créez l’extension dans la base de données cible avant d’exécuter la migration.

Cet article vous a-t-il été utile ?