draw.io, Excalidraw o Kroki: comparación autoalojada
Compara draw.io, Excalidraw y Kroki en un VPS: descubre qué datos llegan al servidor y por qué el modelo de ejecución determina la privacidad real.
¿Qué herramienta de diagramas autoalojada debe ejecutar?
Las herramientas de diagramas autoalojadas tienen dos modelos, y el modelo importa más que la lista de funciones. draw.io y Excalidraw son aplicaciones web: el contenedor sirve JavaScript, el navegador realiza el dibujo y el servidor nunca ve el diagrama. Kroki funciona al contrario. Se le envía texto de diagrama mediante HTTP y devuelve una imagen, por lo que cada diagrama pasa por su propio servidor.
Ejecute draw.io si quiere un editor completo junto a una wiki. Ejecute Excalidraw si quiere un bloc para hacer esquemas rápidos 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 alojar una herramienta de diagramas
Sea exacto sobre qué componentes interactúan con su servidor, porque ese único dato determina si el alojamiento propio aporta privacidad o sólo disponibilidad.
- draw.io se ejecuta en el navegador. Su contenedor sirve el código de la aplicación. El archivo se guarda donde indique en el editor.
- Excalidraw se ejecuta en el navegador y mantiene la escena actual en el almacenamiento local de ese navegador. No se escribe nada en el servidor.
- Kroki se ejecuta en el servidor. Tanto el código fuente del diagrama como la imagen generada existen dentro de su contenedor.
Sólo el tercer caso mueve los datos a hardware bajo su control. En los dos primeros, el alojamiento propio aporta control de los recursos y disponibilidad: JavaScript se sirve desde 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. Esos destinos son servidores de terceros.
Vincular el puerto 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 parece estar configurado correctamente, 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 estar 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 pública de despliegue con una barra final» y DRAWIO_BASE_URL como la «misma URL sin una barra final». El visor, la lightbox y las rutas del código de inserción usan 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 genera a partir de ellos las URL del visor y del código de inserción.
Persistencia: no existe, y ese es el diseño. 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 insertó el editor. Haga una copia de seguridad de ese destino. Si el destino es una carpeta del VPS, lo que debe proteger es esa carpeta y el gestor de archivos que utilice para acceder a ella, ya que draw.io no conserva ninguna copia.
Qué sigue saliendo de su servidor. La exportación a PDF es el caso más claro. El README describe DRAWIO_SELF_CONTAINED como «Establecerlo en 1 para encaminar las solicitudes de exportación a través de ExportProxyServlet de Tomcat (/service/0) en lugar de llamar directamente al servidor de exportación». Léalo al revés: de forma predeterminada, una solicitud de exportación no permanece dentro de su despliegue. El proyecto también publica jgraph/export-server, un «servidor independiente de exportación de imágenes de draw.io», para quienes quieren realizar esa renderización en su propio hardware. ENABLE_DRAWIO_PROXY está desactivado de forma predeterminada y habilita un endpoint /proxy que obtiene URL de imágenes externas en nombre del navegador. Déjelo 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:latestMueva el puerto publicado a loopback por la misma razón 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 unos 41 MB comprimidos (Docker Hub, agosto de 2026), lo que indica lo poco que contiene. No hay 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 el límite: "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 de JavaScript. Establecerlas como variables de entorno del contenedor no cambia nada, porque ningún código las lee durante la ejecución. Apuntar la colaboración a su propio servidor de salas implica 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: la imagen excalidraw/excalidraw-room de Docker Hub no se había vuelto a compilar en más de dos años en agosto de 2026.
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 estará 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, renderizados en el servidor
Kroki es una puerta de enlace HTTP delante de varios renderizadores. Se envía texto mediante POST y se recibe un SVG o PNG. Graphviz, PlantUML, D2 y varios renderizadores más están incluidos en la imagen de la puerta de enlace. El renderizado de Mermaid, BPMN y Excalidraw se ejecuta 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 por "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 de 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í demuestra 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 demuestra que KROKI_MERMAID_HOST se resolvió y que el contenedor complementario respondió. Si el primero funciona y el segundo no, el problema está entre los dos contenedores. Por tanto, lea docker compose logs kroki antes de revisar la sintaxis del diagrama.
El método 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 añade al final de una URL con el formato /{diagram-type}/{output-format}/{encoded-diagram}, y cualquier etiqueta <img> puede apuntar a ella. Hay 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 sus ajustes de seguridad son los que importan. KROKI_SAFE_MODE tiene el valor predeterminado SECURE, el nivel más restrictivo de los tres, y KROKI_PLANTUML_ALLOW_INCLUDE tiene el 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 relaja estas opciones en un endpoint accesible para cualquiera, habrá entregado a Internet un lector de archivos que se ejecuta dentro de su contenedor. Déjelas sin cambios salvo que sepa qué ruta de inclusión necesita. En ese caso, especifíquela con KROKI_PLANTUML_INCLUDE_PATH.
Memoria: qué componente consume más en un VPS pequeño
El orden es predecible cuando sabe qué ejecuta cada contenedor.
- La imagen de Excalidraw ejecuta nginx para servir archivos estáticos. Es, con diferencia, la opción que menos recursos consume 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 representa los diagramas en un motor de navegador real.
Por tanto, las cifras en reposo aportan muy poca información. La cifra importante es el pico mientras se representa un diagrama, y KROKI_MERMAID_MAX_CONCURRENCY tiene el valor predeterminado 6, por lo que puede haber seis representaciones del navegador en curso al mismo tiempo. Mídalo en su propio servidor en lugar de confiar en una cifra publicada.
docker stats --no-stream
docker system dfEjecute el primero cuando todo esté en reposo y, después, mientras representa 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 seguirá sirviendo todos los renderizadores integrados.
Ninguno de estos servicios incluye un modelo de usuarios, así que debe colocar uno delante
draw.io no tiene cuentas. Excalidraw no tiene cuentas. Kroki responde a cualquier solicitud que recibe. El proxy debe proporcionar el inicio de sesión.
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 la configuración no es válida, una recarga mantiene activa la configuración anterior. El sitio sigue funcionando, pero 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 subrecurso y no envía sus credenciales a un origen diferente. Por eso, la solicitud devuelve 401 y todos los diagramas de la página se muestran como imágenes rotas. En su lugar, mantenga Kroki fuera de Internet público. Colóquelo en la misma red 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 autohospedada
Este es el motivo habitual por el que se necesita 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 ofrece integración directa con un editor autohospedado. 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 cambia al contenedor.
DRAWIO=https://drawio.example.com/?embed=1&proto=json&spin=1&configure=1Copie 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 recomienda stealth=1 «si no quiere utilizar otros servicios externos». Esta es la opción que debe añadir si el objetivo del autohospedaje era detener las llamadas salientes. Con esta configuración, 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 realiza también incluye la copia de seguridad del diagrama.
Si todavía no ha decidido qué wiki utilizar, resuelva primero esa cuestión. Cómo 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 debe añadir.
Modos de fallo y cadenas que verá
El editor de diagramas se abre en BookStack y gira indefinidamente. El indicador de carga está spin=1 esperando un handshake que nunca llega. Compruebe que embed=1&proto=json&spin=1 esté presente en el valor de DRAWIO y que la parte del host no contenga errores tipográficos.
El marco del editor permanece en blanco 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. Esa cadena procede de nginx, no de Kroki. El valor predeterminado de client_max_body_size en nginx es 1 MB y el valor predeterminado de KROKI_MAX_BODY_SIZE en Kroki es 1mb, por lo que una fuente PlantUML grande alcanza el límite que sea menor. Aumente ambos valores.
Mermaid falla mientras graphviz funciona. El gateway está operativo, pero no se está accediendo al servicio auxiliar. Compruebe que el servicio esté activo con docker compose ps y, después, verifique que KROKI_MERMAID_HOST coincida con el nombre del servicio, porque su valor predeterminado es 127.0.0.1, que dentro del contenedor del gateway significa el propio gateway.
La colaboración de Excalidraw nunca 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, el handshake de websocket recibe una respuesta como una solicitud HTTP normal y la sesión nunca se inicia.
El lienzo está vacío después de limpiar el navegador. La escena estaba almacenada en el almacenamiento local de ese dispositivo y no existe una copia en el servidor. La solución es un hábito, 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 procesa los diagramas en el navegador, por lo que el contenedor nunca contiene ningún diagrama. La privacidad depende entonces de dónde guarde el archivo y de qué llamadas salientes mantenga habilitadas. Use ?offline=1 para deshabilitar los destinos de almacenamiento en la nube y recuerde que las solicitudes de exportación se envían a un servidor de exportación, a menos que configure DRAWIO_SELF_CONTAINED=1 y ejecute jgraph/export-server por su cuenta.
¿Por qué no funciona la colaboración en mi Excalidraw alojado por mí?
La página de la imagen oficial 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 bundle de JavaScript durante la compilación como variables de Vite, por ejemplo 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 de Mermaid en mi propio servidor?
Ejecute Kroki con su contenedor complementario de Mermaid y configure 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> hacia ella. El contenedor complementario controla Chromium mediante Puppeteer porque Mermaid necesita un motor de navegador, así que planifique la memoria: KROKI_MERMAID_MAX_CONCURRENCY tiene como valor predeterminado seis renderizaciones simultáneas.
¿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 la wiki, porque una solicitud <img> desde el navegador de un lector no incluirá las credenciales al dirigirse a otro origen y todos los diagramas integrados dejarían de funcionar.