SSD Nodes Learn 🎉 VPS vanaf $5.50/mnd
Gidsen Matt ConnorDoor Matt Connor · Bijgewerkt 2026-08-13

AFFiNE zelf hosten met Docker Compose handleiding

Installeer AFFiNE op uw eigen VPS met Docker Compose. Leer hoe u de vier containers beheert, data opslaat, back-ups maakt en waarom 2 GB RAM de minimale vereiste is voor stabiliteit.

Wat u krijgt bij het zelf hosten van AFFiNE

Het zelf hosten van AFFiNE biedt u een werkruimte in Notion-stijl op een server die u zelf beheert, draaiend als vier containers: de applicatie, een eenmalige migratietaak, Postgres en Redis. Real-time samenwerking is inbegrepen, tot maximaal 10 gebruikers die een zelfgehoste werkruimte standaard krijgt. De installatie bestaat uit één compose-bestand en één JSON-configuratiebestand. Zaken die aandacht vereisen zijn de image-tags, de schijfindeling, het geheugenplafond en de proxy die u ervoor plaatst.

AFFiNE combineert een documenteditor en een oneindig canvas in dezelfde werkruimte, waardoor één pagina kan worden gelezen als document of kan worden uitgezet als whiteboard. Als u nog beslist wat u wilt draaien, lees dan eerst de vergelijking van zelfgehoste Notion-alternatieven. Deze handleiding gaat ervan uit dat de keuze is gemaakt en behandelt het correct draaien van AFFiNE in plaats van het opnieuw te vergelijken.

Alles in deze handleiding is gecontroleerd aan de hand van de AFFiNE self-host documentatie en de gepubliceerde releasebestanden op 8 augustus 2026. De nieuwste stabiele release op die datum was 0.27.3, gepubliceerd op 23 juli 2026.

Wat de vier containers daadwerkelijk doen

affine is de server en de webclient in één image. Deze luistert op poort 3010.

affine_migration is een eenmalige taak die node ./scripts/self-host-predeploy.js uitvoert, de databasemigraties toepast en vervolgens afsluit. De applicatie declareert condition: service_completed_successfully voor die taak, wat betekent dat als een migratie met een status ongelijk aan nul afsluit, affine helemaal niet start. Wanneer de webinterface niet verschijnt, is het logboek van die taak het eerste wat u moet raadplegen.

postgres bevat uw documenten, gebruikers, werkruimten en rechten. De meegeleverde image is pgvector/pgvector:pg16, wat een standaard Postgres 16 is met de gecompileerde pgvector-extensie. pgvector voegt een vector-kolomtype toe aan Postgres, de numerieke vorm die wordt gebruikt om embeddings op te slaan zodat tekst op betekenis kan worden doorzocht.

redis is een harde afhankelijkheid: zowel de server als de migratietaak wachten op de health check van deze service voordat ze starten. Let op wat het meegeleverde compose-bestand niet aan Redis geeft: een volume. Niets daarin overleeft een docker compose down, wat duidelijk aangeeft dat het geen inhoud van u bevat en geen back-up vereist.

Waarom de Postgres-image pgvector is en niet de standaard postgres

Deze vereiste komt voort uit het schema van AFFiNE, niet uit een voorkeur. In schema.prisma declareert de datasource extensions = [pgvector(map: "vector")], en vier tabellen bevatten een embedding-kolom van het type vector(1024). De migratietaak maakt deze tabellen aan, ongeacht of u de AI-functies inschakelt of niet. De extensie moet daarom al in de database aanwezig zijn voordat de migratie kan worden voltooid. Wisselt u naar postgres:16, dan ontbreekt de extensie, kan de migratie deze kolommen niet aanmaken en blijft de server wachten op een taak die is mislukt.

AFFiNE is vanaf versie 0.21 overgestapt op de pgvector-image. Bij een installatie die ouder is dan dat, is het aanpassen van de image-regel niet de volledige upgrade. Lees daarom de upgrade-pagina in de AFFiNE self-host docs voordat u iets ophaalt.

Nog een punt over die tag. pg16 staat voor Postgres 16, en een major-versie van Postgres is geen getal dat u zomaar kunt verhogen. Wijzigt u dit naar pg17 boven op een bestaande datamap, dan weigert Postgres te starten, met een regel zoals The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17 in docker compose logs postgres. Een overstap naar een nieuwe major-versie vereist een dump en een restore naar een nieuwe datamap.

