Migré mi wiki de Outline a AFFiNE


Homelab · Self-hosting
AFFiNE Outline Docker Authentik OIDC Self-hosting Migración

Outline me funcionó bien como wiki personal (documenté ese montaje en la sección de proyectos cuando lo armé), pero AFFiNE ofrece algo distinto: un editor de bloques similar a Notion, mejor organización jerárquica y, el motivo que terminó de convencerme, soporte nativo de MCP (Model Context Protocol) — el mismo protocolo que usan asistentes de IA como Claude para conectarse a herramientas externas. Poder usar una IA para ayudar a organizar mi propia base de conocimiento, directo desde la wiki, me pareció motivo suficiente para migrar.

El despliegue siguió el mismo patrón que uso para mis otros servicios: Docker Compose propio, Postgres con la extensión pgvector (AFFiNE la usa para embeddings), Redis, y nginx junto con Cloudflare Tunnel para exponerlo en affine.schoperena.com. Al conectar AFFiNE con Authentik por OIDC, Authentik empezó a rechazar el login con “Redirect URI Error”. No era un problema de configuración del proveedor: AFFiNE construye su propio redirect_uri a partir de una variable de entorno, AFFINE_SERVER_EXTERNAL_URL, que no había configurado. Sin ella, AFFiNE asumía que corría en localhost y mandaba ese valor en vez del dominio público. Bastó con agregar la variable para resolverlo.

Para migrar el contenido usé la API de exportación de Outline en vez de hacerlo colección por colección a mano: el endpoint collections.export_all descarga todo el workspace como un ZIP de Markdown con la jerarquía de carpetas intacta, el mismo formato que esperan los importadores tipo “Notion markdown zip” que soporta AFFiNE. Antes de migrar todo de una vez probé con una colección pequeña — cuatro documentos, uno anidado — para confirmar que el import respetara jerarquía y formato. Funcionó sin problemas, así que seguí con el export completo de los 111 documentos.

Después de importar todo, revisé la base de datos de AFFiNE en el servidor para confirmar que hubiera llegado bien. No encontré ningún workspace. El import completo había quedado guardado solo en el almacenamiento local del navegador — un “workspace local”, el modo por defecto de AFFiNE cuando no elegís explícitamente sincronizar con un servidor. Antes de activar la sincronización quise confirmar algo que me preocupaba: el botón dice literalmente “Enable AFFiNE Cloud”, y quería saber a qué servidor se conecta eso exactamente antes de tocarlo. En vez de asumir, revisé el código fuente del proyecto. La respuesta está en packages/frontend/core/src/modules/cloud/constant.ts:

environment.isSelfHosted
  ? [{ id: 'affine-cloud', baseUrl: location.origin, ... }]

En una instalación self-hosted, “Cloud” es literalmente location.origin: el dominio desde el que se carga la página. En mi caso, mi propio servidor y mi propia base de datos. El nombre “AFFiNE Cloud” es solo la etiqueta genérica que reutiliza el mismo código tanto en la versión self-hosted como en el servicio oficial de pago; no implica que la información se envíe a los servidores de la empresa. Con eso confirmado, activé la sincronización y los 111 documentos quedaron guardados en mi propia base de datos.

Ya con todo sincronizado, quise mover parte del contenido a un workspace separado. AFFiNE todavía no tiene una función confiable para mover contenido entre workspaces — hay una solicitud abierta desde hace tiempo en el repositorio del proyecto, y una implementación anterior se retiró por errores. Usé la misma técnica de exportar e importar: exporté la página como Markdown, la importé en el workspace nuevo, y eliminé el original una vez confirmado que todo se había copiado bien. La lección de toda la migración no fue técnica: revisar el código fuente antes de asumir qué hace un botón sigue siendo el mejor consejo cuando se trata de decidir dónde queda guardada la información.

© 2026 Sebastian Choperena Solano — Construido con Astrofy