Herramientas de diagramas autoalojadas comparadas
Compare draw.io, Excalidraw y Kroki en su VPS: descubra qué procesa el navegador, qué llega al servidor y cuándo el autoalojamiento mejora la privacidad.
¿Qué herramienta de diagramas autoalojada debe ejecutar?
Las herramientas de diagramas autoalojadas tienen dos modelos de funcionamiento, y el modelo importa más que la lista de funciones. draw.io y Excalidraw son aplicaciones de navegador: el contenedor proporciona JavaScript, el navegador realiza el dibujo y el servidor nunca ve el diagrama. Kroki funciona de forma opuesta. Se le envía texto de diagrama mediante HTTP y devuelve una imagen, por lo que cada diagrama pasa por su propia máquina.
Ejecute draw.io si quiere un editor completo junto a una wiki. Ejecute Excalidraw si quiere un bloc de dibujo rápido y acepta que no guarda nada fuera del navegador en el que realizó el dibujo. Ejecute Kroki si sus diagramas son texto almacenado en git junto al código que describen.
Qué cambia realmente al autoalojar una herramienta de diagramas
Sea preciso sobre qué componentes interactúan con su servidor, porque ese único hecho determina si el autoalojamiento aporta privacidad o sólo disponibilidad.
- draw.io procesa los diagramas en el navegador. Su contenedor sirve el código de la aplicación. El archivo se guarda donde indique en el editor.
- Excalidraw procesa los diagramas en el navegador y conserva la escena actual en el almacenamiento local de ese navegador. No se escribe nada en el servidor.
- Kroki procesa los diagramas en el servidor. Tanto el código fuente del diagrama como la imagen final existen dentro de su contenedor.
Sólo el tercer caso mueve los datos a hardware que usted controla. En los dos primeros, el autoalojamiento proporciona control sobre los recursos y disponibilidad: JavaScript procede de su host, por lo que el editor sigue funcionando cuando un tercero sufre una interrupción, cambia sus condiciones o deja de ser accesible desde su red. Para algunos equipos, esto tiene un valor económico real. Es una afirmación distinta de que «el diagrama nunca sale del edificio».
draw.io: un contenedor oficial que no almacena nada
El proyecto publica su propia imagen, y el inicio rápido de su README ocupa una sola línea.
docker run -it --rm --name="draw" -p 8080:8080 -p 8443:8443 jgraph/drawioEsto publica el editor en todas las direcciones que tiene el equipo. En un VPS, vincule el puerto publicado a loopback y acceda a él mediante un proxy inverso o un túnel SSH.
docker run -d --name drawio --restart unless-stopped -p 127.0.0.1:8080:8080 jgraph/drawioAbra http://127.0.0.1:8080/?offline=1&https=0 a través del túnel. El README describe ?offline=1 como «una función de seguridad que desactiva la compatibilidad con el almacenamiento en la nube». Sin esta opción, el editor ofrece Google Drive, OneDrive y GitHub como destinos de guardado, que son servidores de terceros.
Vincularlo a 127.0.0.1 es lo que mantiene ese puerto fuera de Internet pública. Un -p 8080:8080 simple no queda filtrado por ufw, porque Docker inserta sus propias reglas de iptables antes de las cadenas que administra ufw. Por eso el firewall puede parecer correcto mientras el puerto responde a todo Internet. Docker publica directamente y evita ufw explica el mecanismo y la solución.
Dos variables de entorno son importantes en cuanto el editor deja de ejecutarse en localhost.
services:
drawio:
image: jgraph/drawio
container_name: drawio
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080"
environment:
DRAWIO_SERVER_URL: "https://drawio.example.com/"
DRAWIO_BASE_URL: "https://drawio.example.com"La barra final no es un error tipográfico. El README define DRAWIO_SERVER_URL como la «URL de despliegue pública con una barra final» y DRAWIO_BASE_URL como la «misma URL sin una barra final». El visor, lightbox y las rutas de código de inserción utilizan estos valores. Si sirve el editor bajo una subruta como https://www.example.com/drawio/, ambos valores deben incluir esa subruta, porque la aplicación construye a partir de ellos las URL del visor y del contenido insertado.
Persistencia: no existe, y es intencionado. Ese archivo Compose no contiene ningún volumen porque el contenedor no almacena datos de diagramas. Un archivo .drawio es XML que el editor entrega al navegador, y el destino de guardado que elija determina dónde termina: una descarga en su propio equipo o la aplicación que haya insertado el editor. Haga una copia de seguridad de ese destino. Si se trata de una carpeta del VPS, lo que debe proteger es esa carpeta y el gestor de archivos que utiliza para acceder a ella, porque draw.io no conserva ninguna copia.
Qué datos siguen saliendo del servidor. Menos que antes, porque la exportación ya no se envía a ningún sitio. El README incluye ahora EXPORT_URL y DRAWIO_SELF_CONTAINED entre las variables eliminadas que no tienen ningún efecto. Estas variables dirigían el editor a la imagen independiente jgraph/export-server, que «ha llegado al final de su vida útil». Ahora el entrypoint escribe window.EXPORT_URL = null en PreConfig.js, lo que indica al editor que no existe ningún servicio de exportación. En la práctica, Export as > PDF abre el cuadro de diálogo de impresión del navegador. Por eso no se ofrece la opción de incluir una copia del diagrama en el PDF y se oculta la exportación de PNG y JPEG procesada por el servidor. La exportación de PNG, JPEG y SVG procesada en el navegador sigue funcionando. ENABLE_DRAWIO_PROXY está desactivado de forma predeterminada y habilita un endpoint /proxy que obtiene URL de imágenes externas en nombre del navegador. Manténgalo desactivado salvo que lo necesite.
Excalidraw: un paquete estático sin servidor detrás
La página oficial de la imagen proporciona este comando.
docker run --rm -dit --name excalidraw -p 5000:80 excalidraw/excalidraw:latestCambie el puerto publicado para que escuche en loopback, por el mismo motivo que antes.
docker run -d --name excalidraw --restart unless-stopped -p 127.0.0.1:5000:80 excalidraw/excalidraw:latestDentro del contenedor, nginx sirve un paquete de JavaScript compilado en el puerto 80. La imagen publicada ocupa alrededor de 41 MB comprimidos (Docker Hub, octubre de 2026), lo que indica lo poco que contiene. No incluye base de datos, almacén de sesiones ni directorio de cargas, porque no hay nada que almacenar en el servidor.
La página de la imagen indica claramente la limitación: «Por el momento, alojar su propia instancia no admite las funciones de uso compartido ni de colaboración». Los botones siguen apareciendo en la interfaz, por lo que conviene conocer el motivo. La colaboración en tiempo real necesita un servidor websocket, publicado por separado como excalidraw/excalidraw-room. Un enlace compartido necesita un servicio de almacenamiento que conserve la escena cifrada. Las direcciones de ambos servicios se incorporan al paquete durante la compilación como variables de Vite (VITE_APP_WS_SERVER_URL, VITE_APP_BACKEND_V2_GET_URL, VITE_APP_BACKEND_V2_POST_URL), y los valores de producción del repositorio apuntan a los servicios alojados por Excalidraw. Vite sustituye esos valores durante la compilación, por lo que terminan como cadenas literales dentro del código JavaScript. Definirlos como variables de entorno del contenedor no cambia nada, porque ningún código las lee durante la ejecución. Para apuntar la colaboración a su propio servidor de salas, debe compilar el frontend desde el código fuente con sus propios valores. Compruebe el estado de ese servidor antes de basar su planificación en él: en octubre de 2026, el push más reciente de la imagen excalidraw/excalidraw-room en Docker Hub era de diciembre de 2023.
Dónde se almacena realmente un dibujo. La escena se guarda en el almacenamiento local del navegador, en ese dispositivo y para ese origen. Abra la misma URL en una ventana privada y el lienzo aparecerá vacío. Es la forma más rápida de comprobarlo. Al borrar los datos del sitio se elimina el dibujo, y no existe una copia en el servidor que se pueda restaurar. Por tanto, indique a los usuarios que utilicen «Guardar en...» y que conserven el archivo .excalidraw, que está en formato JSON, en una ubicación incluida en las copias de seguridad. Una instancia compartida proporciona a cada persona su propio lienzo privado. Trátela como un bloc de dibujo personal alojado en un servidor.
Kroki: diagramas como código, representados en el servidor
Kroki es una única puerta de enlace HTTP delante de varios renderizadores. Se envía texto mediante POST y se obtiene SVG o PNG. Graphviz, PlantUML, D2 y varios renderizadores más están integrados en la imagen de la puerta de enlace. Mermaid, BPMN y Excalidraw se ejecutan en contenedores complementarios, por lo que Compose es la forma adecuada de ejecutarlo. Este es el ejemplo de la documentación de Kroki.
services:
kroki:
image: yuzutech/kroki
depends_on:
- mermaid
- bpmn
- excalidraw
environment:
- KROKI_MERMAID_HOST=mermaid
- KROKI_BPMN_HOST=bpmn
- KROKI_EXCALIDRAW_HOST=excalidraw
ports:
- "8000:8000"
tmpfs:
- /tmp:exec
mermaid:
image: yuzutech/kroki-mermaid
expose:
- "8002"
bpmn:
image: yuzutech/kroki-bpmn
expose:
- "8003"
excalidraw:
image: yuzutech/kroki-excalidraw
expose:
- "8004"expose no publica nada en el host, por lo que los contenedores complementarios sólo son accesibles desde la puerta de enlace en la red de Compose. Eso es lo que se busca. Cambie la línea de la puerta de enlace a "127.0.0.1:8000:8000", salvo que la wiki que la utiliza se ejecute en otro host. Si nunca ha escrito un archivo de Compose en un servidor, ejecutar Docker Compose en un VPS explica la estructura de archivos y el ciclo docker compose up -d.
Ejecute dos pruebas básicas, en este orden, porque fallan por motivos diferentes.
curl -s -X POST http://127.0.0.1:8000/graphviz/svg \
-H 'Content-Type: text/plain' \
--data-binary 'digraph G {Hello->World}' | head -c 60Graphviz se ejecuta dentro de la puerta de enlace, por lo que un documento SVG aquí confirma que la propia puerta de enlace funciona correctamente. Ahora pruebe la ruta que atraviesa los contenedores.
curl -s -X POST http://127.0.0.1:8000/mermaid/svg \
-H 'Content-Type: text/plain' \
--data-binary 'graph TD; A-->B;' | head -c 60El SVG del segundo comando confirma que KROKI_MERMAID_HOST se resolvió y que el contenedor complementario respondió. Si el primero funciona y el segundo no, el fallo está entre los dos contenedores, así que revise docker compose logs kroki antes de modificar la sintaxis del diagrama.
El formulario GET codifica el diagrama en la URL. Así es como una wiki inserta una imagen sin ningún complemento. La documentación proporciona este codificador.
cat hello.dot | python -c "import sys; import base64; import zlib; print(base64.urlsafe_b64encode(zlib.compress(sys.stdin.read().encode('utf-8'), 9)).decode('ascii'))"En Ubuntu, eso muestra python: command not found, porque el sistema incluye python3 y no incluye ningún python sin versión. Use python3. La salida se coloca al final de una URL con el formato /{diagram-type}/{output-format}/{encoded-diagram}, y cualquier etiqueta <img> puede apuntar a ella. Existe un límite: KROKI_MAX_URI_LENGTH tiene un valor predeterminado de 4096 bytes, por lo que un diagrama largo debe enviarse mediante POST.
Kroki lee el texto que se le envía, por lo que la configuración de seguridad de Kroki es la que importa. KROKI_SAFE_MODE tiene como valor predeterminado SECURE, el nivel más restrictivo de los tres, y KROKI_PLANTUML_ALLOW_INCLUDE tiene como valor predeterminado false. Estos valores predeterminados existen porque la directiva !include de PlantUML lee archivos y URL desde el punto de vista del renderizador. Si los relaja en un endpoint accesible para cualquiera, habrá proporcionado a Internet un lector de archivos que se ejecuta dentro del contenedor. Déjelos sin cambios salvo que sepa qué ruta de inclusión necesita; en ese caso, especifíquela con KROKI_PLANTUML_INCLUDE_PATH.
Memoria: cuál consume más en un VPS pequeño
El orden es predecible cuando se sabe qué ejecuta cada contenedor.
- La imagen de Excalidraw ejecuta nginx para servir archivos estáticos. Es, con diferencia, la opción más económica de las tres.
- draw.io ejecuta Tomcat, un servidor de aplicaciones Java, por lo que mantiene una JVM (máquina virtual de Java) aunque nadie esté dibujando.
- La puerta de enlace de Kroki también es un servicio Java, distribuido como un archivo jar para instalaciones manuales.
- El complemento de mermaid es el que más recursos consume. Su Dockerfile instala Chromium y establece
PUPPETEER_EXECUTABLE_PATH=/usr/lib/chromium/chrome, porque Mermaid renderiza mediante un motor de navegador real.
Por tanto, las cifras en reposo dicen muy poco. La cifra importante es el pico mientras se renderiza un diagrama, y KROKI_MERMAID_MAX_CONCURRENCY tiene un valor predeterminado de 6, por lo que puede haber seis renderizados del navegador en curso a la vez. Mídalo en su propio servidor en lugar de confiar en una cifra publicada.
docker stats --no-stream
docker system dfEjecute el primero mientras todo está en reposo y, después, mientras renderiza en bucle un diagrama grande de mermaid. Si el pico resulta excesivo en un plan pequeño, limítelo en lugar de hacer suposiciones: configurar límites de memoria en un servicio de Compose muestra la sintaxis y lo que ocurre cuando un contenedor alcanza su límite. Eliminar el complemento de mermaid también es una opción válida, porque la puerta de enlace sigue sirviendo todos los renderizadores incluidos.
Ninguno de estos servicios incluye un modelo de usuarios, así que hay que poner uno delante
draw.io no tiene cuentas. Excalidraw no tiene cuentas. Kroki responde a cualquier solicitud que recibe. La autenticación debe proporcionarla el proxy.
sudo apt update && sudo apt install -y apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd alicehtpasswd -c crea el archivo y sobrescribe uno existente, así que pase -c la primera vez y nunca más.
server {
listen 443 ssl;
server_name drawio.example.com;
location / {
auth_basic "diagrams";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Aplíquelo con sudo nginx -t && sudo systemctl reload nginx. La parte nginx -t es la importante: si se recarga una configuración incorrecta, la configuración anterior sigue activa. Por tanto, el sitio continúa funcionando y el cambio no se aplica. La configuración del proxy inverso, explicada línea por línea describe el bloque de cabeceras y las rutas de los certificados que este fragmento omite.
La autenticación básica no es adecuada para Kroki, y conviene entender el motivo. Una página wiki inserta una imagen de Kroki con una etiqueta <img>. El navegador del lector solicita esa URL como un recurso secundario de origen cruzado. El navegador enviará las credenciales de Basic que ya tenga para el host de Kroki, pero los navegadores actuales no muestran un aviso de inicio de sesión para una imagen de origen cruzado. Por tanto, cualquier lector que no haya iniciado sesión directamente en el host de Kroki recibe un 401 y ve todos los diagramas de la página como imágenes rotas. En su lugar, mantenga Kroki fuera de Internet. Colóquelo en la misma red de Docker que el contenedor de la wiki y permita que la wiki acceda a él mediante el nombre del servicio, sin publicar ningún puerto en el host. Cómo las redes de Compose resuelven los nombres de servicio explica el mecanismo que permite hacerlo.
Diagramas junto a una wiki autoalojada
Este es el motivo habitual para querer algo así. Una página de la wiki necesita una imagen, y nadie quiere que esa imagen sea una captura de pantalla del portátil de otra persona.
BookStack tiene un punto de integración específico para un editor autoalojado. Su URL de inserción predeterminada es https://embed.diagrams.net/?embed=1&proto=json&spin=1&configure=1, y una línea en .env la dirige a tu contenedor.
DRAWIO=https://drawio.example.com/?embed=1&proto=json&spin=1&configure=1Copia la cadena de consulta exactamente. La documentación de BookStack indica que embed=1&proto=json&spin=1 «son necesarios para que la integración con BookStack funcione», porque seleccionan el protocolo de mensajes JSON que utilizan ambas páginas para comunicarse. La misma página remite a stealth=1 «si no quieres que se utilicen otros servicios externos». Esta es la opción que debes añadir si el objetivo del autoalojamiento era detener las llamadas salientes. Una vez configurado, BookStack guarda el dibujo en su propio almacenamiento de imágenes junto a la página. Por tanto, la copia de seguridad de la wiki que ya realizas también incluye la copia de seguridad de los diagramas.
Si todavía no has decidido qué wiki usar, resuelve eso primero. Elegir entre BookStack, Wiki.js y Outline es la decisión anterior, porque la wiki determina cómo se adjunta un diagrama a una página y, por tanto, cuál de estas herramientas debes añadir.
Modos de fallo y cadenas que aparecerán
El editor de diagramas se abre en BookStack y queda cargando indefinidamente. El indicador está spin=1 esperando los datos del diagrama. Con proto=json, el editor envía un evento init cuando está listo y espera una acción load que devuelva el diagrama. Por eso el indicador permanece visible cuando ese intercambio no se completa. Compruebe que embed=1&proto=json&spin=1 esté incluido en el valor de DRAWIO y que la parte del host no contenga errores tipográficos.
El marco del editor aparece vacío en una wiki HTTPS. La consola del navegador informa de contenido mixto al cargar http:// dentro de https://. El navegador bloquea el marco y draw.io nunca se ejecuta. Sirva el editor mediante HTTPS.
Kroki devuelve 413 Request Entity Too Large. Ese texto exacto es la forma en que nginx nombra el error, por lo que el proxy es el primer elemento que debe comprobar. Sin embargo, Kroki también puede enviar un 413 propio, que su documentación denomina Content Too Large. El valor predeterminado de nginx para client_max_body_size es 1 MB. Ese es el límite que alcanza una fuente PlantUML grande, porque PlantUML se procesa dentro de la gateway. El valor de KROKI_MAX_BODY_SIZE de Kroki también es 1mb de forma predeterminada, pero sólo existe en los contenedores auxiliares de diagrams.net, Excalidraw y Mermaid. Por tanto, una fuente Mermaid grande puede alcanzar cualquiera de los dos límites. Aumente primero client_max_body_size y aumente también KROKI_MAX_BODY_SIZE en el contenedor auxiliar cuando el diagrama pase por uno de ellos.
Mermaid falla, pero graphviz funciona. La gateway funciona correctamente y no se está llegando al contenedor auxiliar. Compruebe que el servicio esté activo con docker compose ps. Después, compruebe que KROKI_MERMAID_HOST coincida con el nombre del servicio, porque su valor predeterminado es 127.0.0.1. Dentro del contenedor de la gateway, ese valor hace referencia a la propia gateway.
La colaboración de Excalidraw nunca se conecta. Si compiló un frontend contra su propio servidor de salas y lo colocó detrás de nginx, el proxy debe actualizar la conexión mediante proxy_set_header Upgrade $http_upgrade; y proxy_set_header Connection "upgrade";. Sin ellos, la negociación websocket recibe una respuesta como una solicitud HTTP normal y la sesión nunca se inicia.
El lienzo aparece vacío después de limpiar el navegador. La escena estaba almacenada localmente en ese dispositivo y no existe una copia en el servidor. La solución es una práctica, no un ajuste: exporte el archivo .excalidraw para cualquier contenido que deba conservar.
FAQ
¿El alojamiento propio de draw.io mantiene mis diagramas privados?
Mantiene el código de la aplicación en su servidor, pero eso no equivale a mantener los datos privados. draw.io renderiza en el navegador, por lo que el contenedor nunca contiene un diagrama. La privacidad depende de dónde guarde el archivo y de qué conexiones salientes deje habilitadas. Use ?offline=1 para deshabilitar los destinos de almacenamiento en la nube y mantenga ENABLE_DRAWIO_PROXY desactivado. La exportación tampoco envía datos al exterior: la imagen no configura ningún servicio de exportación, por lo que el PDF pasa por el cuadro de diálogo de impresión del navegador y DRAWIO_SELF_CONTAINED deja de tener efecto.
¿Por qué no funciona la colaboración en mi Excalidraw alojado en mi propio servidor?
La página oficial de la imagen indica que el alojamiento propio "no admite las funciones de uso compartido ni de colaboración". La colaboración en tiempo real necesita el servidor websocket independiente excalidraw/excalidraw-room, y los enlaces compartidos necesitan un servicio de almacenamiento. Las direcciones de ambos se incorporan al paquete JavaScript durante la compilación como variables de Vite, como VITE_APP_WS_SERVER_URL, por lo que establecer una variable de entorno en el contenedor en ejecución no tiene ningún efecto. Para usar su propio servidor de salas, debe compilar el frontend desde el código fuente con sus valores.
¿Cómo renderizo diagramas Mermaid en mi propio servidor?
Ejecute Kroki con su contenedor complementario de mermaid y establezca KROKI_MERMAID_HOST con el nombre de ese servicio. Después, envíe el texto del diagrama mediante POST a /mermaid/svg y lea el SVG de la respuesta, o codifique el diagrama en una URL GET y apunte una etiqueta <img> a ella. El contenedor complementario controla Chromium mediante Puppeteer porque Mermaid necesita un motor de navegador, así que planifique el consumo de memoria: KROKI_MERMAID_MAX_CONCURRENCY permite seis renderizaciones simultáneas de forma predeterminada.
¿Necesito poner una contraseña delante de estas herramientas?
Sí, porque ninguna tiene cuentas de usuario. draw.io y Excalidraw entregan un editor completo a cualquiera que encuentre la URL, y Kroki renderiza cualquier texto que se le envíe. La autenticación básica en el reverse proxy es suficiente para los dos editores. En el caso de Kroki, manténgalo sin publicar en una red Docker compartida con el wiki, porque el navegador del lector no mostrará un aviso de inicio de sesión para un <img> en otro origen; por tanto, todos los diagramas incrustados dejarían de funcionar para cualquiera que no haya iniciado sesión directamente en Kroki.