Self-host AFFiNE con Docker Compose: guida completa
Configura AFFiNE su un VPS con Docker Compose: 4 container, tag delle immagini, dati persistenti, backup e limiti reali di un server con 2 GB di RAM.
Cosa si ottiene con il self-hosting di AFFiNE
Il self-hosting di AFFiNE offre un workspace in stile Notion su un server sotto il tuo controllo. L'installazione usa quattro container: l'applicazione, un job di migrazione eseguito una sola volta, Postgres e Redis. La collaborazione in tempo reale è inclusa, fino ai 10 utenti consentiti per impostazione predefinita da un workspace self-hosted. L'installazione richiede un file Compose e un file di configurazione JSON. Gli aspetti da valutare sono i tag delle immagini, la struttura del disco, il limite di memoria e il proxy da configurare davanti all'applicazione.
AFFiNE integra un editor di documenti e un canvas infinito nello stesso workspace. Una pagina può quindi essere letta come documento oppure sviluppata come lavagna. Se devi ancora scegliere cosa eseguire, leggi prima il confronto tra le alternative self-hosted a Notion. Questa guida presuppone che la scelta sia già stata fatta e descrive come eseguire AFFiNE correttamente, senza ripetere il confronto.
Le informazioni riportate sono state verificate sulla documentazione ufficiale per il self-hosting di AFFiNE e sui file delle release pubblicati l'8 agosto 2026. La versione stabile più recente a quella data era la 0.27.3, pubblicata il 23 luglio 2026.
Cosa fanno realmente i quattro container
affine contiene il server e il client web nella stessa immagine. È in ascolto sulla porta 3010.
affine_migration è un job eseguito una sola volta. Esegue node ./scripts/self-host-predeploy.js, applica le migrazioni del database e termina. L'applicazione dichiara condition: service_completed_successfully come dipendenza da questo job. Se una migrazione termina con uno stato diverso da zero, affine non viene mai avviato. Se l'interfaccia web non si avvia, il log di questo job è il primo elemento da controllare.
postgres contiene documenti, utenti, workspace e autorizzazioni. L'immagine fornita è pgvector/pgvector:pg16, ovvero Postgres 16 standard con l'estensione pgvector compilata al suo interno. pgvector aggiunge a Postgres un tipo di colonna vector. Si tratta del formato numerico usato per memorizzare gli embedding, in modo da poter cercare il testo in base al significato.
redis è una dipendenza obbligatoria: sia il server sia il job di migrazione attendono il relativo health check prima di avviarsi. Notare che il file compose fornito non assegna a Redis alcun volume. Nulla al suo interno sopravvive a un docker compose down. Questo indica chiaramente che Redis non contiene dati personali e non richiede backup.
Perché l'immagine Postgres è pgvector e non postgres standard
Il requisito deriva dallo schema di AFFiNE, non da una preferenza. In schema.prisma il datasource dichiara extensions = [pgvector(map: "vector")] e quattro tabelle contengono una colonna embedding tipizzata come vector(1024). Il job di migrazione crea queste tabelle indipendentemente dall'attivazione delle funzionalità AI. L'estensione deve quindi essere già presente nel database prima che la migrazione possa terminare. Se sostituisci l'immagine con postgres:16, l'estensione non è più disponibile, la migrazione non può creare queste colonne e il server resta in attesa di un job terminato con errore.
AFFiNE è passato all'immagine pgvector alla versione 0.21. In un'installazione precedente, modificare la riga dell'immagine non è sufficiente per completare l'upgrade. Prima di eseguire il pull, consulta quindi la pagina sull'upgrade nella documentazione self-host di AFFiNE.
Un'ultima precisazione sul tag. pg16 indica Postgres 16 e la versione major di Postgres non è un numero che puoi incrementare direttamente. Se lo sostituisci con pg17 usando una directory dati esistente, Postgres rifiuta di avviarsi e registra una riga simile a The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17 in docker compose logs postgres. Il passaggio a una nuova versione major richiede un dump e un restore in una directory dati nuova.
Quanta CPU e RAM richiede AFFiNE in self-hosting
La pagina dei requisiti di AFFiNE richiede almeno 4 core CPU e 2 GB di RAM e porta la memoria a 4 GB quando i documenti superano 10.000 parole. La stessa pagina indica dove viene usata la memoria: il sistema di sincronizzazione e l'unione dei documenti. Riporta anche un dato da ricordare: l'unione di un documento con 10.000 modifiche può raggiungere un picco di 1 GB.
Consideriamo ora questo dato nel caso di un piano da 2 GB usato da due persone per la scrittura. Il consumo medio è normale. Postgres e il processo Node restano sotto il limite, con memoria disponibile. Il problema è il picco. Una singola operazione di unione di grandi dimensioni può richiedere 1 GB in aggiunta alla memoria già occupata. Su un server da 2 GB senza swap, l'out-of-memory (OOM) killer del kernel soddisfa la richiesta terminando il processo più grande, cioè il server AFFiNE.
Il collega non vede un errore. Vede il ricaricamento della pagina, perché restart: unless-stopped riporta il container in esecuzione entro pochi secondi. Non fare supposizioni: verifica:
docker inspect affine_server --format '{{.State.OOMKilled}} {{.RestartCount}}'
sudo dmesg -T | grep -i -E 'out of memory|killed process'true dal primo comando, oppure una riga Killed process che indica node dal secondo comando, significa che la memoria è esaurita e non che hai trovato un bug. Risolvi il problema da entrambi i lati. Aggiungi prima lo swap, così un picco diventa lento invece di causare un arresto:
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 -hfree -h dovrebbe ora riportare un totale di swap pari a 2.0Gi. Lo swap non rende AFFiNE veloce e non è questo il suo scopo. Trasforma un picco di un secondo in un rallentamento, invece di lasciare il container terminato. L'altro lato della soluzione consiste nell'impedire a Postgres di espandere la cache nello spazio richiesto dall'applicazione durante l'unione. A questo servono i limiti di memoria su un servizio Compose.
Lo spazio di archiviazione è molto più facile da prevedere. Questi sono i dati pubblicati da AFFiNE nella stessa pagina:
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'installazione del server occupa 1.5 GB. Mille documenti di circa mille parole ciascuno aggiungono 0.1 GB di dati Postgres, una quantità quasi trascurabile. Mille file caricati aggiungono 10 GB, e questo rappresenta l'intero fabbisogno principale. Si tratta di valori di pianificazione pubblicati, non di misurazioni ottenute da un'istanza in esecuzione. Considerali quindi come un'indicazione dell'ordine di grandezza, non come una garanzia. È proprio l'ordine di grandezza a essere importante: il database rimane piccolo, mentre sono i file caricati a determinare lo spazio su disco.
Scrivi tu il file Compose, bloccando i tag
L'installazione documentata scarica un file già pronto con curl -L -o docker-compose.yml https://github.com/toeverything/AFFiNE/releases/latest/download/docker-compose.yml. Funziona. Prima di farvi affidamento, però, è utile conoscere un dettaglio: all'8 agosto 2026 il file allegato alla release 0.27.3 legge ancora i percorsi da un file .env, usando ${UPLOAD_LOCATION}, ${CONFIG_LOCATION} e ${DB_DATA_LOCATION}, mentre la pagina di riferimento della documentazione mostra una struttura più recente che mantiene tutto sotto ./data e non richiede affatto .env. Entrambe le versioni sono corrette. Scrivere il file manualmente elimina il dubbio e, in ogni caso, dovete modificarlo per bloccare le immagini e impostare una password per il database.
mkdir -p ~/affine/config ~/affine/data
cd ~/affine
printf 'DB_PASSWORD=%s\n' "$(openssl rand -hex 24)" > .env
chmod 600 .envCompose legge autonomamente .env dalla directory del progetto e sostituisce ${DB_PASSWORD} al posto vostro, quindi la password non compare nel file che incollereste in una discussione di supporto. È una prassi utile da mantenere per ogni stack che eseguite; il motivo è spiegato in mantenere i secret fuori dal file Compose.
Ora scrivete ~/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-stoppedRispetto al file distribuito dal progetto upstream ci sono quattro differenze, ciascuna motivata da una ragione precisa.
127.0.0.1:3010:3010pubblica la porta solo sull'indirizzo di loopback, quindi nulla all'esterno del server può raggiungere AFFiNE finché non decidete come esporlo. Il file upstream'3010:3010'esegue il bind su tutte le interfacce e, nella maggior parte delle immagini VPS, questo include anche l'interfaccia pubblica.POSTGRES_HOST_AUTH_METHOD: trustè stato rimosso e al suo posto viene impostata una password. L'autenticazione trust accetta senza password qualsiasi connessione al database come utenteaffine. È limitata alla rete Compose privata, il che va bene finché non aggiungete un altro container a quella rete o pubblicate la porta 5432 durante il troubleshooting.redis:8-alpinesostituisce unredisnon vincolato, che risolve inlatest. Ad agosto 2026 si tratta di Redis 8, quindi il vincolo mantiene la versione principale testata e impedisce che Redis 9 venga installato durante undocker compose pullnon correlato.pgvector/pgvector:pg16resta esattamente come impostato da upstream, per il motivo indicato sopra.
POSTGRES_PASSWORD viene letto solo quando Postgres crea per la prima volta la propria directory dei dati. Su un'istanza già esistente, impostate la password con docker compose exec postgres psql -U affine -c "ALTER USER affine WITH PASSWORD 'yourpassword'" e quindi aggiornate DATABASE_URL in modo coerente.
La configurazione si trova in config/config.json
AFFiNE legge le proprie impostazioni da config/config.json, ovvero la directory montata in /root/.affine/config. Nessun componente crea questo file automaticamente, quindi è necessario scriverlo prima del primo avvio. Aprire ~/affine/config/config.json in un editor e inserire questo contenuto, sostituendo il dominio di esempio con il proprio:
{
"$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 deve essere l'indirizzo che gli utenti aprono effettivamente nel browser. AFFiNE usa questo valore per creare i link di condivisione e gli inviti ai workspace. Se viene lasciato su http://localhost:3010, un invito inviato indirizza il destinatario al proprio computer e lì non funziona. Impostarlo sull'indirizzo HTTPS pubblico prima del primo avvio, in modo che il file e il pannello di amministrazione riportino sempre lo stesso valore.
copilot controlla le funzionalità di AI. copilot.byok.enabled abilita l'uso di una chiave propria e consente al proprietario di un workspace di inserire la chiave del proprio provider di modelli nelle impostazioni del workspace. Il self-hosting di AFFiNE non include un abbonamento AI. Lasciare entrambi i valori su false se non si desidera usare queste funzionalità.
Avviare lo stack:
docker compose up -d
docker compose psdocker compose ps dovrebbe elencare affine_postgres e affine_redis come integri, affine_server come in esecuzione e affine_migration_job con lo stato exited (0). Qualsiasi altro codice di uscita del job di migrazione richiede un controllo: il relativo log indica il passaggio in cui l'esecuzione si è arrestata:
docker compose logs affine_migrationBlocca l'immagine prima di dimenticartene
stable è un tag mobile. Il flusso di rilascio di AFFiNE assegna diversi tag a ogni build stabile. Qui ne interessano due: stable, che viene riassegnato a ogni release, e stable- seguito dall'hash breve di git, che invece non cambia. Se lasci stable, un comando docker compose pull eseguito tra sei mesi scaricherà un'immagine diversa ed eseguirà le relative migrazioni sul database in un momento che non hai scelto. Blocca l'immagine esatta che hai testato:
docker compose pull
docker image inspect ghcr.io/toeverything/affine:stable --format '{{index .RepoDigests 0}}'Il comando stampa una riga simile a ghcr.io/toeverything/affine@sha256:, seguita da un hash lungo. Copia l'intera stringa nella riga image: di entrambi affine e affine_migration. I due valori devono sempre corrispondere, perché rappresentano la stessa immagine usata in due ruoli diversi. Se non corrispondono, il database viene migrato a uno schema mentre il servizio lo utilizza con un altro. L'aggiornamento diventa così un'operazione deliberata, non una sorpresa: modifica il digest, esegui il backup, docker compose pull, docker compose up -d.
Crea l'account amministratore prima che lo faccia qualcun altro
Apri /admin su un'istanza appena creata. AFFiNE ti reindirizza alla pagina di creazione dell'account perché sul server non esiste ancora alcun amministratore. In questo flusso non sono richiesti né un codice di invito né un token di configurazione. La prima persona che apre quella pagina diventa l'amministratore del server. Per questo motivo la porta deve rimanere chiusa finché non hai completato la registrazione.
Per questo il file compose precedente esegue il bind su 127.0.0.1. Raggiungilo tramite un tunnel SSH dalla tua macchina:
ssh -L 3010:127.0.0.1:3010 you@your-server-ipLascia il tunnel attivo e apri http://127.0.0.1:3010/admin nel browser locale. Completa la registrazione ed esegui l'accesso, quindi chiudi il tunnel. Solo a questo punto è sicuro pubblicare l'istanza con un nome pubblico.
Dove AFFiNE conserva i dati
Tre percorsi contengono tutti i dati e si trovano nella directory creata.
./data/postgresè la directory dei dati di Postgres: documenti, utenti, workspace e autorizzazioni../data/storageè montato in/root/.affine/storagenel container e contiene tutti i file caricati../configè montato in/root/.affine/confige contieneconfig.json.
Il progetto upstream usa qui bind mount anziché volumi denominati. La scelta è intenzionale: puoi creare archivi tar e copiare questi percorsi con i normali comandi, senza chiedere a Docker dove li ha memorizzati. Lo svantaggio è che la proprietà dei file sull'host diventa una tua responsabilità. Questo è il compromesso descritto in bind mount e volumi denominati.
Come eseguire il backup di AFFiNE
È necessario eseguire il backup di due elementi, con procedure diverse. Il database è un server attivo, quindi copiare i relativi file mentre è in esecuzione produce una copia corrotta. Eseguire invece 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/Il dump viene eseguito all'interno del container tramite il socket locale, quindi non richiede la password. Controllare la dimensione nell'output di ls. Un file di poche centinaia di byte indica che il dump non è riuscito, anche se la shell ha creato comunque il file. Questo è il problema che spesso viene scoperto sei mesi dopo. Anche -T è importante: senza questa opzione Compose può allocare un terminale e corrompere il flusso binario.
I file caricati sono normali file, quindi archiviarli con tar:
tar czf backup/storage-$(date +%F).tgz -C data storage
cp config/config.json backup/config-$(date +%F).jsonConservare manualmente config.json nel backup. La documentazione di AFFiNE indica ancora come non implementata l'esportazione della configurazione dal pannello di amministrazione; questa informazione è stata verificata ad agosto 2026. Il file sul disco è quindi l'unica copia delle impostazioni. Copiare tutti e tre i file fuori dal server. Un backup conservato sullo stesso disco dei dati che protegge non è un backup.
Ripristino e un problema nei passaggi pubblicati
Leggere i passaggi ufficiali per il ripristino prima di averne bisogno e verificarli con attenzione. Nella versione pubblicata ad agosto 2026, copiano un file denominato affine.backup nel container e poi eseguono il ripristino da ./pg.backup: sono due nomi diversi. Inoltre, rimuovono una directory ./postgres, mentre il file Compose corrente conserva i dati in ./data/postgres. Usare i percorsi effettivamente configurati, non quelli dello snippet. Questa è la sequenza per la struttura usata in questa guida:
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 -dNotare l'uso di mv e non di rm. Eseguire il ripristino sopra un database di cui non è stata conservata una copia è sufficiente per trasformare un comando errato in una perdita completa dei dati; spostare prima la vecchia directory non costa nulla. Ripristinare anche gli upload con tar xzf backup/storage-2026-08-08.tgz -C data, altrimenti ogni documento verrà visualizzato con gli allegati non disponibili. Quindi accedere e aprire un documento che contiene un'immagine. Questo è il test. Un ripristino che non è stato verificato aprendo un documento nel browser è solo un file, non un backup.
Mettere AFFiNE dietro un proxy già in uso
AFFiNE usa WebSocket e non è un'opzione facoltativa. La documentazione è chiara: WebSocket è alla base del sistema di sincronizzazione e collaborazione di AFFiNE. Un proxy che non esegue l'upgrade delle connessioni lascia quindi un workspace in cui le modifiche smettono di essere sincronizzate senza messaggi evidenti. La pagina viene caricata e l'accesso funziona, ma una modifica effettuata in un browser non raggiunge l'altro. Negli strumenti per sviluppatori del browser, apri la scheda Network e filtra per WS. Una connessione che si apre e si chiude ripetutamente indica che il proxy non inoltra l'upgrade.
Se usi già Traefik per altri container, AFFiNE si aggiunge come un normale servizio. Elimina il blocco ports: dal servizio affine, quindi aggiungi:
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'e, in fondo al file, accanto a services::
networks:
proxy:
external: trueIl nome del certificate resolver deve corrispondere a quello definito nella configurazione di Traefik, mentre loadbalancer.server.port è la porta del container 3010, mai una porta dell'host. Traefik esegue il proxy delle connessioni WebSocket senza configurazione aggiuntiva, quindi non devi aggiungere altro. Se il resto dello stack è già protetto da Authentik per il single sign-on, un middleware forward auth su questo router limiterà l'accesso ad AFFiNE tramite browser. Lascialo però disattivato finché non hai testato l'app desktop, che non dispone di una sessione del browser e semplicemente non riuscirà a sincronizzarsi. La gestione di più app dietro una singola istanza è descritta in un singolo Traefik davanti a più app.
Con nginx devi richiedere esplicitamente l'upgrade:
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;
}In nginx client_max_body_size ha un valore predefinito di 1 MB. Senza quella riga, quindi, ogni upload più grande di una foto di piccole dimensioni fallisce con stato 413 e nei log di AFFiNE non compare nulla, perché la richiesta non è mai arrivata. Caddy richiede una sola riga, reverse_proxy http://127.0.0.1:3010, e gestisce autonomamente certificati e upgrade WebSocket.
Cosa non include la build self-hosted
Prima di trasferire un team, valutate con attenzione questo aspetto.
La collaborazione in tempo reale è disponibile ed è la funzione a cui si riferiscono tutte le indicazioni sul dimensionamento, perché la documentazione di AFFiNE attribuisce il consumo di memoria al sistema di sincronizzazione e all'unione dei documenti. La modifica offline è uno dei motivi per cui molte persone scelgono uno strumento local-first, e l'applicazione desktop può aggiungere il server self-hosted all'elenco degli spazi di lavoro ed eseguire l'accesso. Prima di procedere, testate il comportamento offline esatto da cui dipende il vostro team: modificate un documento nell'applicazione desktop con la rete disattivata, riconnettetevi, quindi controllate il risultato su un secondo dispositivo. Gli elenchi delle funzionalità non sono una prova, incluso questo.
La ricerca full-text lato server è disattivata nel file compose fornito, dove AFFINE_INDEXER_ENABLED=false è impostato sul server e sul job di migrazione. Per abilitarla è necessario aggiungere un container Manticore Search, che porta il numero di servizi a cinque e aumenta il consumo di memoria. Su una macchina con 2 GB, questa è la modifica che fa superare il limite. La ricerca all'interno del client continua a funzionare nello spazio di lavoro aperto.
Prima di invitare altre persone, è utile conoscere 2 limiti. A uno spazio di lavoro self-hosted possono essere assegnati al massimo 10 utenti; per superare questo limite serve una licenza Team di AFFiNE. La documentazione descrive lo storage blob illimitato e le dimensioni illimitate dei blob per le istanze self-hosted come funzionalità previste ma non ancora completamente implementate, secondo la verifica effettuata ad agosto 2026. Nessuno dei 2 limiti è rilevante per una famiglia o un piccolo team. Entrambi lo diventano se prevedete di trasferire 40 persone.
Aggiornamenti
Leggere prima le note di rilascio, soprattutto per un aggiornamento di versione minore come quello dalla 0.26 alla 0.27, perché può introdurre modifiche incompatibili. Eseguire il backup del database e della directory di storage prima di procedere, perché il job di migrazione modifica lo schema al successivo avvio e non è possibile annullare l’operazione. Quindi modificare il digest fissato, eseguire docker compose pull seguito da docker compose up -d e monitorare docker compose logs -f affine_migration finché non termina correttamente. docker image prune rimuove in seguito i layer precedenti. Una nota storica per chi usa un’installazione molto vecchia: dalla versione 0.23.0 il nome dell’immagine è cambiato da affine-graphql a affine, quindi in un file compose precedente è necessario riscrivere le righe image prima che un pull possa trovare l’immagine.
FAQ
Perché il container AFFiNE non si avvia mai?
Il servizio affine dichiara condition: service_completed_successfully nel job affine_migration, quindi, se la migrazione termina con uno stato diverso da 0, il server non viene mai avviato e l'interfaccia web non compare. Esegui docker compose logs affine_migration per individuare il passaggio che si è arrestato. La causa più comune in un file compose modificato manualmente è usare un'immagine postgres standard al posto di pgvector/pgvector:pg16, perché lo schema di AFFiNE dichiara l'estensione pgvector e crea tabelle con colonne vector(1024) che un'installazione Postgres semplice non può creare.
Quanta RAM richiede AFFiNE self-hosted?
La pagina dei requisiti di AFFiNE indica almeno 4 core CPU e 2 GB di RAM. Il requisito sale a 4 GB quando i documenti superano 10,000 parole. La pagina segnala inoltre che l'unione di un documento con 10,000 modifiche può raggiungere un picco di 1 GB. Su un server con 2 GB, il problema è questo picco, non il carico a riposo: il kernel OOM killer arresta il processo AFFiNE e restart: unless-stopped lo avvia nuovamente. Gli utenti vedono quindi un ricaricamento della pagina invece di un errore. Conferma la causa con docker inspect affine_server --format '{{.State.OOMKilled}}' e sudo dmesg -T | grep -i 'out of memory', quindi aggiungi un file di swap da 2 GB, così il picco rallenta il sistema invece di causare un arresto.
Dove archivia AFFiNE i miei dati e di cosa devo fare il backup?
Tre percorsi nella directory compose contengono tutti i dati: ./data/postgres per il database, ./data/storage per i file caricati e ./config per config.json. Esegui il backup del database con docker compose exec -T postgres pg_dump --format c --username affine affine > affine.dump invece di copiare i file, perché non è possibile copiare in sicurezza un'istanza Postgres in esecuzione. Crea un archivio tar di ./data/storage per i file caricati e conserva manualmente una copia di config.json, perché, ad agosto 2026, l'esportazione della configurazione dal pannello di amministrazione risulta ancora non implementata.
La collaborazione in tempo reale funziona con AFFiNE self-hosted?
Sì. Non è necessario abilitare alcuna opzione. L'unico requisito riguarda il reverse proxy, perché la sincronizzazione utilizza connessioni WebSocket. Con nginx, questo richiede proxy_http_version 1.1 e gli header Upgrade e Connection: upgrade. Traefik e Caddy inoltrano invece queste connessioni senza configurazione aggiuntiva. Se il proxy non esegue l'upgrade delle connessioni, il workspace si carica e l'accesso funziona normalmente, ma le modifiche effettuate in un browser non compaiono mai negli altri.
Posso eseguire AFFiNE con un'immagine Postgres standard?
No. schema.prisma di AFFiNE dichiara extensions = [pgvector(map: "vector")] e definisce quattro tabelle con una colonna embedding di tipo vector(1024). Il job di migrazione crea queste tabelle anche quando le funzionalità AI sono disabilitate. Usa pgvector/pgvector:pg16, che è Postgres 16 con l'estensione già compilata. Se invece punti AFFiNE a un server Postgres esterno, installa pgvector su quel server e crea l'estensione nel database di destinazione prima di eseguire la migrazione.