Hoeveel CPU en RAM heeft zelfgehoste AFFiNE nodig

De vereistenpagina van AFFiNE adviseert minimaal 4 CPU-cores en 2 GB RAM, en verhoogt dit naar 4 GB zodra uw documenten de 10.000 woorden overschrijden. Dezelfde pagina licht toe waar dit geheugen voor wordt gebruikt: het synchronisatiesysteem en het samenvoegen van documenten. Eén cijfer is hierbij essentieel om te onthouden: het samenvoegen van een document met 10.000 wijzigingen kan een piekbelasting van 1 GB veroorzaken.

Vergelijk dit met een 2 GB-server waarop twee personen tegelijkertijd schrijven. Gemiddeld is dit geen probleem. Postgres en het Node-proces blijven onder de limiet met voldoende marge. De piekbelasting vormt echter het probleem. Een enkele grote samenvoeging kan 1 GB extra geheugen vragen bovenop het reeds actieve gebruik. Op een server met 2 GB RAM en zonder swap zal de out-of-memory (OOM) killer van de kernel dit verzoek beantwoorden door het grootste proces te beëindigen: de AFFiNE-server.

Uw collega ziet geen foutmelding. Zij zien de pagina verversen, omdat restart: unless-stopped de container binnen enkele seconden weer opstart. Ga niet af op aannames, maar controleer het:

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

true uit het eerste commando, of een Killed process-regel die node noemt uit het tweede, betekent dat het geheugen op was in plaats van dat er sprake is van een bug. Los dit aan beide kanten op. Voeg eerst swap toe, zodat een piekbelasting vertraagt in plaats van fataal wordt:

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 zou nu een totaal van 2.0Gi swap moeten rapporteren. Swap maakt AFFiNE niet sneller, en dat is ook niet de bedoeling. Het verandert een piekbelasting van één seconde in een trage seconde in plaats van een gecrashte container. De andere kant van de oplossing is voorkomen dat Postgres zijn cache uitbreidt naar de ruimte die de applicatie nodig heeft tijdens het samenvoegen; dit is waar geheugenlimieten voor een Compose-service voor dienen.

Opslag is veel eenvoudiger te voorspellen. Dit zijn de cijfers die AFFiNE op dezelfde pagina publiceert:

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
  }
]

De serverinstallatie neemt 1.5 GB in beslag. Duizend documenten van elk ongeveer duizend woorden voegen 0.1 GB aan Postgres-data toe, wat verwaarloosbaar is. Duizend geüploade bestanden voegen 10 GB toe, wat de voornaamste factor is. Dit zijn gepubliceerde richtlijnen voor planning en geen metingen van een actieve instantie; beschouw ze daarom als een indicatie en niet als een garantie. De verhouding is wat telt: uw database blijft klein en uw uploads bepalen uw schijfgebruik.

Schrijf het compose-bestand zelf, met vastgezette tags

De gedocumenteerde installatie downloadt een kant-en-klaar bestand met curl -L -o docker-compose.yml https://github.com/toeverything/AFFiNE/releases/latest/download/docker-compose.yml. Dat werkt. Eén detail is het vermelden waard voordat u hierop vertrouwt: per 8 augustus 2026 leest het bestand dat bij release 0.27.3 hoort nog steeds zijn paden uit een .env-bestand, gebruikmakend van ${UPLOAD_LOCATION}, ${CONFIG_LOCATION} en ${DB_DATA_LOCATION}, terwijl de referentiepagina van de documentatie een nieuwere indeling toont die alles onder ./data houdt en helemaal geen .env nodig heeft. Beide zijn legitiem. Het zelf schrijven van het bestand lost de onduidelijkheid op, en u moet het toch bewerken om de images vast te zetten en een databasewachtwoord in te stellen.

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

Compose leest .env automatisch vanuit de projectmap en vervangt ${DB_PASSWORD} voor u, zodat het wachtwoord nooit verschijnt in het bestand dat u in een support-thread zou plakken. Die gewoonte is het waard om toe te passen op elke stack die u beheert, en de redengeving staat in geheimen buiten het compose-bestand houden.

Schrijf nu ~/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

