Skip to content

Self Hosting

David Benson edited this page Oct 7, 2026 · 1 revision

This page covers running your own draw.io instance: plain static hosting, the official Docker image, the .war on a servlet container, and the desktop app. It then describes the configuration hooks a deployment uses, the server endpoints the client calls and which of them exist in this repository, offline use, Content-Security-Policy and keeping an instance up to date. For end-user documentation of the Docker image see Run your own draw.io server with Docker.

Choose a deployment

Option Server code Suits
Static files on any web server, including GitHub Pages None Editing with device and browser storage, no sign-in to cloud storage
Docker image jgraph/drawio Tomcat with the .war Most self-hosters: configured with environment variables, optional Google Drive, OneDrive and GitLab
The .war on your own servlet container The servlets in src/main/server/java Existing Java infrastructure
Desktop app None Single users, fully offline

In all cases the editor runs in the browser. Diagrams are stored where the user chooses (their device, the browser, or a cloud storage service they sign in to); the instance itself does not store diagrams.

Static hosting

src/main/webapp is a complete static site. Copy it to any web server, or serve it from a clone, and open index.html. All paths are relative, so a sub-path works too. No build is needed because the minified bundles are committed (see Building if you change the source).

The README's GitHub Pages route is the simplest example: fork the repository, publish the dev branch with GitHub Pages and open https://<your-user>.github.io/drawio/src/main/webapp/index.html (step by step in Getting started).

Without the servlets you lose the features that need a server: signing in to Google Drive, OneDrive, GitHub and GitLab, the /proxy for loading files, images and fonts from other hosts, and anything listed as "not in this repository" under Server endpoints.

Docker

jgraph/docker-drawio publishes the jgraph/drawio image. Its build clones this repository, runs ant war with JDK 11 and deploys the result as the root application of tomcat:9.0-jdk11-temurin. Images are tagged latest and with the draw.io version (for example jgraph/drawio:32.3.0), so you can pin a release.

docker run -it --rm --name="draw" -p 8080:8080 -p 8443:8443 jgraph/drawio

Then open http://localhost:8080/ or, with the generated self-signed certificate, https://localhost:8443/. Adding ?offline=1 disables the cloud storage integrations.

At startup the container's docker-entrypoint.sh turns environment variables into configuration: it rewrites js/PreConfig.js, appends to js/PostConfig.js, writes the OAuth credentials into files under WEB-INF, and adjusts Tomcat's server.xml. Whatever you do not set stays disabled: the entrypoint always sets sync=manual, EXPORT_URL = null and EditorUi.enableLogging = false, turns off Dropbox, GitHub and Trello, and turns off Google Drive, OneDrive and GitLab unless their credentials are given.

The variables that matter most (the README has the full list and details):

Variable Purpose
DRAWIO_SERVER_URL Public URL of the deployment, with a trailing slash. A sub-path also sets the Tomcat context path
DRAWIO_BASE_URL The same without the trailing slash; derived from DRAWIO_SERVER_URL if unset
DRAWIO_VIEWER_URL, DRAWIO_LIGHTBOX_URL Your own viewer script and lightbox host, see Global variables
DRAWIO_CONFIG, DRAWIO_CONFIG_FILE The editor configuration as JSON, inline or from a mounted file; written to window.DRAWIO_CONFIG (see Configuration)
DRAWIO_LANG Default UI language when the URL has no lang and the user has not chosen one
DRAWIO_CSP_HEADER Replaces the default Content-Security-Policy that the entrypoint injects as a <meta> tag
ENABLE_DRAWIO_PROXY 1 enables the /proxy endpoint (off by default)
DRAWIO_GOOGLE_CLIENT_ID, its secret and DRAWIO_GOOGLE_APP_ID Google Drive with your own OAuth client
DRAWIO_MSGRAPH_CLIENT_ID, its secret and DRAWIO_MSGRAPH_TENANT_ID OneDrive with your own Azure app
DRAWIO_GITLAB_ID, DRAWIO_GITLAB_SECRET, DRAWIO_GITLAB_URL GitLab, including self-managed GitLab
LETS_ENCRYPT_ENABLED, PUBLIC_DNS, KEYSTORE_PASS, KEY_PASS TLS: Let's Encrypt or the self-signed certificate
DRAWIO_USE_HTTP 1 builds the OAuth redirect URIs with http:// and drops the Secure cookie flag. Insecure; only for setups without TLS where you accept that risk

