Cómo alojar AFFiNE con Docker Compose en un VPS
Instala AFFiNE en un VPS con Docker Compose: cuatro contenedores, etiquetas fijadas, ubicación de datos, copias de seguridad y el límite real de 2 GB de RAM.
Qué se obtiene al alojar AFFiNE por cuenta propia
Alojar AFFiNE por cuenta propia proporciona un espacio de trabajo similar a Notion en un servidor bajo su control. Se ejecuta mediante cuatro contenedores: la aplicación, un trabajo de migración de una sola ejecución, Postgres y Redis. La colaboración en tiempo real está incluida, hasta las 10 plazas que un espacio de trabajo autohospedado obtiene de forma predeterminada. La instalación consta de un archivo compose y un archivo de configuración JSON. Debe prestar atención a las etiquetas de imagen, la distribución del disco, el límite de memoria y el proxy que coloque delante.
AFFiNE mantiene un editor de documentos y un lienzo infinito en el mismo espacio de trabajo. Por eso, una página puede leerse como un documento o desplegarse como una pizarra. Si todavía está decidiendo qué ejecutar, lea primero la comparación de alternativas autohospedadas a Notion. Esta guía parte de que la decisión ya está tomada y explica cómo ejecutar AFFiNE correctamente, en lugar de volver a compararlo.
Todo lo indicado aquí se verificó con la documentación de autohospedaje de AFFiNE y los archivos de lanzamiento publicados el 8 de agosto de 2026. La versión estable más reciente en esa fecha era la 0.27.3, publicada el 23 de julio de 2026.
Qué hacen realmente los cuatro contenedores
affine contiene el servidor y el cliente web en una sola imagen. Escucha en el puerto 3010.
affine_migration es un trabajo de ejecución única que ejecuta node ./scripts/self-host-predeploy.js, aplica las migraciones de la base de datos y finaliza. La aplicación declara condition: service_completed_successfully como dependencia de ese trabajo, por lo que una migración que finaliza con un estado distinto de cero significa que affine nunca se inicia. Cuando la interfaz web no se inicia, el registro de ese trabajo es lo primero que debe revisar.
postgres almacena sus documentos, usuarios, espacios de trabajo y permisos. La imagen proporcionada es pgvector/pgvector:pg16, que contiene Postgres 16 estándar con la extensión pgvector compilada. pgvector añade a Postgres un tipo de columna vector, la representación numérica que se utiliza para almacenar embeddings y permitir búsquedas de texto por significado.
redis es una dependencia obligatoria: tanto el servidor como el trabajo de migración esperan a que su comprobación de estado finalice correctamente antes de iniciarse. Observe lo que el archivo compose proporcionado no asigna a Redis, que es un volumen. Nada de su contenido sobrevive a un docker compose down, lo que indica claramente que no almacena datos suyos y no necesita copias de seguridad.
Por qué la imagen de Postgres es pgvector y no la imagen estándar de Postgres
Este requisito procede del esquema de AFFiNE, no de una preferencia. En schema.prisma, el origen de datos declara extensions = [pgvector(map: "vector")], y cuatro tablas contienen una columna embedding con el tipo vector(1024). El trabajo de migración crea esas tablas aunque nunca se activen las funciones de IA, por lo que la extensión ya debe existir en la base de datos antes de que termine la migración. Si se sustituye por postgres:16, la extensión desaparece, la migración no puede crear esas columnas y el servidor queda esperando un trabajo que ha fallado.
AFFiNE cambió a la imagen de pgvector en la versión 0.21. En una instalación anterior, editar la línea de la imagen no completa la actualización. Consulte la página de actualización de la documentación de autohospedaje de AFFiNE antes de descargar nada.
Hay otro aspecto importante sobre esa etiqueta. pg16 significa Postgres 16, y la versión principal de Postgres no es un número que se pueda incrementar directamente. Si se cambia a pg17 sobre un directorio de datos existente, Postgres se niega a iniciar y muestra una línea como The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17 en docker compose logs postgres. El cambio de versión principal requiere realizar un volcado y restaurarlo en un directorio de datos nuevo.
Cuánta CPU y RAM necesita AFFiNE autohospedado
La página de requisitos de AFFiNE solicita al menos 4 núcleos de CPU y 2 GB de RAM. También aumenta la memoria recomendada a 4 GB cuando los documentos superan las 10,000 palabras. La misma página explica en qué se usa la memoria: el sistema de sincronización y la combinación de documentos. Incluye una cifra que conviene recordar: combinar un documento con 10,000 modificaciones puede consumir hasta 1 GB.
Ahora compare esto con un plan de 2 GB usado por dos personas para escribir. El promedio no supone un problema. Postgres y el proceso de Node permanecen por debajo del límite, con margen disponible. El problema es el pico. Una sola combinación grande puede solicitar 1 GB adicional a todo lo que ya está en memoria. En un equipo de 2 GB sin swap, el kernel responde a esa solicitud con el asesino de procesos sin memoria (OOM), que termina el proceso más grande: el servidor de AFFiNE.
Su colega no ve un error. Ve que la página se vuelve a cargar porque restart: unless-stopped vuelve a iniciar el contenedor en pocos segundos. No lo suponga: confírmelo:
docker inspect affine_server --format '{{.State.OOMKilled}} {{.RestartCount}}'
sudo dmesg -T | grep -i -E 'out of memory|killed process'true en la primera orden, o una línea Killed process que mencione node en la segunda, significa que se quedó sin memoria y no que encontró un error del software. Corríjalo desde ambos extremos. Añada swap primero para que un pico provoque lentitud en lugar de un fallo:
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 debería mostrar ahora un total de 2.0Gi de swap. El swap no hace que AFFiNE sea más rápido, y no está pensado para eso. Convierte un pico de un segundo en un segundo lento, en lugar de provocar la terminación del contenedor. El otro extremo de la solución consiste en impedir que Postgres haga crecer su caché hasta ocupar el espacio que la aplicación necesita al combinar documentos. Para eso sirven los límites de memoria en un servicio de Compose.
El almacenamiento es mucho más fácil de prever. Estas son las cifras que AFFiNE publica en esa misma página:
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
}
]La instalación del servidor ocupa 1.5 GB. Mil documentos de aproximadamente mil palabras cada uno añaden 0.1 GB de datos de Postgres, una cantidad casi insignificante. Mil archivos subidos añaden 10 GB, que es el factor principal. Son cifras publicadas para planificar, no mediciones de una instancia en ejecución. Considérelas una referencia aproximada, no una garantía. Lo importante es la proporción: la base de datos sigue siendo pequeña y los archivos subidos determinan el espacio de disco.
Escriba usted mismo el archivo de Compose, con las etiquetas fijadas
La instalación documentada descarga un archivo ya preparado con curl -L -o docker-compose.yml https://github.com/toeverything/AFFiNE/releases/latest/download/docker-compose.yml. Funciona. Conviene conocer un detalle antes de depender de él: a fecha del 8 August 2026, el archivo adjunto a la versión 0.27.3 todavía lee sus rutas desde un archivo .env, mediante ${UPLOAD_LOCATION}, ${CONFIG_LOCATION} y ${DB_DATA_LOCATION}, mientras que la página de referencia de la documentación muestra una disposición más reciente que mantiene todo bajo ./data y no necesita .env. Ambas opciones son válidas. Escribir el archivo usted mismo resuelve la duda. Además, tendrá que editarlo de todos modos para fijar las imágenes y establecer una contraseña de base de datos.
mkdir -p ~/affine/config ~/affine/data
cd ~/affine
printf 'DB_PASSWORD=%s\n' "$(openssl rand -hex 24)" > .env
chmod 600 .envCompose lee .env automáticamente desde el directorio del proyecto y sustituye ${DB_PASSWORD} por usted. Así, la contraseña nunca aparece en el archivo que pegaría en un hilo de soporte. Conviene mantener este hábito en todas las pilas que ejecute. El motivo se explica en mantener los secretos fuera del archivo de Compose.
Ahora escriba ~/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-stoppedHay cuatro diferencias con respecto al archivo que distribuye el proyecto, y cada una tiene un motivo.
127.0.0.1:3010:3010publica el puerto sólo en la dirección de loopback. Así, nada fuera del servidor puede acceder a AFFiNE hasta que decida cómo hacerlo. El archivo original'3010:3010'enlaza con todas las interfaces. En la mayoría de las imágenes de VPS, eso incluye la interfaz pública.POSTGRES_HOST_AUTH_METHOD: trustdesaparece y se establece una contraseña. La autenticación de confianza acepta cualquier conexión a esa base de datos como el usuarioaffinesin contraseña. Está limitada a la red privada de Compose. Esto funciona hasta que conecte otro contenedor a esa red o publique 5432 durante una tarea de depuración.redis:8-alpinesustituye a unredissin versión fijada, que se resuelve enlatest. En August 2026, esa etiqueta corresponde a Redis 8. La fijación conserva la versión principal probada y evita que Redis 9 se instale durante unadocker compose pullno relacionada.pgvector/pgvector:pg16se mantiene exactamente como lo establece el proyecto original, por el motivo indicado arriba.
POSTGRES_PASSWORD sólo se lee cuando Postgres crea por primera vez su directorio de datos. En una instancia que ya existe, establezca la contraseña con docker compose exec postgres psql -U affine -c "ALTER USER affine WITH PASSWORD 'yourpassword'" y actualice después DATABASE_URL para que coincida.
La configuración se encuentra en config/config.json
AFFiNE lee la configuración desde config/config.json, que es el directorio montado en /root/.affine/config. Ningún componente crea ese archivo automáticamente, por lo que debe escribirlo antes del primer arranque. Abra ~/affine/config/config.json en un editor y asígnele este contenido. Sustituya el dominio de ejemplo por el suyo:
{
"$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 debe ser la dirección que los usuarios abren realmente en un navegador. AFFiNE genera los enlaces para compartir y las invitaciones a espacios de trabajo a partir de ese valor. Si se deja como http://localhost:3010, la invitación apunta al equipo del propio destinatario y falla allí. Establézcalo en la dirección HTTPS pública antes del primer arranque. Así, el archivo y el panel de administración siempre usarán el mismo valor.
copilot controla las funciones de IA. copilot.byok.enabled activa la opción de usar una clave propia. Esto permite que el propietario de un espacio de trabajo introduzca la clave de su proveedor de modelos en la configuración del espacio de trabajo. El alojamiento propio de AFFiNE no incluye una suscripción de IA. Deje ambos valores en false si no desea usarla.
Inicie la pila:
docker compose up -d
docker compose psdocker compose ps debe mostrar affine_postgres y affine_redis como healthy, affine_server como running y affine_migration_job con el estado exited (0). Debe investigar cualquier otro código de salida del trabajo de migración. Su registro indica el paso que se detuvo:
docker compose logs affine_migrationFije la imagen antes de olvidarlo
stable es una etiqueta mutable. El flujo de publicación de AFFiNE apunta varias etiquetas a cada compilación estable, y aquí importan dos: stable, que se reasigna en cada publicación, y stable- seguida del hash corto de git, que no cambia. Si deja stable, un docker compose pull dentro de seis meses descargará una imagen diferente y ejecutará sus migraciones en la base de datos en un momento que usted no eligió. Fije la imagen exacta que probó:
docker compose pull
docker image inspect ghcr.io/toeverything/affine:stable --format '{{index .RepoDigests 0}}'Esto muestra una línea como ghcr.io/toeverything/affine@sha256: seguida de un hash largo. Pegue la cadena completa en la línea image: de ambos affine y affine_migration. Ambos valores deben coincidir siempre, porque representan la misma imagen con dos funciones distintas. Si no coinciden, la base de datos se migra a un esquema y se sirve con otro. A partir de ese momento, actualizar requiere una edición deliberada en lugar de una sorpresa: cambie el digest, haga una copia de seguridad, docker compose pull, docker compose up -d.
Cree la cuenta de administrador antes de que lo haga otra persona
Abra /admin en una instancia nueva. AFFiNE le redirigirá a una página de creación de cuenta porque el servidor todavía no tiene ningún administrador. Ese flujo no usa ningún código de invitación ni token de configuración. La primera persona que cargue esa página se convertirá en la administradora del servidor. Por eso, el puerto debe permanecer cerrado hasta que se haya registrado.
Por ese motivo, el archivo compose anterior enlaza con 127.0.0.1. Acceda mediante un túnel SSH desde su propio equipo:
ssh -L 3010:127.0.0.1:3010 you@your-server-ipDeje el túnel abierto y acceda a http://127.0.0.1:3010/admin desde el navegador local. Regístrese e inicie sesión. Después, cierre el túnel. Solo entonces será seguro publicar la instancia con un nombre público.
Dónde almacena AFFiNE los datos
Tres rutas contienen todos los datos y están dentro del directorio que creó.
./data/postgreses el directorio de datos de Postgres: documentos, usuarios, espacios de trabajo y permisos../data/storagese monta en/root/.affine/storagedentro del contenedor y contiene todos los archivos cargados../configse monta en/root/.affine/configy contieneconfig.json.
El proyecto upstream usa aquí montajes bind en lugar de volúmenes con nombre. Esta elección es intencionada: puede empaquetar y copiar estas rutas con comandos normales, sin tener que preguntar a Docker dónde las ubicó. La desventaja es que ahora debe gestionar la propiedad de los archivos en el host. Este es el compromiso que se explica en montajes bind y volúmenes con nombre.
Cómo hacer una copia de seguridad de AFFiNE
Hay que hacer una copia de seguridad de dos elementos, y cada uno se respalda de una forma distinta. La base de datos es un servidor activo, por lo que copiar sus archivos mientras está en ejecución produce una copia dañada. Genere un volcado:
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/El volcado se ejecuta dentro del contenedor mediante su socket local, por lo que no solicita la contraseña. Compruebe el tamaño en la salida de ls. Un archivo de unos cientos de bytes significa que el volcado falló, aunque el shell haya creado el archivo. Este es el fallo que muchas personas descubren seis meses después. -T también es importante: sin esta opción, Compose puede asignar un terminal y dañar el flujo binario.
Los archivos cargados son archivos normales, así que archívelos con tar:
tar czf backup/storage-$(date +%F).tgz -C data storage
cp config/config.json backup/config-$(date +%F).jsonConserve config.json manualmente en la copia de seguridad. La documentación de AFFiNE todavía indica que la exportación de la configuración desde el panel de administración no está implementada, según la comprobación realizada en agosto de 2026. Por tanto, el archivo del disco es la única copia de la configuración. Copie los tres archivos fuera del servidor. Una copia de seguridad almacenada en el mismo disco que los datos que protege no es una copia de seguridad.
Restauración y una trampa en los pasos publicados
Lea los pasos oficiales de restauración antes de necesitarlos y revíselos con atención. En la versión publicada en agosto de 2026, copian un archivo llamado affine.backup al contenedor y después restauran desde ./pg.backup. Son dos nombres diferentes. Además, eliminan un directorio ./postgres, mientras que el archivo compose actual guarda los datos en ./data/postgres. Use las rutas que realmente configuró, no las del fragmento. Esta es la secuencia para la estructura de este tutorial:
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 -dObserve el uso de mv en lugar de rm. Restaurar sobre una base de datos de la que no ha conservado una copia convierte un comando incorrecto en una pérdida total de datos. Mover el directorio antiguo a otra ubicación no cuesta nada. Restaure también las cargas mediante tar xzf backup/storage-2026-08-08.tgz -C data. De lo contrario, todos los documentos se mostrarán con los archivos adjuntos dañados. Después, inicie sesión y abra un documento que contenga una imagen. Esa es la prueba. Una restauración que no ha abierto en un navegador es un archivo, no una copia de seguridad.
Poner AFFiNE detrás de un proxy existente
AFFiNE usa WebSocket y no es opcional. La documentación lo deja claro: WebSocket es la base del sistema de sincronización y colaboración de AFFiNE. Por tanto, un proxy que no actualice esas conexiones deja un espacio de trabajo en el que la edición deja de sincronizarse silenciosamente. La página carga y el inicio de sesión funciona, pero una edición realizada en un navegador nunca llega al otro. En las herramientas de desarrollo del navegador, abra la pestaña Network y filtre por WS. Una conexión que se abre y se cierra repetidamente indica que el proxy no está pasando la actualización.
Si ya ejecuta Traefik para otros contenedores, AFFiNE se añade como un servicio normal. Elimine el bloque ports: del servicio affine y añada:
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'Al final del archivo, junto a services:, añada:
networks:
proxy:
external: trueEl nombre del certificate resolver debe coincidir con el definido en la configuración de Traefik, y loadbalancer.server.port es el puerto 3010 del contenedor, nunca un puerto del host. Traefik funciona como proxy para las conexiones WebSocket sin configuración adicional, por lo que no es necesario añadir nada más. La ejecución de varias aplicaciones detrás de una sola instancia se explica en un solo Traefik delante de varias aplicaciones.
Con nginx debe solicitar la actualización explícitamente:
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 tiene un valor predeterminado de 1 MB en nginx. Sin esa línea, toda carga superior a una foto pequeña falla con un estado 413 y no aparece nada en los registros de AFFiNE, porque la solicitud nunca llegó. Caddy necesita una línea, reverse_proxy http://127.0.0.1:3010, y gestiona por sí mismo los certificados y las actualizaciones de WebSocket.
Lo que no incluye la versión autohospedada
Sea honesto consigo mismo sobre esto antes de migrar un equipo.
La colaboración en tiempo real está disponible y es la función a la que se refieren todas las recomendaciones de dimensionamiento, porque la documentación de AFFiNE atribuye el uso de memoria al sistema de sincronización y a la combinación de documentos. La edición sin conexión es la razón por la que muchas personas quieren una herramienta local-first, y la aplicación de escritorio puede añadir el servidor autohospedado a su lista de espacios de trabajo e iniciar sesión en él. Pruebe el comportamiento exacto sin conexión del que depende su equipo antes de comprometerse: edite en la aplicación de escritorio con la red desconectada, vuelva a conectarse y compruebe el resultado en un segundo dispositivo. Las listas de funciones no son una prueba, y esta tampoco.
La búsqueda de texto completo en el servidor está desactivada en el archivo compose incluido, donde AFFINE_INDEXER_ENABLED=false está establecido en el servidor y en el trabajo de migración. Activarla implica añadir un contenedor de Manticore Search, que sería un quinto servicio y consumiría más memoria. En un equipo con 2 GB, ese cambio es el que hace que se supere el límite. La búsqueda dentro del cliente sigue funcionando en el espacio de trabajo que tenga abierto.
Conviene conocer dos límites antes de invitar a otras personas. A un espacio de trabajo autohospedado se le conceden como máximo 10 puestos, y superar esa cantidad requiere una licencia Team de AFFiNE. La documentación describe el almacenamiento ilimitado de blobs y el tamaño ilimitado de los blobs para instancias autohospedadas como funciones previstas, pero todavía no completamente implementadas, según la comprobación realizada en agosto de 2026. Ninguno de estos límites importa en un hogar o en un equipo pequeño. Ambos importan si tenía previsto migrar a cuarenta personas.
Actualizaciones
Lea primero las notas de la versión, especialmente para un cambio de versión menor como 0.26 a 0.27, en el que pueden introducirse cambios incompatibles. Haga una copia de seguridad de la base de datos y del directorio de almacenamiento antes de modificar nada, porque la tarea de migración cambia el esquema en el siguiente arranque y no se puede deshacer. Después, cambie el digest fijado, ejecute docker compose pull seguido de docker compose up -d y supervise docker compose logs -f affine_migration hasta que finalice correctamente. docker image prune elimina las capas antiguas después. Una nota histórica para quienes utilizan una instalación muy antigua: a partir de la versión 0.23.0, el nombre de la imagen cambió de affine-graphql a affine, por lo que un archivo compose anterior a esa versión necesita que se reescriban sus líneas de imagen antes de que un pull pueda encontrarla.
FAQ
¿Por qué el contenedor de AFFiNE nunca se inicia?
El servicio affine declara condition: service_completed_successfully en el trabajo affine_migration. Por tanto, si la migración termina con un estado distinto de 0, el servidor nunca se inicia y no aparece ninguna interfaz web. Ejecute docker compose logs affine_migration para ver en qué paso se detuvo. La causa más habitual en un archivo compose editado manualmente es usar una imagen postgres estándar en lugar de pgvector/pgvector:pg16. El esquema de AFFiNE declara la extensión pgvector y crea tablas con columnas vector(1024) que un Postgres estándar no puede crear.
¿Cuánta RAM necesita una instancia de AFFiNE autohospedada?
La página de requisitos de AFFiNE solicita al menos 4 núcleos de CPU y 2 GB de RAM. La memoria necesaria aumenta a 4 GB cuando los documentos superan las 10,000 palabras. También indica que combinar un documento con 10,000 modificaciones puede consumir hasta 1 GB. En un servidor con 2 GB, ese pico es lo que provoca el problema, no la carga en reposo. El kernel detiene el proceso de AFFiNE mediante el mecanismo de falta de memoria, y restart: unless-stopped lo inicia de nuevo. Por eso los usuarios ven que la página se vuelve a cargar en lugar de mostrar un error. Confírmelo con docker inspect affine_server --format '{{.State.OOMKilled}}' y sudo dmesg -T | grep -i 'out of memory'. Después, añada un archivo de swap de 2 GB para que un pico ralentice el sistema en lugar de provocar un fallo.
¿Dónde almacena AFFiNE mis datos y qué debo incluir en las copias de seguridad?
Tres rutas bajo el directorio de compose contienen todos los datos: ./data/postgres para la base de datos, ./data/storage para los archivos subidos y ./config para config.json. Haga una copia de seguridad de la base de datos con docker compose exec -T postgres pg_dump --format c --username affine affine > affine.dump en lugar de copiar los archivos, porque no es seguro copiar un Postgres en ejecución. Comprima ./data/storage con tar para guardar los archivos subidos y conserve manualmente una copia de config.json. En agosto de 2026, el panel de administración indica que la exportación de la configuración todavía no está implementada.
¿Funciona la colaboración en tiempo real en una instancia de AFFiNE autohospedada?
Sí. No es necesario activar ninguna opción. El único requisito es configurar correctamente el reverse proxy, porque la sincronización utiliza conexiones WebSocket. En nginx, esto requiere proxy_http_version 1.1 y las cabeceras Upgrade y Connection: upgrade. Traefik y Caddy reenvían esas conexiones sin configuración adicional. Si el proxy no actualiza la conexión, el espacio de trabajo se carga y el inicio de sesión funciona con normalidad, pero las ediciones realizadas en un navegador nunca aparecen en otro.
¿Puedo ejecutar AFFiNE con una imagen estándar de Postgres?
No. El schema.prisma de AFFiNE declara extensions = [pgvector(map: "vector")] y define cuatro tablas con una columna embedding de tipo vector(1024). El trabajo de migración crea esas tablas aunque las funciones de IA estén desactivadas. Use pgvector/pgvector:pg16, que es Postgres 16 con esa extensión compilada. Si conecta AFFiNE a un servidor Postgres externo, instale pgvector en ese servidor y cree la extensión en la base de datos de destino antes de ejecutar la migración.