AFFiNE mit Docker installieren: All-in-one Notion-Alternative mit Whiteboard und Kanban selbst hosten
AFFiNE vereint Docs, Whiteboard und Kanban in einer einzigen Plattform – lokal deploybar mit voller Datenkontrolle. Diese Anleitung zeigt dir Schritt für Schritt, wie du AFFiNE per Docker Compose mit PostgreSQL und Redis aufsetzte, absicherst und aktuell hältst.

Wer Notion, Miro und Trello in einem einzigen selbst gehosteten Tool zusammenführen will, landet früher oder später bei AFFiNE. Das quelloffene Projekt vereint strukturierte Docs und Datenbanken, ein freies Whiteboard/Canvas und Kanban-Boards in einer einzigen Oberfläche – und lässt sich dank einer local-first-Architektur vollständig auf dem eigenen Server betreiben. Mit über 69.000 GitHub-Stars ist AFFiNE die bekannteste visuelle Notion-Alternative für datenbewusste Teams und KMU, die keine Abhängigkeit von einem Cloud-Anbieter eingehen wollen. Diese Anleitung richtet sich an IT-Admins und ambitionierte Selfhoster, die AFFiNE auf einem beliebigen Linux-Host mit Docker Compose aufsetzen möchten.
Voraussetzungen
- Docker Engine >= 20.10 und Docker Compose Plugin v2 (oder Docker Desktop) – noch nicht installiert? Unsere Grundlagenanleitung Docker und Docker Compose auf Linux installieren führt dich durch den Setup.
- Linux-Host, VM oder NAS mit mindestens 4 CPU-Kernen und 2 GB RAM; weniger führt zu Latenzproblemen bei mehreren gleichzeitigen Nutzern.
- Ausreichend Festplattenplatz für PostgreSQL-Daten, Uploads und Konfiguration (je nach Nutzung mehrere GB).
- openssl zum Erzeugen eines sicheren Datenbankpassworts (auf jedem Linux-System vorhanden).
- Für den Produktionsbetrieb: öffentliche IP-Adresse, Domain und ein Reverse-Proxy mit SSL-Terminierung (z. B. Traefik als Docker-Reverse-Proxy oder Caddy).
- Optional: SMTP-Zugangsdaten für E-Mail-Einladungen und Benachrichtigungen.
Schritt 1: Projektordner anlegen und Verzeichnisstruktur erstellen
Alle Dateien für den AFFiNE-Stack leben in einem gemeinsamen Projektordner. Lege ihn an einem Ort an, der genug Platz hat und den du in deine Backup-Routine einbeziehen kannst – zum Beispiel unter /opt/affine/.
mkdir -p /opt/affine
cd /opt/affine
mkdir -p postgres storage configDie drei Unterverzeichnisse entsprechen den Pflicht-Volumes: postgres/ für die Datenbankdaten, storage/ für Uploads und Blobs, config/ für Konfigurationsdateien. Du kannst die Pfade später in der .env anpassen, solltest sie danach aber niemals mehr ändern, ohne vorher einen vollständigen Backup-und-Restore-Workflow durchzuführen.
Verifizieren: ls -la /opt/affine/ sollte die drei Unterverzeichnisse postgres, storage und config anzeigen.
Schritt 2: .env-Datei mit Secrets und Pfaden anlegen
Die .env-Datei enthält alle sensiblen und umgebungsspezifischen Werte. Das Datenbankpasswort muss zwingend vor dem ersten Start gesetzt sein – nachträgliche Änderungen erfordern einen vollständigen Datenbankdump und eine Neuinitialisierung.
Generiere zunächst ein starkes Passwort:
openssl rand -base64 32Erstelle dann die .env im Projektordner und ersetze DEIN_STARKES_PASSWORT durch die eben generierte Zeichenkette:
# ── AFFiNE Self-Host .env ──────────────────────────────────────────────────
# Datenbankzugang (PFLICHT vor erstem Start – danach nicht mehr ändern!)
DB_USERNAME=affine
DB_PASSWORD=DEIN_STARKES_PASSWORT
DB_DATABASE=affine
# Speicherpfade (absolute oder relative Host-Pfade)
DB_DATA_LOCATION=./postgres
UPLOAD_LOCATION=./storage
CONFIG_LOCATION=./config
# Image-Kanal: stable | beta | canary (für Produktion: stable)
AFFINE_REVISION=stable
# Host-Port für die Web-Oberfläche (Standard: 3010)
PORT=3010
# ── Produktionsbetrieb (Domain / HTTPS) ───────────────────────────────────
# Auskommentiert lassen, solange du nur lokal testest.
# AFFINE_SERVER_HOST=affine.example.com
# AFFINE_SERVER_HTTPS=true
# AFFINE_SERVER_EXTERNAL_URL=https://affine.example.com
# ── E-Mail (optional, für Einladungen) ────────────────────────────────────
# MAILER_HOST=smtp.example.com
# MAILER_PORT=465
# MAILER_USER=noreply@example.com
# MAILER_PASSWORD=SMTP_PASSWORT
# MAILER_SENDER=noreply@example.comSichere die Datei sofort gegen unberechtigten Zugriff:
chmod 600 /opt/affine/.envVerifizieren: grep DB_PASSWORD /opt/affine/.env muss dein gesetztes Passwort zurückgeben (kein leerer Wert). ls -la /opt/affine/.env sollte Berechtigungen -rw------- zeigen.
Schritt 3: compose.yaml anlegen
Der Stack besteht aus vier Services. Der affine_migration-Container führt Datenbankmigrationen aus, bevor der Haupt-Container startet – das ist ein häufig übersehenes Detail in Community-Anleitungen, das ohne den Migrations-Job zu Startfehlern führt. Verwende ausschließlich das aktuelle Image ghcr.io/toeverything/affine; das ältere affine-self-hosted-Image ist veraltet und nutzt noch Port 3000.
name: affine
services:
affine:
image: ghcr.io/toeverything/affine:${AFFINE_REVISION:-stable}
container_name: affine_server
ports:
- '${PORT:-3010}:3010'
depends_on:
redis:
condition: service_healthy
postgres:
condition: service_healthy
affine_migration:
condition: service_completed_successfully
volumes:
- ${UPLOAD_LOCATION}:/root/.affine/storage
- ${CONFIG_LOCATION}:/root/.affine/config
env_file:
- .env
environment:
- REDIS_SERVER_HOST=redis
- DATABASE_URL=postgresql://${DB_USERNAME}:${DB_PASSWORD}@postgres:5432/${DB_DATABASE:-affine}
- AFFINE_INDEXER_ENABLED=false
restart: unless-stopped
affine_migration:
image: ghcr.io/toeverything/affine:${AFFINE_REVISION:-stable}
container_name: affine_migration_job
volumes:
- ${UPLOAD_LOCATION}:/root/.affine/storage
- ${CONFIG_LOCATION}:/root/.affine/config
command: ['sh', '-c', 'node ./scripts/self-host-predeploy.js']
env_file:
- .env
environment:
- REDIS_SERVER_HOST=redis
- DATABASE_URL=postgresql://${DB_USERNAME}:${DB_PASSWORD}@postgres:5432/${DB_DATABASE:-affine}
- AFFINE_INDEXER_ENABLED=false
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
redis:
image: redis
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:
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
environment:
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_DATABASE:-affine}
POSTGRES_INITDB_ARGS: '--data-checksums'
healthcheck:
test: ['CMD', 'pg_isready', '-U', "${DB_USERNAME}", '-d', "${DB_DATABASE:-affine}"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stoppedWichtig: PostgreSQL muss als pgvector/pgvector:pg16 laufen – nicht als Standard-postgres:16 – da AFFiNE die pgvector-Erweiterung für Vektorsuche und KI-Features benötigt.
Eckdaten auf einen Blick
| Parameter | Wert |
|---|---|
| AFFiNE-Image | ghcr.io/toeverything/affine:stable |
| PostgreSQL-Image | pgvector/pgvector:pg16 |
| Redis-Image | redis (latest) |
| Web-UI Port | 3010 (konfigurierbar via PORT) |
| Upload-Volume | UPLOAD_LOCATION → /root/.affine/storage |
| Config-Volume | CONFIG_LOCATION → /root/.affine/config |
| DB-Volume | DB_DATA_LOCATION → /var/lib/postgresql/data |
| Mindest-Ressourcen | 4 CPU-Kerne, 2 GB RAM |
| Lizenz | MIT (Community Edition) |
Verifizieren: docker compose config im Projektordner gibt die zusammengeführte Konfiguration ohne Fehler aus. Kein „variable is not set"-Warning sollte erscheinen.
Schritt 4: Stack starten
Starte alle Container im Hintergrund. Docker Compose lädt zunächst alle Images von der GitHub Container Registry herunter – je nach Verbindung dauert das einige Minuten.
cd /opt/affine
docker compose up -dDer Startablauf ist durch die depends_on-Bedingungen streng geordnet: Zuerst starten Redis und PostgreSQL, deren Healthchecks müssen grün sein. Dann führt affine_migration die Datenbankmigrationen aus und muss erfolgreich abschließen (service_completed_successfully). Erst danach startet der eigentliche affine_server.
Verifizieren:
# Alle Container anzeigen
docker compose ps
# Erwartete Ausgabe (nach 1-2 Minuten):
# NAME STATUS
# affine_server Up (running)
# affine_migration_job Exited (0)
# affine_postgres Up (healthy)
# affine_redis Up (healthy)
# Logs der Haupt-App prüfen
docker compose logs affine --tail=30
# HTTP-Antwort prüfen
curl -I http://localhost:3010Der affine_migration_job mit Exit-Code 0 ist korrekt – er ist ein einmaliger Job, kein dauerhaft laufender Dienst. curl -I http://localhost:3010 sollte HTTP/1.1 200 oder einen 302-Redirect zurückgeben. Falls affine_server nicht startet, liefert docker compose logs affine_migration den Grund.
Schritt 5: Ersteinrichtung im Browser
Rufe http://localhost:3010 (oder die IP deines Servers mit Port 3010) im Browser auf. AFFiNE zeigt beim ersten Aufruf einen Einrichtungsassistenten.
- Der erste registrierte Benutzer erhält automatisch Admin-Rechte – lege daher sofort nach dem ersten Start deinen Admin-Account an.
- Workspace-Name und Einstellungen kannst du im Assistenten frei wählen.
- E-Mail-Einladungen an weitere Teammitglieder funktionieren erst, wenn du die
MAILER_*-Variablen in der.envgesetzt und den Stack neu gestartet hast (docker compose up -d). - Für Einladungs- und Share-Links mit der richtigen externen URL musst du im Produktionsbetrieb
AFFINE_SERVER_EXTERNAL_URLsetzen (siehe Schritt 6).
Verifizieren: Nach dem Anlegen des Admin-Accounts öffnet sich das AFFiNE-Dashboard. Du siehst die drei Hauptmodi: Docs, Edgeless (Whiteboard) und Database (Kanban/Grid). Ein neues Dokument lässt sich anlegen und speichern, ohne Fehlermeldung.
Schritt 6: HTTPS und Produktionsbetrieb (optional, aber empfohlen)
AFFiNE selbst kennt kein eigenes HTTPS – die SSL-Terminierung übernimmt ein vorgelagerter Reverse-Proxy. Wie du Traefik mit automatischem Let's-Encrypt-Zertifikat einrichtest, beschreibt unsere Anleitung Traefik als Docker-Reverse-Proxy mit automatischem HTTPS. Für einen manuellen Nginx-Aufbau hilft Nginx als Reverse Proxy mit TLS manuell einrichten.
Sobald der Reverse-Proxy läuft, ergänze in der .env die folgenden Zeilen (Raute entfernen) und starte den Stack neu:
AFFINE_SERVER_HOST=affine.example.com
AFFINE_SERVER_HTTPS=true
AFFINE_SERVER_EXTERNAL_URL=https://affine.example.com
docker compose up -dOhne AFFINE_SERVER_EXTERNAL_URL erzeugt AFFiNE Einladungs- und Share-Links mit localhost:3010, die für externe Nutzer nicht erreichbar sind. Einige Features wie Passwortänderung per E-Mail erfordern außerdem zwingend eine HTTPS-Umgebung (WSS).
Verifizieren: Rufe https://affine.example.com im Browser auf. Das Schloss-Symbol im Browser zeigt ein gültiges Zertifikat. Erstelle eine Einladung für einen zweiten Benutzer und prüfe, ob der Link in der E-Mail https://affine.example.com/... enthält (nicht localhost).
Schritt 7: Updates und Backup-Routine
AFFiNE befindet sich noch im Pre-1.0-Stadium und wird aktiv weiterentwickelt. Neue Releases bringen automatisch Datenbankmigrationen mit – ein Rollback ist ohne vorheriges Backup nicht möglich.
Update-Workflow
cd /opt/affine
# 1. Backup ZUERST (vor jedem Update!)
docker compose exec postgres pg_dump -U affine affine > affine_backup_$(date +%Y%m%d).sql
cp -a ./storage ./storage_backup_$(date +%Y%m%d)
# 2. Neue Images ziehen
docker compose pull
# 3. Stack neu starten (affine_migration läuft automatisch)
docker compose up -d
# 4. Logs prüfen
docker compose logs affine --tail=20Backup-Strategie
Drei Komponenten müssen regelmäßig gesichert werden:
- PostgreSQL-Datenbank: Via
pg_dump(wie oben) oder als Volume-Backup vonDB_DATA_LOCATION. - Blob-Storage: Gesamter Inhalt von
UPLOAD_LOCATION(hochgeladene Dateien, Bilder). - Konfiguration: Inhalt von
CONFIG_LOCATIONsowie die.env-Datei.
Eine vollständige Backup-Automatisierung mit Rotation und Cloud-Sync beschreibt unsere Anleitung MySQL & PostgreSQL Backup automatisieren mit cron.
Verifizieren: Nach dem Update zeigt docker compose ps alle Services als Up bzw. den Migrations-Job als Exited (0). docker compose logs affine --tail=30 enthält keine Fehler zu Datenbankverbindung oder Migrationen. Die AFFiNE-Web-UI ist erreichbar und alle bestehenden Workspaces und Dokumente sind noch vorhanden.
Troubleshooting / Typische Fehler
- affine_server startet nicht / wartet ewig: Der Migrations-Job ist fehlgeschlagen. Diagnose:
docker compose logs affine_migration. Häufigste Ursache: PostgreSQL war beim ersten Start noch nicht bereit. Lösung:docker compose down && docker compose up -derneut ausführen. Die Healthchecks aufpostgresundredisverhindern das normalerweise – prüfe, ob beide den Statushealthyhaben. - Falsches Image / Port 3000 statt 3010: Du verwendest das veraltete
ghcr.io/toeverything/affine-self-hosted:latest. Ersetze das Image in dercompose.yamldurchghcr.io/toeverything/affine:${AFFINE_REVISION:-stable}und führedocker compose up -d --force-recreateaus. - DB_PASSWORD leer / Datenbankverbindung schlägt fehl: Wenn
DB_PASSWORDin der.envfehlt oder leer ist, schlägt die PostgreSQL-Initialisierung fehl. Bei einer noch nicht initialisierten Datenbank:.envmit Passwort befüllen,docker compose down -v(Achtung: löscht Daten!),docker compose up -d. Bei bereits existierenden Daten ist ein vollständiger Dump, Neuinitialisierung und Restore nötig. - Einladungslinks zeigen localhost:3010:
AFFINE_SERVER_EXTERNAL_URList nicht gesetzt. In.enveintragen unddocker compose up -dausführen. - Fehler „pgvector extension not found": Du verwendest
postgres:16stattpgvector/pgvector:pg16. Images tauschen, bestehende Daten perpg_dumpsichern, DB-Volume löschen, mit korrektem Image neu starten und Daten einspielen. - Langsame Performance bei mehreren Nutzern: Ressourcen unter dem Minimum (4 CPU, 2 GB RAM). Prüfe mit
docker statsauf Memory-Druck oder CPU-Throttling. - Redis-Reconnect-Fehler nach Neustart: Redis hat kein persistiertes Volume – das ist im offiziellen Setup so vorgesehen. Falls du Redis-Persistenz benötigst, füge dem Redis-Service ein benanntes Volume hinzu.
Häufige Fragen
Welchen Tag soll ich für AFFINE_REVISION verwenden?
Für Produktionsbetrieb immer stable (Standard). Dieser Tag zeigt stets auf die neueste stabile Version (aktuell v0.26.3). Mit beta erhältst du frühzeitig neue Features, mit canary die aktuellen Entwicklungs-Builds – Letzteres ist ausschließlich für Entwickler und Tester geeignet, da Breaking Changes möglich sind.
Wie aktualisiere ich AFFiNE auf eine neue Version?
Führe im Projektordner aus: docker compose pull && docker compose up -d. Der affine_migration-Container läuft automatisch mit und führt notwendige Datenbankmigrationen aus. Erstelle zwingend vorher ein Backup von PostgreSQL und dem UPLOAD_LOCATION-Verzeichnis.
Wie richte ich den ersten Admin-Account ein?
Nach dem ersten erfolgreichen Start rufst du http://localhost:3010 auf. AFFiNE zeigt einen Ersteinrichtungsassistenten. Der erste Benutzer, der sich registriert, erhält automatisch Admin-Rechte. Weitere Benutzer kannst du anschließend über das Admin-Panel einladen – vorausgesetzt, die MAILER-Variablen sind konfiguriert.
Kann ich AFFiNE auf ARM64 (Raspberry Pi, Apple Silicon) betreiben?
Die Basis-Images (redis, pgvector/pgvector:pg16) unterstützen arm64. Für das AFFiNE-Image selbst solltest du die ghcr.io-Registry auf Multi-Arch-Support prüfen. Für den Produktionseinsatz auf ARM64 empfiehlt sich ein ausgiebiger Vortest.
Wie sichere ich AFFiNE vollständig?
Drei Komponenten müssen gesichert werden: (1) PostgreSQL-Datenbank via pg_dump oder Volume-Backup von DB_DATA_LOCATION, (2) der gesamte Blob-Storage (UPLOAD_LOCATION), (3) die Konfiguration (CONFIG_LOCATION plus die .env-Datei). Teste die Wiederherstellung regelmäßig in einer separaten Umgebung – ein Backup, dessen Restore nicht geprüft wurde, ist kein echtes Backup.
Fazit
AFFiNE ist eine der überzeugendsten visuellen Notion-Alternativen für Teams, die ihre Daten selbst kontrollieren wollen. Der Setup per Docker Compose ist mit vier Containern etwas umfangreicher als bei simpleren Tools, aber gut durchdacht: Der Migrations-Container sorgt für saubere Datenbankinitialisierung, Healthchecks auf PostgreSQL und Redis verhindern Race-Conditions beim Start. Die wichtigsten Fallstricke – veraltetes Image, fehlendes Datenbankpasswort, falsche EXTERNAL_URL – lassen sich mit dieser Anleitung sicher umschiffen. Da AFFiNE noch pre-1.0 ist, solltest du Updates im Staging testen, bevor du sie im Team ausrollst, und eine lückenlose Backup-Routine etablieren.
Weiterführende Anleitungen und Quellen
- Docker und Docker Compose auf Linux installieren – Grundlage für alle Docker-Deployments
- Traefik als Docker-Reverse-Proxy mit automatischem HTTPS – SSL für AFFiNE und andere Dienste
- MySQL & PostgreSQL Backup automatisieren mit cron – Backup-Automation für den AFFiNE-PostgreSQL-Stack
- Docker Compose absichern: Secrets, Healthchecks, Non-Root – Härtung für den Produktivbetrieb
Quellen: Alle technischen Details dieser Anleitung basieren auf der offiziellen AFFiNE-Dokumentation unter docs.affine.pro sowie dem GitHub-Repository toeverything/AFFiNE. Bei Widersprüchen zwischen Community-Anleitungen und offizieller Dokumentation gilt stets Letztere.