The image runs as the non-root user tomcat. To run it under another UID, that user needs group 0, otherwise the entrypoint skips all runtime configuration; a read-only root file system is not supported because the configuration is written into the web application at startup. The EXPORT_URL and DRAWIO_SELF_CONTAINED variables of older images no longer have any effect.

How to register the OAuth apps for each storage service is described in Storage backends and in the Docker README.

The .war on a servlet container

Download draw.war from the releases page, or build it with ant war (Building). Then:

  1. Deploy it to a container that implements the javax.servlet API with web.xml version 3.1, such as Tomcat 9 (the version the Docker image uses). Containers built on the jakarta.servlet namespace (Tomcat 10 and later) cannot load these servlets unchanged.
  2. Configure the client by editing js/PreConfig.js and js/PostConfig.js in the deployed application, as described below.
  3. If you use the cloud storage integrations, put your OAuth client IDs and secrets into the matching files in WEB-INF (google_client_id, msgraph_client_id, gitlab_client_id, github_client_id and the secret file next to each). The repository ships them with placeholder text; the servlets read them through SecretFacade.getSecret.
  4. Set environment variables for the servlets as needed:
Variable Read by Effect
ENABLE_DRAWIO_PROXY ProxyServlet 1 enables /proxy; otherwise it answers 404
DRAWIO_PROXY_ALLOWED_PORTS Utils Comma-separated ports the proxy may connect to. Without it, only URLs without an explicit port are allowed
ALLOW_INTERNAL_10_NET Utils 1 lets the proxy reach 10.x.x.x addresses. Loopback, link-local and the other private and reserved ranges stay blocked
EXPORT_URL ExportProxyServlet Base URL that /export and /service/* forward to. There is no export service in this repository
DRAWIO_USE_HTTP OAuth servlets See the Docker table above
DRAWIO_MEMCACHED_ENDPOINT CacheFacade Shares the OAuth state cache between several instances through a memcached JCache provider, whose jar you must add to WEB-INF/lib (it is not shipped)

Set DRAWIO_BASE_URL in PreConfig.js to the public URL of the application (for example https://example.com/draw when draw.war is deployed under its own name); otherwise the links the editor generates point at app.diagrams.net. DRAWIO_SERVER_URL defaults to the directory of the page and usually needs no change.

Desktop app

draw.io Desktop is an Electron application for Windows, macOS and Linux built from jgraph/drawio-desktop, which includes this repository as a git submodule. It needs no server and works offline. See Desktop app.

Configuration hooks: PreConfig.js and PostConfig.js

A deployment is configured through two small scripts in src/main/webapp/js that you edit (or, with Docker, that the entrypoint writes). In production mode bootstrap.js loads them on every host except *.draw.io and *.diagrams.net; in development mode (?dev=1) it always loads them.

File Runs Use it for
js/PreConfig.js After bootstrap.js has parsed urlParams, before Init.js and any application code Setting global variables such as DRAWIO_BASE_URL, EXPORT_URL and DRAWIO_CONFIG, and setting or forcing URL parameters (urlParams[...])
js/PostConfig.js After app.min.js (in development mode, after all sources), before App.main runs. shapes-14-6-5.min.js, stencils.min.js and extensions.min.js load later, inside App.main Overriding defaults that Init.js has already filled in, static flags such as Editor.enableCustomGitLabUrl, and prototype methods

The repository's PreConfig.js sets DRAWIO_PUBLIC_BUILD = true, EXPORT_URL = null (PDF export through the print dialog), placeholders for DRAWIO_BASE_URL, DRAWIO_VIEWER_URL, DRAWIO_LIGHTBOX_URL and DRAWIO_CONFIG, DRAW_MATH_URL = 'math4/es5' and urlParams['sync'] = 'manual'. Its PostConfig.js sets ICONSEARCH_PATH and ICON_SERVICE_PATH to null.

Init.js fills in most variables with window.X = window.X || default, so setting one of them to null in PreConfig.js has no effect: the default replaces it. To disable such a variable, set it to null in PostConfig.js, which is why ICONSEARCH_PATH is cleared there. EXPORT_URL is the exception: Init.js only applies its default when the variable is undefined, so null in PreConfig.js turns server export off.

A typical PreConfig.js for an instance at https://drawio.example.com:

window.DRAWIO_PUBLIC_BUILD = true;
window.DRAWIO_BASE_URL = 'https://drawio.example.com';
window.DRAWIO_LIGHTBOX_URL = 'https://drawio.example.com';
window.DRAWIO_VIEWER_URL = 'https://drawio.example.com/js/viewer-static.min.js';
window.EXPORT_URL = null;
window.DRAW_MATH_URL = 'math4/es5';
window.DRAWIO_CONFIG = {"defaultFonts": ["Helvetica", "Verdana"]};
urlParams['sync'] = 'manual';

Global variables

These are the window variables read at startup, mostly defaulted in js/diagramly/Init.js and js/grapheditor/Init.js (the draw.io Init.js runs first). Set them in PreConfig.js, or in PostConfig.js to clear them as explained above.

Deployment URLs

Variable Default on your host Used for
DRAWIO_BASE_URL https://app.diagrams.net Absolute base URL of the deployment, without a trailing slash. Used for the links the editor generates and for some script and image URLs in exported HTML. Set it, or those point at app.diagrams.net
DRAWIO_SERVER_URL The directory of the page, with a trailing slash Base for the server endpoints (proxy, save, the OAuth servlets) and for loading js/extensions.min.js and js/orgchart.min.js
DRAWIO_LIGHTBOX_URL https://viewer.diagrams.net Host of the lightbox (?lightbox=1) used for lightbox links and in exported and embedded HTML
DRAWIO_VIEWER_URL null (then js/viewer-static.min.js on the lightbox or base host) Viewer script referenced by exported and embedded HTML
EXPORT_URL null from PreConfig.js Server-side export service. null means none: PDF export uses the print dialog. It cannot be set from a URL parameter (GHSA-ff67-v9r9-6877)
PROXY_URL DRAWIO_SERVER_URL + 'proxy' Loading files, images and fonts from other hosts, except those that Editor.prototype.isCorsEnabledForUrl or the cors URL parameter lets the browser fetch directly
SAVE_URL DRAWIO_SERVER_URL + 'save' Server echo for downloads, only used with save=remote or in browsers that cannot save files themselves
REALTIME_URL, RT_WEBSOCKET_URL DRAWIO_SERVER_URL + 'cache', wss://<host>/rt Real-time collaboration services, not part of this repository
NOTIFICATIONS_URL null Notifications, only used on draw.io's own hosts
ICONSEARCH_PATH, ICON_SERVICE_PATH null from PostConfig.js Icon search in the shape search box
VSS_CONVERT_URL https://convert.diagrams.net/VsdConverter/api/converter Fallback conversion service for Visio files the browser cannot convert itself. Set it to null in PostConfig.js to keep such files local
DRAW_MATH_URL math4/es5 MathJax
DRAWIO_LOG_URL '' Client error and event logging. Logging (EditorUi.enableLogging) is only on for draw.io's own hosts, so nothing is sent from yours

Configuration, plugins and paths

Variable Purpose
DRAWIO_CONFIG Editor configuration object passed to Editor.configure, see Configuration
ALLOW_CUSTOM_PLUGINS false by default: only the plugins in App.pluginRegistry are loaded. true also allows other same-origin plugins added through the plugins setting, see Plugins
PLUGINS_BASE_PATH Prefix for the paths of the built-in plugins and of stored plugin paths that start with /
mxLanguage Forces the UI language. Set unconditionally it overrides the lang URL parameter and the user's choice, see Configuration
mxLoadSettings false stops the stored preferences from being loaded
SHAPES_PATH, STENCIL_PATH, STYLE_PATH, CSS_PATH, IMAGE_PATH, GRAPH_IMAGE_PATH, TEMPLATE_PATH, RESOURCES_PATH, RESOURCE_BASE, mxBasePath, mxImageBasePath Locations of shapes, stencils, styles, images, templates, language files and mxGraph resources. The relative defaults fit the layout of src/main/webapp
DOM_PURIFY_CONFIG DOMPurify configuration for sanitising labels and other HTML. Do not loosen it, see Security
MAX_REQUEST_SIZE, MAX_AREA Limits for the data sent to server endpoints and for the area of exported images
DRAWIO_PUBLIC_BUILD Set by this repository's PreConfig.js; changes what development mode loads and the preference keys of the sketch theme

Storage services (details in Storage backends): DRAWIO_GOOGLE_CLIENT_ID, DRAWIO_GOOGLE_APP_ID, DRAWIO_MSGRAPH_CLIENT_ID, DRAWIO_MSGRAPH_TENANT_ID, DRAWIO_GITHUB_ID, DRAWIO_GITHUB_URL, DRAWIO_GITHUB_API_URL, DRAWIO_GITLAB_ID, DRAWIO_GITLAB_URL, DRAWIO_DROPBOX_ID. Their defaults are draw.io's own app registrations, which only work on draw.io's hosts.

Making the viewer self-contained

The viewer bundles have their own defaults, compiled in by the build: PROXY_URL, STYLE_PATH, SHAPES_PATH, STENCIL_PATH, DRAW_MATH_URL, GRAPH_IMAGE_PATH, mxImageBasePath and mxBasePath point at https://viewer.diagrams.net unless the page sets them before loading the script. The committed viewer.min.js and viewer-static.min.js also fetch stencil XML from STENCIL_PATH at runtime. To serve the viewer entirely from your own host, rebuild the bundles with ant app (the public build embeds stencils.min.js in both, see Building) and set the variables above to your host on pages that load the viewer. See Embedding for the viewer API.

Disabling integrations

Each storage integration has a URL parameter. To change the default for all users, set it in PreConfig.js. A plain assignment forces the value; checking for null first keeps it overridable from the URL:

// Forced off for everyone
urlParams['db'] = '0';    // Dropbox
urlParams['tr'] = '0';    // Trello
urlParams['gh'] = '0';    // GitHub

// Off by default, but ?gapi=1 still works
if (urlParams['gapi'] == null) urlParams['gapi'] = '0';   // Google Drive
if (urlParams['od'] == null) urlParams['od'] = '0';       // OneDrive
if (urlParams['ms365'] == null) urlParams['ms365'] = '0'; // Microsoft 365
if (urlParams['gl'] == null) urlParams['gl'] = '0';       // GitLab

Without these, the editor loads the Google API script from apis.google.com on every start, even though sign-in cannot work without your own OAuth client.

Broader switches, applied in Init.js: offline=1, local=1, stealth=1 and lockdown=1 all turn off every cloud storage integration. offline=1 also stops plugins from loading, local=1 also stops the use of localStorage, and stealth=1 and lockdown=1 also disable the features that use external services (data governance lockdown). plugins=0 disables plugins. See Configuration.

Server endpoints

The client calls the following endpoints. Paths are relative to DRAWIO_SERVER_URL unless they are absolute. The public .war maps its servlets in WEB-INF/web.xml.

Endpoint Used for In the public .war
/proxy (PROXY_URL) Fetching URLs on other hosts: opening a diagram from a URL, images and fonts for export Yes, ProxyServlet. Answers 404 unless ENABLE_DRAWIO_PROXY=1; refuses private and reserved addresses
/google, /microsoft, /gitlab, /github2, /dropbox OAuth code-for-token exchange of the storage integrations Yes, the *AuthServlet classes, reading credentials from WEB-INF
/export, /service/* Server-side export, when EXPORT_URL points here Yes, ExportProxyServlet, which only forwards to the service named by the EXPORT_URL environment variable
/embed2.js Viewer script for embedded diagrams Yes, EmbedServlet2
/log Client error logging Yes, LogServlet; the client does not send logs from your host (see DRAWIO_LOG_URL)
/i/* Redirect for Imgur image links to app.diagrams.net Yes, ImgurRedirectServlet
/embed.js Legacy embed script Mapped in web.xml, but the servlet class is not in this repository, so requests fail
/save (SAVE_URL) Download echo No
/cache, wss://<host>/rt, notifications Real-time collaboration and notifications No. PreConfig.js sets sync=manual
iconSearch2, api/icons Icon search No. PostConfig.js clears the paths
https://convert.diagrams.net/... (VSS_CONVERT_URL) Fallback conversion of Visio files External service; clear it to keep files local

Every other request is for a static file under src/main/webapp. Remove mappings you do not use from web.xml; each one is server code reachable from the internet.

Offline use and the service worker

draw.io registers its service worker only where Editor.enableServiceWorker is true: on *.diagrams.net and *.draw.io, or on any host when the URL contains offline=1 or enableSW=1 (pwa=0 prevents it everywhere). On your own host, offline=1 is therefore what turns it on.

An installable offline application is not available from a deployment of this repository. The committed service-worker.js is generated for app.diagrams.net and its precache list includes files that are not in this repository (under connect/ and some plugins). Installation is all-or-nothing and fails when any listed file returns an error, so on your host the service worker does not install. The public build has no step that regenerates the file. offline=1 is still useful because it turns off the cloud storage integrations and plugins.

If you maintain your own service-worker.js, keep in mind that it is cache-first and keys every entry by the revision in its MANIFEST. A changed bundle is only fetched again when its revision changes.

For offline use, give users the desktop app. To remove a service worker from a browser, open clear.html on the same host, or use the browser's developer tools (Application > Service workers).

Content-Security-Policy

index.html does not set a Content-Security-Policy, so it is up to the deployment. The Docker image injects one as a <meta> tag from PreConfig.js (override it with DRAWIO_CSP_HEADER).

The policies draw.io uses itself are in the CSP-POLICIES block at the top of js/diagramly/Devel.js. Open ?dev=1&print-csp=1 and the console prints each of them; the one labelled app.diagrams.net is the production policy and a good starting point. Remove the hosts of integrations you have disabled and add your own. What the code requires:

Directive Needs Why
script-src 'self'; https://apis.google.com only with Google Drive The bundles are loaded as same-origin scripts. Production mode does not need 'unsafe-eval': App.main sets mxStencilRegistry.allowEval = false. Development mode does need it
style-src 'self' 'unsafe-inline', plus https://fonts.googleapis.com for Google Fonts The UI sets inline styles throughout
img-src, media-src, font-src * (or your allowed hosts), data: and blob: Diagrams embed images and fonts from anywhere and as data URIs
connect-src 'self' and the APIs of the storage services you enable (for example https://*.googleapis.com, https://graph.microsoft.com, https://api.github.com, your GitLab host) Storage APIs and resource loading use XMLHttpRequest and fetch
frame-src 'self' plus Google and Microsoft sign-in hosts if those integrations are on Pickers and sign-in dialogs
frame-ancestors The hosts allowed to embed your instance Only effective as an HTTP header

Prefer sending the policy as an HTTP header: a <meta> tag added by PreConfig.js does not cover bootstrap.js and PreConfig.js themselves, and frame-ancestors is ignored in a <meta> tag.

Security checklist

  • Serve the instance over HTTPS. The OAuth integrations require it unless you set DRAWIO_USE_HTTP, which is insecure.
  • Leave ENABLE_DRAWIO_PROXY off unless you need it. If you turn it on, keep the default address filtering.
  • Keep ALLOW_CUSTOM_PLUGINS false unless you control every script on the origin.
  • Do not loosen DOM_PURIFY_CONFIG; diagrams are untrusted input.
  • Keep the OAuth secrets in WEB-INF readable only by the container.
  • Remove servlet mappings you do not use.

The threat model and the rules for code that renders diagrams are in Security.

Keeping your deployment up to date

SECURITY.md says that only the latest version is supported and fixes are not backported. Plan to follow releases:

  • Watch the repository's releases on GitHub (each release is tagged vX.Y.Z and carries draw.war), or pull the matching Docker tag.
  • Read the ChangeLog before upgrading.
  • Keep local changes in PreConfig.js, PostConfig.js, DRAWIO_CONFIG or plugins rather than in the core files, so that upgrading means replacing the application and re-applying a few small files.
  • The bundle file names do not change between releases (app.min.js and so on). Serve them with caching headers that make browsers revalidate, or users keep running the old version after an upgrade.
  • The running version is shown in the Help menu (EditorUi.VERSION) and is in the VERSION file at the root of the repository.

See Releases and versioning.

See also

Clone this wiki locally