SSD Nodes Learn 🎉 VPS dès $4.99/mois
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’images figés, données, sauvegardes et limites réelles de 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 avec 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, dans la limite des 10 sièges disponibles par défaut dans un espace de travail auto-hébergé. L’installation nécessite un seul fichier Compose et un seul fichier de configuration JSON. Les points à traiter sont les tags des images, la disposition du stockage, la limite de mémoire et le proxy placé en amont.

AFFiNE réunit 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 à déployer, consultez d’abord la comparaison des alternatives auto-hébergées à Notion. Ce guide part du principe que le choix est fait et explique comment exécuter AFFiNE correctement, sans refaire cette comparaison.

Tout le contenu de ce guide a été vérifié à partir de la documentation 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 version 0.27.3, publiée le 23 juillet 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. Lorsque l’interface web ne démarre pas, les journaux de cette tâche sont les premiers à consulter.

postgres contient vos documents, vos utilisateurs, vos espaces de travail et vos permissions. L’image fournie est pgvector/pgvector:pg16 : il s’agit d’un Postgres 16 standard avec l’extension pgvector compilée. pgvector ajoute à Postgres un type de colonne vector. Il s’agit du format numérique utilisé pour stocker les embeddings afin de rechercher du texte selon son sens.

redis est une dépendance obligatoire : le serveur et la tâche de migration attendent tous deux que son health check soit valide avant de démarrer. Notez ce que le fichier compose fourni n’attribue pas à Redis : un volume. Rien de ce qu’il contient ne survit à un docker compose down. Cela indique clairement qu’il ne contient aucune de vos données et qu’il ne nécessite aucune sauvegarde.

Pourquoi l’image Postgres est pgvector et non l’image Postgres standard

Cette exigence vient du schéma d’AFFiNE, et non d’une préférence. Dans schema.prisma, la datasource déclare extensions = [pgvector(map: "vector")], et quatre tables contiennent une colonne embedding typée vector(1024). Le job de migration crée ces tables, que les fonctionnalités d’IA soient activées ou non. L’extension doit donc déjà exister dans la base de données avant la fin de la migration. Si vous remplacez l’image par postgres:16, l’extension disparaît. La migration ne peut plus créer ces colonnes, et le serveur reste en attente d’un job qui a échoué.

AFFiNE est passé à l’image pgvector à partir de la version 0.21. Sur une installation antérieure, modifier la ligne de l’image ne suffit pas pour effectuer la mise à niveau. Consultez donc la page de mise à niveau de la documentation d’auto-hébergement d’AFFiNE avant de télécharger une image.

Autre point concernant ce tag. pg16 signifie Postgres 16. Une version majeure de Postgres ne se modifie pas comme un simple numéro. Si vous la remplacez par pg17 en conservant un répertoire de données existant, Postgres refuse de démarrer. Un message tel que The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17 apparaît dans docker compose logs postgres. Le passage à une autre version majeure nécessite un dump, puis une restauration dans un nouveau répertoire de données.

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 porte ensuite la mémoire requise à 4 GB lorsque vos documents dépassent 10,000 mots. La même page indique où va la mémoire : 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 utiliser jusqu’à 1 GB.

Comparez maintenant ces besoins à une offre de 2 GB utilisée par deux personnes qui écrivent. 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 du pic. Une seule fusion volumineuse peut demander 1 GB en plus de toute la mémoire déjà utilisée. Sur une machine de 2 GB sans swap, le tueur out-of-memory (OOM) du kernel répond à cette demande en tuant le plus gros processus, à savoir le serveur AFFiNE.

