SSD Nodes Learn Hosting plans →
Guías Matt ConnorPor Matt Connor · Actualizado 2026-08-27

Cómo alojar AFFiNE con Docker Compose en un VPS

Instala AFFiNE en un VPS con Docker Compose: conoce sus cuatro contenedores, fija las etiquetas de imagen, ubica los datos y evalúa qué permite 2 GB de RAM.

Qué se obtiene al alojar AFFiNE por cuenta propia

Alojar AFFiNE por cuenta propia proporciona un espacio de trabajo al estilo de 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 obtiene por defecto un espacio de trabajo autohospedado. La instalación consta de un archivo compose y un archivo de configuración JSON. Los aspectos que requieren planificación son las etiquetas de imagen, la distribución del disco, el límite de memoria y el proxy que se coloque delante.

AFFiNE mantiene un editor de documentos y un lienzo infinito en el mismo espacio de trabajo. Por tanto, una página puede leerse como un documento o ampliarse como una pizarra. Si todavía está decidiendo qué ejecutar, lea primero la comparativa 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 incluido aquí se comprobó 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 una sola ejecución que ejecuta node ./scripts/self-host-predeploy.js, aplica las migraciones de la base de datos y termina. La aplicación declara condition: service_completed_successfully como dependencia de ese trabajo, por lo que una migración que termina con un estado distinto de cero significa que affine no se inicia. Si 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 es 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 utilizada para almacenar embeddings y permitir búsquedas de texto por significado.

redis es una dependencia estricta: tanto el servidor como el trabajo de migración esperan su comprobación de estado 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 contiene datos suyos y no necesita copias de seguridad.

Por qué la imagen de Postgres es pgvector y no postgres estándar

El requisito procede del esquema de AFFiNE, no de una preferencia. En schema.prisma, la fuente de datos declara extensions = [pgvector(map: "vector")], y cuatro tablas tienen una columna embedding con el tipo vector(1024). El trabajo de migración crea esas tablas, independientemente de que se activen o no las funciones de IA. Por tanto, 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 pasó a la imagen pgvector en la versión 0.21. En una instalación anterior a esa versión, editar la línea de la imagen no basta para completar la actualización. Consulte la página de actualización de la documentación de autoservicio de AFFiNE antes de descargar nada.

Hay otro aspecto relacionado con esa etiqueta. pg16 significa Postgres 16, y la versión principal de Postgres no es un número que se pueda cambiar 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. Cambiar de versión principal requiere hacer un dump y restaurarlo en un directorio de datos nuevo.

Cuánta CPU y RAM necesita AFFiNE en modo autoalojado

La página de requisitos de AFFiNE solicita al menos 4 núcleos de CPU y 2 GB de RAM, y eleva la memoria a 4 GB cuando los documentos superan las 10,000 palabras. La misma página indica en qué se utiliza la memoria: el sistema de sincronización y la fusión de documentos. También proporciona una cifra que conviene recordar: la fusión de un documento con 10,000 modificaciones puede alcanzar un pico de 1 GB.

Compare esto con un plan de 2 GB en el que escriben dos personas. El promedio no supone un problema. Postgres y el proceso de Node permanecen por debajo del límite, con memoria disponible. El problema es el pico. Una sola fusión grande puede solicitar 1 GB adicional sobre toda la memoria ya residente. En un equipo de 2 GB sin swap, el asesino de procesos por falta de memoria del kernel (OOM) responde a esa solicitud terminando el proceso más grande, que es 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 poner el contenedor en ejecución en cuestión de 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 nombre node en la segunda, significa que se quedó sin memoria, no que haya encontrado un error del programa. Corrija el problema por ambos extremos. Añada swap primero, para que un pico ralentice el sistema en lugar de provocar 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 -h

free -h debería mostrar ahora un total de 2.0Gi de swap. La swap no hace que AFFiNE sea rápido, ni está pensada para eso. Convierte un pico de un segundo en un segundo lento, en lugar de dejar el contenedor detenido. 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 durante la fusión. 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:

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

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, lo que equivale casi a nada. Mil archivos subidos añaden 10 GB, que es el factor determinante. Son cifras de planificación publicadas, no mediciones de una instancia en ejecución. Tómalas como una referencia de proporción, no como una garantía. La proporción es lo importante: la base de datos ocupa poco 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 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 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 estructura más reciente que mantiene todo bajo ./data y no necesita .env. Ambas opciones son válidas. Escribir el archivo usted mismo resuelve la cuestión. Además, tendrá que editarlo de todos modos para fijar las imágenes y establecer una contraseña para la base de datos.

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

