draw.io, Excalidraw o Kroki: confronto self-hosted
Confronta draw.io, Excalidraw e Kroki su un VPS: scopri quali dati restano nel browser, quali passano dal server e quando il self-hosting protegge davvero la privacy.
Quale strumento per diagrammi self-hosted dovresti eseguire?
Gli strumenti per diagrammi self-hosted si dividono in due categorie, e questa differenza conta più dell’elenco delle funzionalità. draw.io ed Excalidraw sono applicazioni browser: il container fornisce JavaScript, il browser esegue il disegno e il server non vede mai il diagramma. Kroki funziona nel modo opposto. Gli invii il testo del diagramma tramite HTTP e restituisce un’immagine, quindi ogni diagramma passa attraverso il tuo server.
Esegui draw.io se vuoi un editor completo accanto a un wiki. Esegui Excalidraw se vuoi un blocco per schizzi rapido e accetti che non salvi nulla al di fuori del browser in cui hai creato il disegno. Esegui Kroki se i tuoi diagrammi sono testo archiviato in git accanto al codice che descrivono.
Cosa cambia realmente con il self-hosting di uno strumento per diagrammi
Siate precisi su quali componenti interagiscono con il vostro server, perché questo determina se il self-hosting offre privacy o soltanto disponibilità.
- draw.io esegue il rendering nel browser. Il vostro container serve il codice dell'applicazione. Il file viene salvato dove indicate nell'editor.
- Excalidraw esegue il rendering nel browser e conserva la scena corrente nello storage locale di quel browser. Sul server non viene scritto nulla.
- Kroki esegue il rendering sul server. Sia il sorgente del diagramma sia l'immagine risultante si trovano all'interno del vostro container.
Solo il terzo caso trasferisce i dati su hardware sotto il vostro controllo. Nei primi due casi, il self-hosting offre controllo sugli asset e disponibilità: il codice JavaScript proviene dal vostro host, quindi l'editor continua a funzionare quando un fornitore esterno subisce un'interruzione, modifica le proprie condizioni o non è raggiungibile dalla vostra rete. Per alcuni team questo ha un valore economico concreto. È un'affermazione diversa da «il diagramma non lascia mai l'azienda».
draw.io: un container ufficiale che non memorizza nulla
Il progetto pubblica una propria immagine e il quick start nel README consiste in una sola riga.
docker run -it --rm --name="draw" -p 8080:8080 -p 8443:8443 jgraph/drawioQuesto pubblica l'editor su tutti gli indirizzi disponibili sul server. Su un VPS, associa la porta pubblicata all'interfaccia loopback e raggiungilo tramite un reverse proxy o un tunnel SSH.
docker run -d --name drawio --restart unless-stopped -p 127.0.0.1:8080:8080 jgraph/drawioApri http://127.0.0.1:8080/?offline=1&https=0 tramite il tunnel. Il README definisce ?offline=1 «una funzionalità di sicurezza che disabilita il supporto per il cloud storage». Senza questa opzione, l'editor propone Google Drive, OneDrive e GitHub come destinazioni di salvataggio, cioè server gestiti da terzi.
L'associazione a 127.0.0.1 impedisce che la porta sia esposta a Internet. Un semplice -p 8080:8080 non viene filtrato da ufw, perché Docker inserisce le proprie regole iptables prima delle chain gestite da ufw. Il firewall risulta quindi configurato correttamente, ma la porta risponde a chiunque. Docker pubblica direttamente oltre ufw descrive il meccanismo e la correzione.
Non appena l'editor non è più esposto su localhost, diventano importanti due variabili d'ambiente.
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 slash finale non è un errore di battitura. Il README definisce DRAWIO_SERVER_URL come «URL pubblico della distribuzione con slash finale» e DRAWIO_BASE_URL come «lo stesso URL senza slash finale», usato dal viewer, dal lightbox e dai percorsi del codice di embed. Se pubblichi l'editor in un sottopercorso come https://www.example.com/drawio/, entrambi i valori devono includere quel sottopercorso, perché l'applicazione costruisce da questi valori gli URL del viewer e dell'embed.
Persistenza: non esiste, ed è una scelta progettuale. Quel file Compose non contiene alcun volume, perché il container non memorizza dati dei diagrammi. Un file .drawio è XML che l'editor invia al browser e la destinazione di salvataggio scelta determina dove viene memorizzato: un download sul tuo computer oppure l'applicazione che ha incorporato l'editor. Esegui il backup di quella destinazione. Se si tratta di una directory sul VPS, ciò che devi proteggere è quella directory e il file manager che usi per raggiungerla, perché draw.io non conserva alcuna copia.
Cosa continua a lasciare il server. Meno di prima, perché l'esportazione non viene più inviata altrove. Il README elenca ora EXPORT_URL e DRAWIO_SELF_CONTAINED tra le variabili rimosse, che non hanno alcun effetto. Queste variabili indirizzavano l'editor verso l'immagine separata jgraph/export-server, che «ha raggiunto la fine del ciclo di vita». Ora l'entrypoint scrive window.EXPORT_URL = null in PreConfig.js, indicando all'editor che non esiste alcun servizio di esportazione. In pratica, Esporta come > PDF apre la finestra di stampa del browser. Per il PDF non viene quindi offerta l'opzione di includere una copia del diagramma e l'esportazione PNG e JPEG eseguita dal server è nascosta. L'esportazione PNG, JPEG e SVG eseguita dal browser continua a funzionare. ENABLE_DRAWIO_PROXY è disabilitato per impostazione predefinita e abilita un endpoint /proxy che recupera URL di immagini esterne per conto del browser; lascialo disabilitato, salvo necessità specifiche.
Excalidraw: un bundle statico senza server sottostante
La pagina dell'immagine ufficiale fornisce questo comando.
docker run --rm -dit --name excalidraw -p 5000:80 excalidraw/excalidraw:latestSposta la porta pubblicata su loopback per lo stesso motivo indicato in precedenza.
docker run -d --name excalidraw --restart unless-stopped -p 127.0.0.1:5000:80 excalidraw/excalidraw:latestAll'interno del container, nginx pubblica un bundle JavaScript compilato sulla porta 80. L'immagine pubblicata occupa circa 41 MB compressi (Docker Hub, ottobre 2026), quindi il suo contenuto è minimo. Non ci sono database, session store o directory per gli upload, perché non c'è nulla da memorizzare sul server.
La pagina dell'immagine indica chiaramente il limite: "Al momento, l'hosting autonomo di una propria istanza non supporta le funzionalità di condivisione o collaborazione." I pulsanti sono comunque presenti nell'interfaccia, quindi è utile conoscerne il motivo. La collaborazione in tempo reale richiede un server websocket, pubblicato separatamente come excalidraw/excalidraw-room. Un link di condivisione richiede un servizio di storage che conservi la scena crittografata. Gli indirizzi di entrambi vengono compilati nel bundle al momento della build come variabili Vite (VITE_APP_WS_SERVER_URL, VITE_APP_BACKEND_V2_GET_URL, VITE_APP_BACKEND_V2_POST_URL), e i valori di produzione nel repository puntano ai servizi ospitati da Excalidraw. Vite sostituisce questi valori durante la build, quindi nel JavaScript finiscono come stringhe letterali. Impostarli come variabili d'ambiente del container non cambia nulla, perché nessun codice li legge a runtime. Per fare puntare la collaborazione al proprio room server è necessario compilare il frontend dai sorgenti usando i propri valori. Verifica lo stato di quel server prima di basarci il progetto: a ottobre 2026, l'ultimo push dell'immagine excalidraw/excalidraw-room su Docker Hub risaliva a dicembre 2023.
Dove risiede realmente un disegno. La scena si trova nel local storage del browser, su quel dispositivo e per quell'origine. Apri lo stesso URL in una finestra privata: il canvas sarà vuoto. È il modo più rapido per verificarlo. La cancellazione dei dati del sito elimina il disegno e non esiste una copia sul server da cui ripristinarlo. Insegna quindi agli utenti a usare "Salva in..." e a conservare il file .excalidraw, che è in formato JSON, in una posizione inclusa nei backup. Un'istanza condivisa assegna a ogni persona il proprio canvas privato. Considerala un blocco da disegno personale ospitato su un server.
Kroki: diagrammi come codice, renderizzati sul server
Kroki è un gateway HTTP che si trova davanti a numerosi renderer. Si invia testo con una richiesta POST e si riceve un file SVG o PNG. Graphviz, PlantUML, D2 e diversi altri renderer sono inclusi nell'immagine del gateway. Il rendering di Mermaid, BPMN ed Excalidraw viene eseguito in container complementari, quindi Compose è la scelta più adatta per eseguirlo. Questo è l'esempio riportato nella documentazione di 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 non pubblica nulla sull'host, quindi i container complementari sono raggiungibili soltanto dal gateway tramite la rete Compose. È il comportamento desiderato. Modifica la riga del gateway in "127.0.0.1:8000:8000", a meno che il wiki che lo utilizza non sia eseguito su un host diverso. Se non hai mai scritto un file Compose su un server, eseguire Docker Compose su un VPS illustra la struttura dei file e il ciclo docker compose up -d.
Esegui due smoke test, nell'ordine indicato, perché possono fallire per motivi diversi.
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 viene eseguito all'interno del gateway, quindi un documento SVG ottenuto qui dimostra che il gateway stesso è operativo. Ora verifica il percorso che attraversa i container.
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 60L'SVG restituito dal secondo comando dimostra che KROKI_MERMAID_HOST è stato risolto e che il container complementare ha risposto. Se il primo test funziona e il secondo no, il problema si trova tra i due container, quindi consulta docker compose logs kroki prima di intervenire sulla sintassi del diagramma.
Il metodo GET codifica il diagramma nell'URL. È il metodo usato da un wiki per incorporare un'immagine senza alcun plugin. La documentazione fornisce questo encoder.
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'))"Su Ubuntu viene visualizzato python: command not found, perché il sistema include python3 ma non python senza numero di versione. Usa python3. L'output viene aggiunto alla fine di un URL con il formato /{diagram-type}/{output-format}/{encoded-diagram}, e qualsiasi tag <img> può puntare a quell'URL. Esiste un limite: KROKI_MAX_URI_LENGTH è impostato per impostazione predefinita su 4096 byte, quindi un diagramma lungo deve essere inviato tramite POST.
Kroki legge il testo che gli viene inviato, quindi le impostazioni di sicurezza da controllare sono quelle di Kroki. KROKI_SAFE_MODE è impostato per impostazione predefinita su SECURE, il livello più restrittivo dei tre, mentre KROKI_PLANTUML_ALLOW_INCLUDE è impostato per impostazione predefinita su false. Queste impostazioni predefinite esistono perché la direttiva !include di PlantUML legge file e URL dal punto di vista del renderer. Se le allenti su un endpoint raggiungibile da chiunque, dai a Internet un lettore di file eseguito all'interno del container. Lasciale invariate, a meno che tu non sappia quale percorso di inclusione ti serve; in quel caso, indicalo con KROKI_PLANTUML_INCLUDE_PATH.
Memoria: quale componente pesa di più su un VPS piccolo
L’ordine è prevedibile, se si sa cosa esegue ogni container.
- L’immagine Excalidraw usa nginx per servire file statici. È di gran lunga la più economica delle tre.
- draw.io esegue Tomcat, un application server Java, quindi utilizza una JVM (Java virtual machine) anche quando nessuno sta disegnando.
- Anche il gateway Kroki è un servizio Java, distribuito come jar per le installazioni manuali.
- Il companion mermaid è il componente più costoso. Il relativo Dockerfile installa Chromium e imposta
PUPPETEER_EXECUTABLE_PATH=/usr/lib/chromium/chrome, perché Mermaid esegue il rendering in un motore browser reale.
I valori a riposo, quindi, dicono poco. Il dato importante è il picco durante il rendering di un diagramma, e KROKI_MERMAID_MAX_CONCURRENCY ha valore predefinito 6: possono quindi essere eseguiti contemporaneamente sei rendering nel browser. Misurare sul proprio server è preferibile rispetto ad affidarsi a un valore pubblicato.
docker stats --no-stream
docker system dfEseguire il primo comando quando tutto è inattivo, quindi ripeterlo mentre si esegue in ciclo il rendering di un diagramma Mermaid di grandi dimensioni. Se il picco è eccessivo su un piano piccolo, impostare un limite invece di procedere a tentativi: impostare limiti di memoria su un servizio Compose mostra la sintassi e il comportamento del container quando raggiunge il limite. Anche rimuovere il companion mermaid è una scelta valida, perché il gateway continua a servire tutti i renderer integrati.
Nessuno di questi strumenti fornisce un sistema di gestione degli utenti, quindi è necessario anteporre un proxy
draw.io non gestisce account. Excalidraw non gestisce account. Kroki risponde a qualsiasi richiesta che lo raggiunga. L'autenticazione deve quindi essere gestita dal proxy.
sudo apt update && sudo apt install -y apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd alicehtpasswd -c crea il file e sovrascrive quello esistente, quindi usa -c la prima volta e non ripeterlo.
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;
}
}Applica la configurazione con sudo nginx -t && sudo systemctl reload nginx. La parte nginx -t è quella importante: se la configurazione non è valida, un reload lascia in esecuzione la configurazione precedente. Il sito continua quindi a funzionare, ma la modifica non è attiva. La configurazione del reverse proxy, spiegata riga per riga illustra il blocco degli header e i percorsi dei certificati che questo frammento omette.
L'autenticazione di base è lo strumento sbagliato per Kroki, e il motivo è importante. Una pagina wiki incorpora un'immagine Kroki con un tag <img>. Il browser del lettore recupera quell'URL come risorsa secondaria cross-origin. Invia le credenziali Basic già memorizzate per l'host Kroki, ma i browser attuali non mostrano una richiesta di autenticazione per un'immagine cross-origin. Di conseguenza, chi non ha eseguito direttamente l'accesso all'host Kroki riceve un errore 401 e visualizza ogni diagramma della pagina come un'immagine non disponibile. Mantieni invece Kroki fuori dalla rete Internet pubblica. Collegalo alla stessa rete Docker del container wiki e consenti alla wiki di raggiungerlo tramite il nome del servizio, senza pubblicare alcuna porta sull'host. Come le reti Compose risolvono i nomi dei servizi spiega il meccanismo che rende possibile questa configurazione.
Diagrammi affiancati a una wiki self-hosted
Questo è il motivo più comune per cui si sceglie una soluzione di questo tipo. Una pagina wiki ha bisogno di un'immagine e nessuno vuole che sia una schermata catturata dal portatile di qualcuno.
BookStack offre un'integrazione nativa per un editor self-hosted. Il relativo URL di incorporamento predefinito è https://embed.diagrams.net/?embed=1&proto=json&spin=1&configure=1 e una riga in .env lo reindirizza al container.
DRAWIO=https://drawio.example.com/?embed=1&proto=json&spin=1&configure=1Copia la stringa di query esattamente. La documentazione di BookStack specifica che embed=1&proto=json&spin=1 "sono necessari affinché l'integrazione con BookStack funzioni", perché selezionano il protocollo dei messaggi JSON usato dalle due pagine per comunicare. La stessa pagina indica stealth=1 "se non vuoi usare altri servizi esterni": è l'opzione da aggiungere quando l'obiettivo del self-hosting è impedire le chiamate in uscita. Dopo questa configurazione, BookStack salva il disegno nel proprio archivio delle immagini, accanto alla pagina. Di conseguenza, il backup della wiki che esegui già include anche il backup dei diagrammi.
Se non hai ancora scelto la wiki, risolvi prima questo punto. Scelta tra BookStack, Wiki.js e Outline è la decisione da prendere per prima, perché la wiki determina il modo in cui un diagramma viene allegato a una pagina e quindi quale di questi strumenti integrare.
Modalità di errore e stringhe visualizzate
L'editor di disegni si apre in BookStack e resta in caricamento. Lo spinner attende i dati del diagramma tramite spin=1. Con proto=json l'editor invia un evento init quando è pronto e si aspetta in risposta un'azione load contenente il diagramma. Lo spinner rimane quindi visualizzato quando questo scambio non viene completato. Verificare che embed=1&proto=json&spin=1 sia presente nel valore DRAWIO e che la parte relativa all'host non contenga errori di battitura.
Il frame dell'editor rimane vuoto in un wiki HTTPS. La console del browser segnala contenuti misti durante il caricamento di http:// all'interno di https://. Il browser blocca il frame e draw.io non viene mai eseguito. Pubblicare l'editor tramite HTTPS.
Kroki restituisce 413 Request Entity Too Large. Questa formulazione esatta è quella usata da nginx per indicare l'errore, quindi il proxy è il primo elemento da controllare. Tuttavia, anche Kroki può generare autonomamente un errore 413, che nella documentazione viene chiamato Content Too Large. Il valore predefinito di nginx per client_max_body_size è 1 MB. È questo il limite raggiunto da un sorgente PlantUML di grandi dimensioni, perché PlantUML esegue il rendering all'interno del gateway. Anche KROKI_MAX_BODY_SIZE di Kroki ha come valore predefinito 1mb, ma questa impostazione esiste solo nei container complementari diagrams.net, Excalidraw e Mermaid. Un sorgente Mermaid di grandi dimensioni può quindi raggiungere uno dei due limiti. Aumentare prima client_max_body_size. Quando il diagramma passa attraverso un container complementare, aumentare anche KROKI_MAX_BODY_SIZE su quel container.
Mermaid non funziona mentre graphviz funziona. Il gateway è operativo, ma il container complementare non viene raggiunto. Verificare che il servizio sia attivo con docker compose ps. Controllare quindi che KROKI_MERMAID_HOST corrisponda al nome del servizio. Il valore predefinito è 127.0.0.1, che all'interno del container del gateway indica il gateway stesso.
La collaborazione in Excalidraw non stabilisce mai la connessione. Se è stato creato un frontend collegato al proprio room server e lo si è pubblicato dietro nginx, il proxy deve effettuare l'upgrade della connessione con proxy_set_header Upgrade $http_upgrade; e proxy_set_header Connection "upgrade";. Senza queste impostazioni, l'handshake WebSocket riceve una risposta come normale richiesta HTTP e la sessione non viene avviata.
La tela è vuota dopo la pulizia del browser. La scena era memorizzata nello storage locale di quel dispositivo e non esiste una copia sul server. La soluzione consiste in un'abitudine, non in un'impostazione: esportare il file .excalidraw per tutto ciò che deve essere conservato.
FAQ
Il self-hosting di draw.io mantiene privati i miei diagrammi?
Mantiene il codice dell'applicazione sul server, ma questo non equivale a mantenere privati i dati. draw.io esegue il rendering nel browser, quindi il container non contiene mai alcun diagramma. La privacy dipende quindi dal percorso in cui si salva il file e dalle chiamate in uscita che si lasciano abilitate. Usare ?offline=1 per disabilitare le destinazioni di archiviazione cloud e lasciare ENABLE_DRAWIO_PROXY disabilitato. Anche l'esportazione non invia dati all'esterno: l'immagine non configura alcun servizio di esportazione, quindi il PDF passa dalla finestra di stampa del browser e DRAWIO_SELF_CONTAINED non ha più alcun effetto.
Perché la collaborazione non funziona nel mio Excalidraw self-hosted?
La pagina dell'immagine ufficiale specifica che il self-hosting "non supporta le funzionalità di condivisione o collaborazione". La collaborazione in tempo reale richiede il server websocket separato excalidraw/excalidraw-room, mentre i link di condivisione richiedono un servizio di archiviazione. Gli indirizzi di entrambi vengono compilati nel bundle JavaScript durante la build, come variabili Vite quali VITE_APP_WS_SERVER_URL, quindi impostare una variabile d'ambiente nel container in esecuzione non ha alcun effetto. Per usare un proprio server delle stanze, è necessario compilare il frontend dal codice sorgente con i propri valori.
Come posso eseguire il rendering dei diagrammi Mermaid sul mio server?
Eseguire Kroki insieme al container companion per Mermaid e impostare KROKI_MERMAID_HOST sul nome di quel servizio. In seguito, inviare con POST il testo del diagramma a /mermaid/svg e leggere l'SVG dalla risposta, oppure codificare il diagramma in un URL GET e indicarlo con un tag <img>. Il companion controlla Chromium tramite Puppeteer perché Mermaid richiede un motore browser; occorre quindi considerare il consumo di memoria: KROKI_MERMAID_MAX_CONCURRENCY è impostato per impostazione predefinita su sei rendering simultanei.
Devo configurare una password davanti a questi strumenti?
Sì, perché nessuno di questi strumenti dispone di account. draw.io ed Excalidraw offrono un editor completo a chiunque trovi l'URL, mentre Kroki esegue il rendering di qualunque testo gli venga inviato. Per i due editor è sufficiente l'autenticazione di base sul reverse proxy. Per Kroki, mantenerlo non pubblicato su una rete Docker condivisa con il wiki, perché il browser del lettore non visualizzerà una richiesta di accesso per un <img> su un'altra origine; di conseguenza, ogni diagramma incorporato non funzionerebbe per chi non ha effettuato direttamente l'accesso a Kroki.