Er zijn vier verschillen met het bestand dat upstream wordt geleverd, en elk heeft een reden.

  • 127.0.0.1:3010:3010 publiceert de poort alleen op het loopback-adres, zodat niets buiten de server AFFiNE kan bereiken totdat u beslist hoe. De upstream '3010:3010' bindt aan elke interface, en op de meeste VPS-images omvat dat ook de publieke interface.
  • POSTGRES_HOST_AUTH_METHOD: trust is verwijderd en in plaats daarvan is een wachtwoord ingesteld. Trust-authenticatie accepteert elke verbinding met die database als de affine-gebruiker zonder wachtwoord. Dit is beperkt tot het private Compose-netwerk, wat prima is totdat de dag aanbreekt dat u nog een container aan dat netwerk koppelt of poort 5432 publiceert tijdens het debuggen.
  • redis:8-alpine vervangt een kale redis, die resolve naar latest. Per augustus 2026 is dat Redis 8, dus de pin behoudt de major-versie die u heeft getest en voorkomt dat een toekomstige Redis 9 arriveert tijdens een niet-gerelateerde docker compose pull.
  • pgvector/pgvector:pg16 blijft exact zoals upstream het instelt, om de hierboven genoemde reden.

POSTGRES_PASSWORD wordt alleen gelezen wanneer Postgres voor de eerste keer zijn datamap aanmaakt. Op een instantie die al bestaat, stelt u het wachtwoord in met docker compose exec postgres psql -U affine -c "ALTER USER affine WITH PASSWORD 'yourpassword'" en werkt u vervolgens DATABASE_URL bij om dit te matchen.

Configuratie bevindt zich in config/config.json

AFFiNE leest de instellingen uit config/config.json, de map die u heeft gekoppeld op /root/.affine/config. Er wordt niet automatisch een bestand voor u aangemaakt, dus schrijf dit bestand vóór de eerste start. Open ~/affine/config/config.json in een teksteditor en voeg de volgende inhoud toe, waarbij u het voorbeeld vervangt door uw eigen domein:

{
  "$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 moet het adres zijn dat uw gebruikers daadwerkelijk in een browser openen. AFFiNE genereert deellinks en uitnodigingen voor werkruimtes op basis van deze waarde. Als u dit op http://localhost:3010 laat staan, verwijst een uitnodiging die u verstuurt de ontvanger naar hun eigen machine, waar de link niet zal werken. Stel dit in op het publieke HTTPS-adres vóór de eerste start, zodat het bestand en het beheerderspaneel altijd met elkaar overeenstemmen.

copilot beheert de AI-functies. copilot.byok.enabled is de schakelaar voor het gebruik van een eigen sleutel, waarmee een eigenaar van een werkruimte een eigen sleutel van een modelprovider in de instellingen van de werkruimte kan plakken. Bij het zelf hosten van AFFiNE is geen AI-abonnement inbegrepen. Laat beide op false staan als u dit niet wilt gebruiken.

Start de stack:

docker compose up -d
docker compose ps

docker compose ps hoort affine_postgres en affine_redis als healthy weer te geven, affine_server als running, en affine_migration_job met de status exited (0). Elke andere exitcode bij de migratietaak is het punt dat u moet onderzoeken; het logboek vermeldt de stap waar het proces is gestopt:

docker compose logs affine_migration

Pin de image voordat u het vergeet

stable is een bewegende tag. De release-workflow van AFFiNE wijst meerdere tags toe aan elke stabiele build, en twee daarvan zijn hier van belang: stable, die bij elke release wordt verplaatst, en stable- gevolgd door de git short hash, die dat niet wordt. Als u stable gebruikt, haalt een docker compose pull over zes maanden een andere image op en voert deze migraties uit op uw database op een moment dat u niet heeft gekozen. Pin de exacte image die u heeft getest:

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

Dit drukt een regel af zoals ghcr.io/toeverything/affine@sha256: gevolgd door een lange hash. Plak de volledige string in de image:-regel van beide affine en affine_migration. Deze twee moeten altijd overeenkomen, omdat het dezelfde image is die twee rollen vervult; een mismatch betekent dat de database naar het ene schema wordt gemigreerd terwijl deze met een ander wordt geserveerd. Upgraden is dan een bewuste wijziging in plaats van een verrassing: wijzig de digest, maak een back-up, docker compose pull, docker compose up -d.

Maak het beheerdersaccount aan voordat iemand anders dat doet

Open /admin op een nieuwe instantie en AFFiNE stuurt u door naar een pagina voor het aanmaken van een account, omdat de server nog geen beheerder heeft. Er is in die procedure geen uitnodigingscode of setup-token vereist. De eerste persoon die die pagina laadt, wordt de beheerder van uw server. De poort moet daarom gesloten blijven totdat u zich heeft geregistreerd.

Daarom is het compose-bestand hierboven gekoppeld aan 127.0.0.1. Benader dit via een SSH-tunnel vanaf uw eigen machine:

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

Laat dit proces draaien en open http://127.0.0.1:3010/admin in uw lokale browser. Registreer u en log in, sluit daarna de tunnel. Pas nu is het veilig om de instantie via een publieke naam bereikbaar te maken.

Waar AFFiNE uw data opslaat

Drie paden bevatten alle gegevens en deze bevinden zich in de map die u heeft aangemaakt.

  • ./data/postgres is de Postgres-datamap: documenten, gebruikers, workspaces en permissies.
  • ./data/storage is gekoppeld aan /root/.affine/storage in de container en bevat elk geüpload bestand.
  • ./config is gekoppeld aan /root/.affine/config en bevat config.json.

Upstream gebruikt hier bind mounts in plaats van named volumes, en die keuze is bewust: u kunt deze paden met standaardcommando's archiveren en kopiëren, zonder aan Docker te hoeven vragen waar deze zijn opgeslagen. Het nadeel is dat het eigenaarschap van bestanden op de host nu uw verantwoordelijkheid is; dit is de afweging die wordt besproken in bind mounts en named volumes.

Hoe u AFFiNE back-upt

Er moeten twee onderdelen worden geback-upt, en de werkwijze verschilt per onderdeel. De database is een actieve server; het kopiëren van de bestanden terwijl deze draait, resulteert in een corrupt bestand. Maak in plaats daarvan een 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/

De dump wordt uitgevoerd binnen de container via de lokale socket, waardoor er niet om een wachtwoord wordt gevraagd. Controleer de grootte in de output van ls. Een bestand van enkele honderden bytes betekent dat de dump is mislukt terwijl de shell het bestand toch heeft aangemaakt; dit is de fout die men pas na zes maanden ontdekt. De -T is ook van belang: zonder deze vlag kan Compose een terminal toewijzen en de binaire stream corrumperen.

Geüploade bestanden zijn gewone bestanden, dus archiveer deze met tar:

tar czf backup/storage-$(date +%F).tgz -C data storage
cp config/config.json backup/config-$(date +%F).json

Bewaar config.json handmatig in uw back-up. De documentatie van AFFiNE vermeldt dat het exporteren van configuraties via het admin-paneel nog niet is geïmplementeerd (gecontroleerd in augustus 2026), dus het bestand op de schijf is de enige kopie van uw instellingen. Kopieer alle drie de bestanden van de server af. Een back-up op dezelfde schijf als de data die het moet beschermen, is geen back-up.

Herstellen, en een valkuil in de gepubliceerde stappen

Lees de officiële herstelstappen voordat u ze nodig heeft, en lees ze nauwkeurig. Zoals gepubliceerd in augustus 2026 kopiëren ze een bestand genaamd affine.backup naar de container en herstellen ze vervolgens vanaf ./pg.backup; dit zijn twee verschillende namen. Bovendien verwijderen ze een ./postgres-directory, terwijl het huidige compose-bestand de data in ./data/postgres bewaart. Volg de paden die u daadwerkelijk heeft gebruikt in plaats van de paden in het fragment. Hier is de reeks voor de indeling in deze handleiding:

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

Let op de mv in plaats van een rm. Herstellen over een database waarvan u geen kopie heeft bewaard, is de manier waarop één foutief commando leidt tot totaal dataverlies; het verplaatsen van de oude directory kost niets. Herstel ook de uploads met tar xzf backup/storage-2026-08-08.tgz -C data, anders worden alle documenten weergegeven met kapotte bijlagen. Log daarna in en open een document dat een afbeelding bevat. Dat is de test. Een back-up die u niet in een browser heeft geopend, is slechts een bestand, geen back-up.

AFFiNE achter een bestaande proxy plaatsen

AFFiNE maakt gebruik van WebSocket, en dit is niet optioneel. De documentatie is hier duidelijk over: WebSocket vormt de basis voor het synchronisatie- en samenwerkingssysteem van AFFiNE. Een proxy die deze verbindingen niet upgradet, resulteert in een werkruimte waarbij bewerkingen stilletjes stoppen met synchroniseren. De pagina laadt, inloggen werkt, maar een bewerking in de ene browser bereikt de andere nooit. Open in de ontwikkelaarstools van uw browser het tabblad Network en filter op WS. Een verbinding die herhaaldelijk opent en sluit, duidt op een proxy die de upgrade niet doorlaat.

Als u al Traefik gebruikt voor andere containers, voegt u AFFiNE toe als een normale service. Verwijder het ports:-blok uit de affine-service en voeg vervolgens het volgende toe:

    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'

En onderaan het bestand, naast services::

networks:
  proxy:
    external: true

De naam van de certificaat-resolver moet overeenkomen met de naam die in uw Traefik-configuratie is gedefinieerd, en loadbalancer.server.port is de containerpoort 3010, nooit een hostpoort. Traefik proxiet WebSocket-verbindingen zonder extra configuratie, dus er hoeft niets meer te worden toegevoegd. Als de rest van uw stack al achter Authentik voor single sign-on staat, zal een forward auth-middleware op deze router de browsertoegang tot AFFiNE afschermen. Schakel dit echter pas in nadat u de desktop-app heeft getest; deze beschikt niet over een browsersessie en zal simpelweg niet synchroniseren. Het draaien van meerdere applicaties achter één instantie wordt behandeld in één Traefik voor meerdere applicaties.

Bij nginx moet u expliciet om de upgrade vragen:

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 staat in nginx standaard op 1 MB. Zonder deze regel mislukt elke upload die groter is dan een kleine foto met een 413-status. Er verschijnt niets in de AFFiNE-logs, omdat het verzoek nooit is aangekomen. Caddy heeft slechts één regel nodig, reverse_proxy http://127.0.0.1:3010, en regelt certificaten en WebSocket-upgrades zelf.

Wat de self-hosted build niet bevat

Wees eerlijk tegen uzelf voordat u een team laat overstappen.

Real-time samenwerking is aanwezig en dit is de functie waar alle adviezen over de benodigde capaciteit op gebaseerd zijn, aangezien de documentatie van AFFiNE het geheugengebruik toeschrijft aan het synchronisatiesysteem en het samenvoegen van documenten. Offline bewerken is de reden waarom veel mensen een local-first tool willen, en de desktopapplicatie kan uw self-hosted server toevoegen aan de werkruimtelijst en inloggen. Test het exacte offline gedrag waar uw team afhankelijk van is voordat u beslist: bewerk in de desktop-app zonder netwerkverbinding, maak opnieuw verbinding en controleer het resultaat op een tweede apparaat. Functielijsten zijn geen bewijs, en dat geldt ook voor deze lijst.

Full-text search aan de serverzijde staat uit in het meegeleverde compose-bestand, waarbij AFFINE_INDEXER_ENABLED=false is ingesteld op de server en op de migratietaak. Het inschakelen hiervan betekent het toevoegen van een Manticore Search-container, wat een vijfde service en meer geheugen vereist. Op een 2 GB-server is dit de wijziging die zorgt voor overbelasting. Zoeken binnen de client werkt nog steeds voor de werkruimte die u open heeft staan.

Twee limieten zijn het vermelden waard voordat u mensen uitnodigt. Een self-hosted werkruimte is beperkt tot maximaal 10 gebruikers; voor meer gebruikers is een Team-licentie van AFFiNE vereist. Onbeperkte blob-opslag en onbeperkte blob-grootte voor self-hosted instanties worden in de documentatie beschreven als beoogd, maar zijn nog niet volledig geïmplementeerd, gecontroleerd in augustus 2026. Voor een huishouden of een klein team is dit niet van belang. Voor een organisatie van veertig personen is dit wel relevant.

Upgrades

Lees eerst de release notes, zeker bij een minor-versie-update zoals van 0.26 naar 0.27, aangezien hierin vaak ingrijpende wijzigingen staan. Maak een back-up van de database en de opslagmap voordat u actie onderneemt; het migratieproces wijzigt namelijk het schema bij de eerstvolgende start en dit kan niet ongedaan worden gemaakt. Wijzig vervolgens de vastgezette digest, voer docker compose pull uit gevolgd door docker compose up -d, en monitor docker compose logs -f affine_migration totdat het proces correct is afgesloten. docker image prune verwijdert daarna de oude lagen. Een historische opmerking voor gebruikers met een zeer oude installatie: vanaf versie 0.23.0 is de naam van de image gewijzigd van affine-graphql naar affine. Een compose-bestand dat ouder is dan die versie moet daarom worden aangepast voordat een pull de juiste bestanden kan vinden.

FAQ

Waarom start de AFFiNE-container nooit?

De affine-service declareert condition: service_completed_successfully als afhankelijkheid van de affine_migration-taak. Als de migratie met een andere status dan 0 afsluit, start de server nooit en verschijnt er geen webinterface. Voer docker compose logs affine_migration uit om te zien welke stap is gestopt. De meest voorkomende oorzaak bij een handmatig bewerkt compose-bestand is het gebruik van een standaard postgres-image in plaats van pgvector/pgvector:pg16. Het AFFiNE-schema declareert namelijk de pgvector-extensie en maakt tabellen met vector(1024)-kolommen die een standaard Postgres-installatie niet kan aanmaken.

Hoeveel RAM heeft zelfgehoste AFFiNE nodig?

De vereistenpagina van AFFiNE vraagt om minimaal 4 CPU-cores en 2 GB RAM. Dit loopt op naar 4 GB wanneer documenten de 10.000 woorden passeren. Er wordt opgemerkt dat het samenvoegen van een document met 10.000 wijzigingen een piek van 1 GB kan veroorzaken. Op een server met 2 GB RAM is die piek de boosdoener, niet de idle-belasting: de kernel out-of-memory killer stopt het AFFiNE-proces en restart: unless-stopped start het opnieuw, waardoor gebruikers een paginaverversing zien in plaats van een foutmelding. Bevestig dit met docker inspect affine_server --format '{{.State.OOMKilled}}' en sudo dmesg -T | grep -i 'out of memory' en voeg vervolgens een swap-bestand van 2 GB toe, zodat een piek traag verloopt in plaats van fataal.

Waar slaat AFFiNE mijn gegevens op en wat moet ik back-uppen?

Drie paden onder uw compose-directory bevatten alles: ./data/postgres voor de database, ./data/storage voor geüploade bestanden en ./config voor config.json. Maak een back-up van de database met docker compose exec -T postgres pg_dump --format c --username affine affine > affine.dump in plaats van de bestanden te kopiëren, omdat een actieve Postgres-database niet veilig kan worden gekopieerd. Gebruik tar voor ./data/storage voor de uploads en bewaar een kopie van config.json handmatig, aangezien het exporteren van de configuratie vanuit het beheerderspaneel per augustus 2026 nog niet is geïmplementeerd.

Werkt real-time samenwerking op een zelfgehoste AFFiNE?

Ja, en hiervoor hoeft niets te worden ingeschakeld. De enige vereiste is uw reverse proxy, omdat synchronisatie verloopt via WebSocket-verbindingen. Bij nginx betekent dit proxy_http_version 1.1 plus de Upgrade- en Connection: upgrade-headers, terwijl Traefik en Caddy deze verbindingen zonder extra configuratie doorlaten. Het symptoom van een proxy die deze niet upgradet, is een werkruimte die normaal laadt en inlogt, terwijl bewerkingen in de ene browser nooit in de andere verschijnen.

Kan ik AFFiNE draaien met een standaard Postgres-image?

Nee. De schema.prisma van AFFiNE declareert extensions = [pgvector(map: "vector")] en definieert vier tabellen met een embedding-kolom van het type vector(1024). De migratietaak maakt deze tabellen aan, zelfs als de AI-functies zijn uitgeschakeld. Gebruik pgvector/pgvector:pg16, wat Postgres 16 is met die extensie ingebouwd. Als u AFFiNE naar een externe Postgres-server verwijst, installeer dan pgvector op die server en maak de extensie aan in de doeldatabase voordat u de migratie uitvoert.