Votre collègue ne voit pas d’erreur. Il voit la page se recharger, car restart: unless-stopped relance le conteneur en quelques secondes. Ne faites pas de suppositions : 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 manqué de mémoire, et non que vous avez trouvé un bug. Corrigez le problème des deux côtés. Ajoutez d’abord du swap afin qu’un pic ralentisse le système au lieu de provoquer un arrêt :

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 2.0Gi de swap. Le swap n’accélère pas AFFiNE, et ce n’est pas son objectif. Il transforme un pic d’une seconde en une seconde lente, au lieu de provoquer l’arrêt du conteneur. L’autre volet de la correction consiste à empêcher Postgres d’étendre son cache dans l’espace dont l’application a besoin au moment de la fusion. C’est précisément à cela que servent les limites de mémoire sur un service Compose.

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

ChartPublished AFFiNE storage figures, August 2026
The data behind this chart
[
  {
    "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 utilise 1.5 GB. Mille documents d’environ mille mots chacun ajoutent 0.1 GB de données Postgres, ce qui est presque négligeable. Mille fichiers téléversés ajoutent 10 GB, ce qui représente l’essentiel. Ces chiffres sont des estimations de planification publiées, et non des mesures effectuées sur une instance en fonctionnement. Considérez-les donc comme un ordre de grandeur, pas comme une garantie. L’ordre de grandeur est ce qui compte : votre base de données reste petite, et ce sont vos fichiers téléversés qui déterminent l’espace disque nécessaire.

Rédigez vous-même le fichier Compose, avec les 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 associé à la release 0.27.3 lit 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ésente une structure plus récente qui regroupe tout sous ./data et ne nécessite aucun .env. Les deux versions sont valides. Rédiger vous-même le fichier règle la question, et 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. Ainsi, le mot de passe n’apparaît jamais dans le fichier que vous pourriez coller dans un ticket de support. Conservez cette habitude pour toutes les stacks que vous exécutez ; la raison est expliquée dans garder les secrets hors du 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 par l’éditeur. Chacune a une raison.

  • 127.0.0.1:3010:3010 publie le port uniquement sur l’adresse loopback. Aucun accès depuis l’extérieur du serveur à AFFiNE n’est donc possible tant que vous n’avez pas choisi la méthode d’exposition. Le fichier amont '3010:3010' se lie à toutes les interfaces, ce qui inclut l’interface publique sur la plupart des images VPS.
  • POSTGRES_HOST_AUTH_METHOD: trust est supprimé et un mot de passe est défini à la place. L’authentification trust accepte toute connexion à cette base de données avec l’utilisateur affine, sans mot de passe. Elle est limitée au réseau Compose privé, ce qui convient jusqu’au jour où vous ajoutez un autre conteneur à ce réseau ou publiez le port 5432 pour effectuer un diagnostic.
  • redis:8-alpine remplace un simple redis, qui pointe vers latest. En août 2026, il s’agit de Redis 8. Le tag figé conserve donc la version majeure que vous avez testée et empêche l’arrivée de Redis 9 pendant une autre docker compose pull.
  • pgvector/pgvector:pg16 reste exactement défini comme dans le fichier amont, pour la raison indiquée plus haut.

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 en conséquence.

La configuration se trouve dans config/config.json

AFFiNE lit ses paramètres dans config/config.json, qui correspond au répertoire monté sur /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 ce contenu, 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 espaces de travail. Si elle reste définie sur http://localhost:3010, une invitation que vous envoyez redirige le destinataire vers sa propre machine et échoue. Définissez cette valeur sur l’adresse HTTPS publique avant le premier démarrage. Le fichier de configuration et le panneau d’administration utiliseront ainsi la même adresse.

copilot active ou désactive les fonctionnalités d’IA. copilot.byok.enabled active l’utilisation de votre propre clé et permet au propriétaire d’un espace de travail de saisir la clé de son fournisseur de modèles dans les paramètres de l’espace de travail. L’auto-hébergement d’AFFiNE n’inclut aucun 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

docker compose ps doit afficher affine_postgres et affine_redis comme étant sains, affine_server comme étant en cours d’exécution, et affine_migration_job avec l’état exited (0). Tout autre code de sortie du job de migration doit être analysé. 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 mutable. Le processus de publication d’AFFiNE associe plusieurs tags à chaque build stable. Deux d’entre eux sont importants ici : stable, qui est réaffecté à chaque release, et stable- suivi du hash court du commit Git, qui ne l’est pas. Si vous utilisez stable, un docker compose pull exécuté six mois plus tard récupère une autre image et applique 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. Copiez la chaîne complète dans la ligne image: de affine et de affine_migration. Ces deux valeurs doivent toujours être identiques, car il s’agit de la même image utilisée dans deux rôles différents. Une différence signifie que la base de données est migrée vers un schéma, puis utilisée avec un autre. La mise à niveau devient ainsi 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 flux ne demande ni code d’invitation ni jeton d’installation. La première personne qui charge cette page devient l’administrateur de votre serveur. Le port doit donc rester fermé jusqu’à la création de votre compte.

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

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. Créez votre compte, connectez-vous, puis fermez le tunnel. Vous pouvez seulement ensuite rendre l’instance accessible via un nom public.

Emplacement des données d’AFFiNE

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 : documents, utilisateurs, espaces de travail et permissions.
  • ./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.

Le projet utilise ici des bind mounts plutôt que des volumes nommés, et ce choix est délibéré : vous pouvez archiver et copier ces chemins avec des commandes classiques, sans demander à Docker où il les a placé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 le sont pas de la même manière. La base de données est un serveur actif. Copier ses fichiers pendant qu’elle fonctionne produit une copie corrompue. Utilisez plutôt 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. Le mot de passe n’est donc pas demandé. Vérifiez la taille dans la sortie de ls. Un fichier de quelques centaines d’octets signifie que le dump a échoué, même si le shell a créé le fichier. C’est souvent cette erreur 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
cp config/config.json backup/config-$(date +%F).json

Conservez config.json manuellement dans votre sauvegarde. En août 2026, la documentation d’AFFiNE indiquait toujours que l’export de la configuration depuis le panneau d’administration n’était pas implémenté. Le fichier présent sur le disque est donc la seule copie de vos paramètres. 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.

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 copient un fichier nommé affine.backup dans le conteneur, puis lancent la restauration depuis ./pg.backup. Il s’agit de deux noms différents. Elles suppriment également un répertoire ./postgres, alors que le fichier compose actuel stocke ses données dans ./data/postgres. Utilisez les chemins que vous avez réellement employés, et non ceux de l’extrait. Voici la séquence correspondant à l’organisation utilisée dans 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, et non de rm. Restaurer par-dessus une base de données dont vous n’avez pas conservé de copie transforme une simple mauvaise commande en perte totale de données. Mettre l’ancien répertoire de côté ne coûte rien. Restaurez également les uploads 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. 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à utilisé

AFFiNE utilise WebSocket, et ce point est obligatoire. La documentation est claire : WebSocket est à la base du système de synchronisation et de collaboration d’AFFiNE. Un proxy qui n’effectue pas la mise à niveau de ces connexions fournit un espace de travail où 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 continuellement 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 votre configuration Traefik, et loadbalancer.server.port désigne le port 3010 du conteneur, jamais un port de l’hôte. Traefik relaie les connexions WebSocket sans configuration supplémentaire. Il n’y a donc rien d’autre à ajouter. L’exécution de plusieurs applications derrière une même instance est expliquée dans un seul Traefik 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;
}

Dans nginx, client_max_body_size vaut 1 MB par défaut. Sans cette ligne, tout envoi dépassant la taille 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 ainsi que les mises à niveau WebSocket.

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

Soyez lucide à ce sujet avant d’y transférer une équipe.

La collaboration en temps réel est disponible. C’est la fonctionnalité sur laquelle portent toutes les recommandations de dimensionnement, car la documentation d’AFFiNE attribue l’utilisation mémoire au système de synchronisation et à la fusion des documents. La modification hors ligne est la raison pour laquelle beaucoup de 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 le comportement hors ligne exact 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 second appareil. Les listes de fonctionnalités ne constituent pas une preuve, y compris celle-ci.

La recherche full-text côté serveur est désactivée dans le fichier compose fourni, où AFFINE_INDEXER_ENABLED=false est défini sur le serveur et sur le job de migration. Pour l’activer, vous devez ajouter un conteneur Manticore Search. Cela ajoute un cinquième service et augmente l’utilisation mémoire. Sur une machine équipée de 2 GB de mémoire, c’est la modification qui vous fait dépasser la limite. La recherche dans le client continue de fonctionner pour l’espace de travail ouvert.

Deux limites doivent être connues avant d’inviter des utilisateurs. Un espace de travail auto-hébergé peut accueillir au maximum 10 utilisateurs. Au-delà, vous devez disposer d’une licence Team d’AFFiNE. La documentation indique que le stockage blob illimité et la taille illimitée des blobs pour les instances auto-hébergées sont prévus, mais pas encore entièrement implémentés, selon la vérification effectuée en August 2026. Ces limites n’ont pas d’importance pour un foyer ou une petite équipe. Elles deviennent importantes si vous prévoyez de faire passer quarante personnes.

Mises à niveau

Lisez d’abord les notes de version, en particulier pour une mise à niveau mineure comme le passage de 0.26 à 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. À noter pour les installations très anciennes : à partir de la version 0.23.0, le nom de l’image est passé de affine-graphql à affine. Un fichier Compose antérieur à cette version doit donc être modifié pour remplacer les lignes d’image avant qu’un pull puisse trouver l’image.

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 le processus s’est arrêté. Sur un fichier Compose modifié manuellement, la cause la plus fréquente est l’utilisation d’une image postgres standard à la place de pgvector/pgvector:pg16. Le schéma AFFiNE déclare en effet l’extension pgvector et crée des tables avec des colonnes vector(1024) que Postgres standard ne peut pas créer.

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. Elle indique que ce besoin passe à 4 GB lorsque les documents dépassent 10,000 mots. La fusion d’un document comportant 10,000 modifications peut atteindre temporairement 1 GB. Sur un serveur doté de 2 GB, c’est ce pic qui provoque l’arrêt, et non la charge au repos : le kernel arrête le processus AFFiNE avec l’out-of-memory killer, puis restart: unless-stopped le redémarre. Les utilisateurs voient donc un rechargement de la page au lieu d’une erreur. Confirmez-le avec docker inspect affine_server --format '{{.State.OOMKilled}}' et sudo dmesg -T | grep -i 'out of memory', puis ajoutez un fichier swap de 2 GB afin que le pic ralentisse le système au lieu de provoquer un arrêt.

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

Trois chemins situés sous votre répertoire Compose contiennent toutes les données : ./data/postgres pour la base de données, ./data/storage pour les fichiers importés et ./config pour config.json. 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, car il est impossible de copier une instance Postgres en cours d’exécution de manière sûre. Archivez ./data/storage avec tar pour les fichiers importés et conservez manuellement une copie de config.json, car l’export de la configuration depuis le panneau d’administration est indiqué comme non encore implémenté en août 2026.

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

Oui, et aucune activation n’est nécessaire. La seule condition concerne votre reverse proxy, car la synchronisation utilise des connexions WebSocket. Avec nginx, cela nécessite 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 n’effectue pas la mise à niveau de ces connexions, l’espace de travail se charge et l’utilisateur peut se connecter normalement, mais les modifications effectuées dans un navigateur n’apparaissent jamais dans un autre.

Puis-je exécuter AFFiNE avec une image Postgres standard ?

Non. Le fichier schema.prisma d’AFFiNE déclare extensions = [pgvector(map: "vector")] et définit quatre tables avec une colonne embedding de type vector(1024). Le job de migration crée ces tables même lorsque les fonctionnalités d’IA sont désactivées. Utilisez pgvector/pgvector:pg16, qui correspond à Postgres 16 avec cette extension compilée. Si vous configurez AFFiNE pour utiliser 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.