Compose lee .env automáticamente desde el directorio del proyecto y sustituye ${DB_PASSWORD} por usted. Así, la contraseña no 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-stopped

Hay cuatro diferencias respecto al archivo que publica el proyecto upstream. Cada una tiene un motivo.

  • 127.0.0.1:3010:3010 publica el puerto sólo en la dirección de loopback. Por tanto, nada fuera del servidor puede acceder a AFFiNE hasta que decida cómo exponerlo. El '3010:3010' de upstream enlaza todas las interfaces y, en la mayoría de las imágenes VPS, eso incluye la interfaz pública.
  • POSTGRES_HOST_AUTH_METHOD: trust desaparece y se establece una contraseña. La autenticación de confianza acepta cualquier conexión a esa base de datos como el usuario affine sin contraseña. Está limitada a la red privada de Compose. Esto es adecuado hasta que conecte otro contenedor a esa red o publique 5432 durante una tarea de depuración.
  • redis:8-alpine sustituye a un redis sin versión, que se resuelve en latest. En August 2026, esa versión es Redis 8. Fijarla conserva la versión principal que probó y evita que Redis 9 se instale durante una actualización docker compose pull no relacionada.
  • pgvector/pgvector:pg16 se mantiene exactamente como lo establece upstream, por el motivo indicado arriba.

POSTGRES_PASSWORD sólo se lee cuando Postgres crea su directorio de datos por primera vez. 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 su configuración desde config/config.json, que es el directorio montado en /root/.affine/config. Nada crea ese archivo automáticamente, así 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 compartidos y las invitaciones a espacios de trabajo a partir de ese valor. Si se deja en http://localhost:3010, una invitación que envíe apuntará al equipo del destinatario y fallará allí. Establézcalo en la dirección HTTPS pública antes del primer arranque, para que el archivo y el panel de administración siempre coincidan.

copilot controla las funciones de IA. copilot.byok.enabled activa el uso de una clave propia y 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. Alojar AFFiNE por cuenta propia 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 ps

docker compose ps debe mostrar affine_postgres y affine_redis como saludables, affine_server como en ejecución y affine_migration_job con el estado exited (0). Cualquier otro código de salida en el trabajo de migración requiere investigación. Su registro indica el paso que se detuvo:

docker compose logs affine_migration

Fije la imagen antes de olvidarlo

stable es una etiqueta móvil. 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- seguido 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}}'

Ese comando 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 deben coincidir siempre, porque son la misma imagen con dos funciones distintas. Si no coinciden, la base de datos se migra a un esquema y se sirve con otro. La actualización pasa a ser un cambio deliberado 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 que cualquier otra persona

Abra /admin en una instancia nueva. AFFiNE le dirigirá a una página de creación de cuenta porque el servidor todavía no tiene un 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 el administrador del servidor. Por eso, el puerto debe permanecer cerrado hasta que haya registrado la cuenta.

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-ip

Deje el túnel activo y abra http://127.0.0.1:3010/admin en el navegador local. Registre la cuenta e inicie sesión. Después, cierre el túnel. Sólo entonces será seguro publicar la instancia con un nombre accesible públicamente. La misma condición de carrera existe en otras aplicaciones autoalojadas. Es más grave cuando el primer inicio de sesión crea una passkey vinculada al nombre de host. Por eso, TLS y el dominio definitivo deben estar configurados antes de crear la primera cuenta cuando autoaloje openGym.

Dónde almacena AFFiNE tus datos

Tres rutas contienen todos los datos y están dentro del directorio que creó.

  • ./data/postgres es el directorio de datos de Postgres: documentos, usuarios, espacios de trabajo y permisos.
  • ./data/storage se monta en /root/.affine/storage dentro del contenedor y contiene todos los archivos cargados.
  • ./config se monta en /root/.affine/config y contiene config.json.

El proyecto upstream usa montajes bind aquí en lugar de volúmenes con nombre. Es una decisión deliberada: puede empaquetar y copiar estas rutas con comandos normales, sin tener que preguntar a Docker dónde las ubicó. El coste es que la propiedad de los archivos en el host pasa a ser responsabilidad suya. 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 copias de seguridad de dos elementos, y cada uno se respalda de una forma diferente. La base de datos es un servidor activo, por lo que copiar sus archivos mientras está en ejecución produce una copia dañada. En su lugar, 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 indica 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 corromper el flujo binario.

Los archivos subidos son archivos normales, así que empaquételos con tar:

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

Conserve config.json en su copia de seguridad de forma manual. 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. Esta separación entre un volcado de la base de datos y un archivo tar del directorio de cargas es el patrón que debe repetir con cualquier otro contenedor con estado que ejecute. Es el mismo patrón que mantiene a salvo el historial de conversaciones y los archivos adjuntos cuando aloja Chatwoot por su cuenta como mesa de ayuda.

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, se copia un archivo llamado affine.backup en el contenedor y después se restaura desde ./pg.backup. Son dos nombres distintos. Además, se elimina un directorio ./postgres, mientras que el archivo compose actual guarda los datos en ./data/postgres. Siga las rutas que utilizó realmente, no las del fragmento. Esta es la secuencia para la estructura de este manual:

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

Observe que se usa mv y no 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. Apartar el directorio antiguo no cuesta nada. Restaure también las cargas de archivos con 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 indica claramente: WebSocket es la base del sistema de sincronización y colaboración de AFFiNE. Por tanto, un proxy que no actualice esas conexiones permite acceder a 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 incorpora 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: true

El nombre del resolvedor de certificados debe coincidir con el definido en la configuración de Traefik. loadbalancer.server.port es el puerto 3010 del contenedor, nunca un puerto del host. Traefik gestiona las conexiones WebSocket sin configuración adicional, por lo que no es necesario añadir nada más. Si el resto de la pila ya está detrás de Authentik para el inicio de sesión único, un middleware de autenticación delegada en este router controlará el acceso de los navegadores a AFFiNE. Sin embargo, manténgalo desactivado hasta probar la aplicación de escritorio, que no tiene una sesión de navegador y simplemente dejará de sincronizarse. La ejecución de varias aplicaciones detrás de una misma instancia se explica en un solo Traefik delante de varias aplicaciones.

En 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, cualquier carga superior a una foto pequeña falla con un estado 413 y no aparece nada en los registros de AFFiNE, porque la petición nunca llegó al servicio. 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 realista sobre esto antes de migrar un equipo.

La colaboración en tiempo real está disponible y es la función a la que se refiere toda la información sobre dimensionamiento, porque la documentación de AFFiNE atribuye el consumo de memoria al sistema de sincronización y a la combinación de documentos. La edición sin conexión es una de las razones por las 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 desactivada, vuelva a conectarse y compruebe el resultado en un segundo dispositivo. Las listas de funciones no son pruebas, y esta tampoco.

La búsqueda de texto completo en el servidor está desactivada en el archivo compose distribuido, donde AFFINE_INDEXER_ENABLED=false está establecido en el servidor y en el trabajo de migración. Activarla requiere 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. Un espacio de trabajo autohospedado admite como máximo 10 usuarios, y para superar esa cantidad se necesita una licencia Team de AFFiNE. La documentación describe el almacenamiento ilimitado de blobs y el tamaño ilimitado de blobs para las instancias autohospedadas como funciones previstas, pero todavía no implementadas por completo, según la comprobación realizada en agosto de 2026. Ninguno de estos límites importa en un entorno doméstico o para 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, porque puede incluir cambios incompatibles. Haga una copia de seguridad de la base de datos y del directorio de almacenamiento antes de modificar nada. La tarea de migración modifica el esquema en el siguiente arranque y no hay forma de deshacerlo. 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 termine correctamente. docker image prune elimina las capas antiguas después. Una nota histórica para quienes usan 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 tanto, un archivo de Compose anterior a esa versión necesita que se reescriban sus líneas de imagen antes de que 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 normal no puede crear.

¿Cuánta RAM necesita AFFiNE en una instalación autogestionada?

La página de requisitos de AFFiNE solicita al menos 4 núcleos de CPU y 2 GB de RAM. Esta cifra aumenta a 4 GB cuando los documentos superan las 10,000 palabras. También indica que combinar un documento con 10,000 modificaciones puede alcanzar un pico de 1 GB. En un servidor con 2 GB, ese pico es lo que provoca el fallo, no la carga en reposo: el killer de falta de memoria del kernel detiene el proceso de AFFiNE y restart: unless-stopped lo inicia de nuevo. Por eso los usuarios ven que la página se recarga en lugar de 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 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 del 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 conservar los archivos subidos y guarde manualmente una copia de config.json. La exportación de la configuración desde el panel de administración figura como no implementada todavía en agosto de 2026.

¿Funciona la colaboración en tiempo real en una instalación autogestionada de AFFiNE?

Sí. No es necesario activar ninguna opción. El único requisito es configurar correctamente el proxy inverso, porque la sincronización utiliza conexiones WebSocket. En nginx, esto significa 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 las actualiza, el espacio de trabajo se carga y el inicio de sesión funciona con normalidad, pero las modificaciones realizadas en un navegador nunca aparecen en otro.

¿Puedo ejecutar AFFiNE con una imagen Postgres estándar?

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 incluso cuando 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.