Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Self hosting Excalidraw - Umbrella issue #1772

Open
2 of 3 tasks
kbariotis opened this issue Jun 15, 2020 · 129 comments
Open
2 of 3 tasks

Self hosting Excalidraw - Umbrella issue #1772

kbariotis opened this issue Jun 15, 2020 · 129 comments
Labels

Comments

@kbariotis
Copy link
Contributor

kbariotis commented Jun 15, 2020

So I'm opening the self-hosting thread.

Current architecture

First an architecture overview. Excalidraw is made of three main parts at the moment.

  • The client, what you see when you visit excalidraw.com which is this repo
  • The sockets server, which powers the collaboration feature, it's this repo, a Node.js server running socket.io
  • The store server, which powers the sharing feature, it's this repo, a Python server running on GAE with some kind of a DB

At the moment, we are planning on publishing a Docker image of the Excalidraw client only. That means that people will be able to self-host the client but they won't be able to use sharing/collaboration features because the client will still have to talk to our cloud-hosted backend servers on which there are CORS restrictions.

For now

For the image that we are planning on publishing soon (in the next few days), we have already stripped out the google tag manager but I see the visible sharing/collaboration icons as an issue. People will be frustrated by not understanding why they don't work.

I suggest we strip them out too on the Docker image only. Temporarily until we have a plan for the rest two services. It's the best UX we can offer IMO.

For the future

Moving forward we would like people to be able to self-host a full-fledged Excalidraw environment. My suggestion is to publish a Docker image for each of the two backend servers so people can self-host these as well. Having separate images promotes better scalability and also follows the one service/per container practice. They will be easier to maintain and also people can opt-out of a feature if they don't want to support, e.g. they only want collaboration features but not the storing feature.

Then we will be able to point the client image to specific URLs and it will simply work.

For that to work, the socket server will be much simpler as it has no dependencies. On the other hand, the storing server has a DB dependency which we will have to consider. Ideally, it should be something easy to use and maintain, like a postgres or a redis instance.

We could consider how the future of that service looks like, but for now, we can focus on what it actually needs.

Let me know your thoughts and we can start planning right away.

I should also mention the embedded Excalidraw instance we are working on that would also benefit from being able to connect to a self-host backend server but also have a better UX without icons that don't work.

Todo

@kbariotis
Copy link
Contributor Author

Worth mentioning that atm:

@kjellkvinge
Copy link

just to pitch in an alternative approach. I have experimented with this, and created a bundle - single binary - which will work for selfhosting (on mac/win/linux/rasperry pi) on local network. (currently only link sharing is implemented)

https://github.com/kjellkvinge/excalidrawserver

Is this interesting? It was a fun hack :)

regards

@NMinhNguyen
Copy link
Contributor

NMinhNguyen commented Jun 27, 2020

Made a comment in https://github.com/excalidraw/excalidraw-json/issues/76#issuecomment-650463667 but will re-post here for visibility:

Also, just wanted to say that during our company-wide hackathon yesterday and today, I managed to self-host both the socket and storage server. The socket server basically required no changes. The storage server I decided to re-implement from scratch using Next.js (I'm more familiar with JavaScript than Python and there's lots of GAE dependencies, so a rewrite was a lot simpler) and S3 for blob storage. I'll be looking to open source this soon, so stay tuned 🙂

@kbariotis
Copy link
Contributor Author

Thank you @NMinhNguyen for sharing. Indeed the socket server needs only a Dockerfile and it would be ready to be self hosted along the main Excalidraw client.

For the storage one, we will have to think the current storage solution and storing diagrams in the filesystem makes sense I think. So with a Dockerfile and shared volume on the host, people will be able to self host that as well.

Thank you again

@kjellkvinge
Copy link

@NMinhNguyen did you have any issues when serving the site over HTTP? Or do you plan he use HTTPS in all cases?
For simplicity I wanted to be able to selfhost on local network, but then I had to disable the clientside crypto.

@NMinhNguyen
Copy link
Contributor

NMinhNguyen commented Jun 27, 2020

Thank you @NMinhNguyen for sharing. Indeed the socket server needs only a Dockerfile and it would be ready to be self hosted along the main Excalidraw client.

I don't actually have much Docker experience but we have a PaaS solution (somewhat similar to GAE in some ways) that understands Node.js so all I had to do was push the source code + dependencies but yeah :)

I'll need to think about the best way to parameterise endpoint URLs in the excalidraw client. What I ended up doing was extracting the URLs into like REACT_APP_BACKEND_V2_POST_URL env vars store in .env and then running yarn build:app:docker. But you see, that requires a build :)

If you have any suggestions (or links to good Docker resources), I'm willing to look into those too.

@NMinhNguyen did you have any issues when serving the site over HTTP? Or do you plan he use HTTPS in all cases?
For simplicity I wanted to be able to selfhost on local network, but then I had to disable the clientside crypto.

The PaaS (Cloud Foundry) is able to serve via HTTPS so I didn't have any issues but you're right in that window.crypto is unavailable in insecure contexts https://stackoverflow.com/a/46468377

@NMinhNguyen
Copy link
Contributor

NMinhNguyen commented Jun 29, 2020

So I've been thinking a bit more about this and I'm wondering if the easiest way to make the backend URLs configurable, is to have some kinda @excalidraw/cli package that you would invoke as follows:

REACT_APP_BACKEND_V2_GET_URL=... npx @excalidraw/cli build
# this then creates a `public` folder or something

The CLI itself could take flags like --gitRef for specifying the commit hash/Git tag or branch or something, and default to the main branch in this repo. Such a CLI, in my opinion, has the most flexibility in terms of hosting:

  • you can take public folder and host on Vercel/AWS S3
  • you can still have Dockerfile that uses the CLI to build the site and uses nginx to serve it

Let me know if you think this approach is wack 😛

@Turakar
Copy link

Turakar commented Jun 30, 2020

@NMinhNguyen, from my observations, the by far most common way of configuration for docker images is by environment variables. Sometimes there are also some command line parameters or config files involved, but not a manual build.

@NMinhNguyen
Copy link
Contributor

@NMinhNguyen, from my observations, the by far most common way of configuration for docker images is by environment variables. Sometimes there are also some command line parameters or config files involved, but not a manual build.

Essentially the problem here is that environment variables are read only once when you do yarn build. Unless you want to muck around with rewriting the build output, it’s pretty difficult to inject those variables. Further to that, most solutions I’ve seen will suffer from content hashing issues. A rebuild is simply the most robust way to inject those environment variables for a static website (CRA), but I’m open to suggestions.

@kjellkvinge
Copy link

in my experiment i can do:

REACT_APP_HOST="htps://my.server.com/" npm run build:app:docker

But this require that the backendurls use this variable. I solve this with a patch like this, which I apply in a build step:

diff --git a/src/data/index.ts b/src/data/index.ts
index f9c3bec..793d28e 100644
--- a/src/data/index.ts
+++ b/src/data/index.ts
@@ -24,12 +24,18 @@ export { loadFromBlob } from "./blob";
 export { saveAsJSON, loadFromJSON } from "./json";
 export { saveToLocalStorage } from "./localStorage";
 
-const BACKEND_GET = "https://json.excalidraw.com/api/v1/";
-
-const BACKEND_V2_POST = "https://json.excalidraw.com/api/v2/post/";
-const BACKEND_V2_GET = "https://json.excalidraw.com/api/v2/";
-
-export const SOCKET_SERVER = "https://excalidraw-socket.herokuapp.com";
+// get backend host from env. default excalidraw
+const HOST = process.env.REACT_APP_HOST
+  ? process.env.REACT_APP_HOST
+  : "https://json.excalidraw.com/";
+const BACKEND_GET = `${HOST}api/v1/`;
+
+const BACKEND_V2_POST = `${HOST}api/v2/post/`;
+const BACKEND_V2_GET = `${HOST}api/v2/`;
+
+export const SOCKET_SERVER = process.env.REACT_APP_SOCKET_SERVER
+  ? process.env.REACT_APP_SOCKET_SERVER
+  : "https://excalidraw-socket.herokuapp.com";
 
 export type EncryptedData = {
   data: ArrayBuffer;

@NMinhNguyen
Copy link
Contributor

NMinhNguyen commented Jun 30, 2020

@kjellkvinge yup that is precisely what I did so I could rebuild it. But just wanted to agree on the approach (do we rebuild? do we not?) first before making any changes, although I suppose it doesn't harm to introduce those environment variables and an .env file anyway: https://create-react-app.dev/docs/adding-custom-environment-variables/#adding-development-environment-variables-in-env

@Turakar
Copy link

Turakar commented Jun 30, 2020

@NMinhNguyen, from my observations, the by far most common way of configuration for docker images is by environment variables. Sometimes there are also some command line parameters or config files involved, but not a manual build.

Essentially the problem here is that environment variables are read only once when you do yarn build. Unless you want to muck around with rewriting the build output, it’s pretty difficult to inject those variables. Further to that, most solutions I’ve seen will suffer from content hashing issues. A rebuild is simply the most robust way to inject those environment variables for a static website (CRA), but I’m open to suggestions.

Ok, got your point. I was not aware of the limitations imposed by the build process and just had a look from the sysadmin perspective. Rebuilds are ugly to handle (in my opinion), but if there is no workaround :|

@kbariotis
Copy link
Contributor Author

Thank you all for your ideas. I think a good small step would be to containerize the socket server. Please feel free to make a PR for that and we can then talk about integrating with the main app Docker image. :)

@NMinhNguyen
Copy link
Contributor

NMinhNguyen commented Jul 1, 2020

@kbariotis I've raised a PR that makes it simpler to git clone this repo and yarn build:app:docker to produce build output with customised API endpoints. A Docker solution actually doesn't help my use case much, e.g. if I wanted to use something similar to Vercel to host my website - it only understands static files (or JS, Python etc.) and not Docker containers 😅 That's not to say I'm not willing to dockerise the socket server because I can see value in that, just not my immediate priority (would like to open source my storage server next).

@NMinhNguyen
Copy link
Contributor

NMinhNguyen commented Jul 2, 2020

From https://github.com/excalidraw/excalidraw-json/issues/76#issuecomment-652832953

I've open sourced the Next.js storage server: https://github.com/NMinhNguyen/excalidraw-json. At the moment it only supports Amazon S3-compatible storage (e.g. Google Cloud Storage, Yandex Object Storage) and only implements v2 of the API (i.e. the one with e2e encryption), but I might add an option to store files on the file system.

I have an instance of Excalidraw that uses my storage server: https://excalidraw.minhnguyen.vercel.app/

NMinhNguyen added a commit to NMinhNguyen/excalidraw-room that referenced this issue Jul 14, 2020
NMinhNguyen added a commit to NMinhNguyen/excalidraw-room that referenced this issue Jul 14, 2020
NMinhNguyen added a commit to NMinhNguyen/excalidraw-room that referenced this issue Jul 14, 2020
NMinhNguyen added a commit to NMinhNguyen/excalidraw-room that referenced this issue Jul 14, 2020
NMinhNguyen added a commit to NMinhNguyen/excalidraw-room that referenced this issue Jul 14, 2020
@NMinhNguyen
Copy link
Contributor

Thank you all for your ideas. I think a good small step would be to containerize the socket server. Please feel free to make a PR for that and we can then talk about integrating with the main app Docker image. :)

@kbariotis I've raised excalidraw/excalidraw-room#71 although I've left publishing out of the PR for now.

@NMinhNguyen
Copy link
Contributor

By the way, I wanted to ask about scalability - how many instances of excalidraw-json and excalidraw-room do you have running for https://excalidraw.com/? I'm not too familiar with excalidraw-room, but from what I understand, you can only easily have 1 instance? Or you might need some sort of smart routing so that all requests with the same room ID get routed to the same https://socket.io/ server? At the same time, presumably even a single instance of each service would easily be able to handle quite a lot of load anyway?

@vjeux
Copy link
Contributor

vjeux commented Jul 16, 2020

Right now we have a single instance for both, but they both are really far away from max utilization. The design of both were with scalability in mind. For json, it's just uploading / serving static files. For room, it's just moving opaque binary opaque across all the people in a single room, it doesn't do anything else and doesn't keep any state outside of the list of people in the room.

@kbariotis
Copy link
Contributor Author

Amazing everyone, @NMinhNguyen thank you for containerizing excalidraw-room. Here's the Docker image.

Now I am wrong to believe that the client is not configurable to use a specific URL for that service right? So what we can do now is make it configurable somehow so when someone builds the excalidraw image can pass the URL of the self-hosted excalidraw-room instance.

@Digital39999
Copy link

@lietu as far as I know, Excalidraw has mixed saving ways where one uses json store, and the other one uses firestore which is most likely for collaboration rooms, best way would be if this repository migrated to supabase and dropped json store support, that seems like ideal solution.

@Someone0nEarth
Copy link

I was looking for a fully self hosted excalidraw solution.. many were still using Firebase for the storage.
I used this as an inspiration for a fully self hosted solution using traefik and mongodb. Export to Link and collaboration are working as fine.

https://github.com/Someone0nEarth/excalidraw-self-hosted

@PatWie
Copy link

PatWie commented Apr 13, 2024

Seeing also the need for self-hosting, I did start a self-containing Golang implementation providing collaboration and storage options (S3, SQLite, filesystem, in-memory)
https://github.com/PatWie/excalidraw-complete

I simply don't see any reason for pasting together several docker images, when I can simply put a single binary somewhere and can even cross-compile it for Mac.

While it works with the official excalidraw UI without patching - besides setting endpoints, I am also struggling with the firebase dependency.

@Someone0nEarth
Copy link

Someone0nEarth commented Apr 13, 2024

Seeing also the need for self-hosting, I did start a self-containing Golang implementation providing collaboration and storage options (S3, SQLite, filesystem, in-memory) https://github.com/PatWie/excalidraw-complete

I like your attempt!

While it works with the official excalidraw UI without patching - besides setting endpoints, I am also struggling with the firebase dependency.

At the moment, there are only two ways to get rid of the Google Firebase/-storage usage when using a self-hosted Excalidraw version:

  1. Using a patched version of Excalidraw, which is supporting a different backend storage solution (like this) in combination with a self-hosted backend storage service like this
  2. Implementing/using a self-hosted version of Firestorage (API) to use it with an unpatched version of Excalidraw.

I would recommend to update the readme of your project, that in the end, your self-hosted version uses Googles Firestorage (in collaboration mode or when "Save to..."->"Export to Link" is used).

A way to test, that Googles Firebase storage is still used, can be found here.

@PatWie
Copy link

PatWie commented Apr 14, 2024

Wow! Thanks for that information! I was unaware of that.

Is dislike patching many places in the front-end code (I am not a front-end guy). As the go binary serves the static files anyway, one can patch those calls in the assets on-the-fly and provide an API stub that somehow covers the functions needed for those calls.

A way to test, that Googles Firebase storage is still used, can be found https://github.com/Nenodema/excalidraw-self-hosted-stack/issues/3#issue-2154065668.

Here is a prototype that has both API endpoints (documents:commit, documents:batchGet)
https://github.com/PatWie/excalidraw-complete/tree/firebase-patch .

It removes most of the error messages but is merely a proof-of-concept in the current stage. I haven't fully grasped yet that https://firestore.googleapis.com/google.firestore.v1.Firestore/Listen/channel?database=projects.... calls and I definitely not gonna re-implement firebase.

@andraskuti
Copy link

andraskuti commented May 3, 2024

I have created the following diagram to better understand the architecture and dependencies of self-hosted Excalidraw primarily from a feature perspective (saving, link sharing, collab) based on my current understanding.

What we see here is the vanilla case, where all components are from github.com/excalidraw/* repos.
Obviously custom solutions could change any part of the system.
Library, AI features and Excalidraw+ are not part of this diagram.

Hope this helps others getting started. Any suggestions are welcome.

image

@AlphaCraft9658
Copy link

The official docker image has not been updated in a very long while.

@ad1992
Copy link
Member

ad1992 commented May 6, 2024

Hi PR is welcome to update docker image

@AlphaCraft9658
Copy link

Hi PR is welcome to update docker image

Is there any information on how the docker image is built? I tried setting it up myself, but haven't been able to get it working. Is it the dockerfile and compose file that's already available in the repo?

@ad1992
Copy link
Member

ad1992 commented May 6, 2024

Hi PR is welcome to update docker image

Is there any information on how the docker image is built? I tried setting it up myself, but haven't been able to get it working. Is it the dockerfile and compose file that's already available in the repo?

Yes the files are already available in the repo, the build is not. being published as its failing so you will need to check why the publish docker gh action is failing and fix it

@AlphaCraft9658
Copy link

Hi PR is welcome to update docker image

Is there any information on how the docker image is built? I tried setting it up myself, but haven't been able to get it working. Is it the dockerfile and compose file that's already available in the repo?

Yes the files are already available in the repo, the build is not. being published as its failing so you will need to check why the publish docker gh action is failing and fix it

You mean in the GitHub actions? I don't really know how they work, but it seems like there is some server error when fetching the packages for the build. I'll try to fork and look into it.

@ad1992
Copy link
Member

ad1992 commented May 6, 2024

@AlphaCraft9658 you should be able to run those commands in local and test it out as well in publish-docker.yml

@AlphaCraft9658
Copy link

@AlphaCraft9658 you should be able to run those commands in local and test it out as well in publish-docker.yml

I got it. Now I'll see what's going on. I added a workflow_dispatch trigger for the workflow, for testing.

@AlphaCraft9658
Copy link

Hi PR is welcome to update docker image

Is there any information on how the docker image is built? I tried setting it up myself, but haven't been able to get it working. Is it the dockerfile and compose file that's already available in the repo?

Yes the files are already available in the repo, the build is not. being published as its failing so you will need to check why the publish docker gh action is failing and fix it

The docker build workflow is still failing. That's the current status: https://github.com/AlphaCraft9658/excalidraw/actions/runs/8970588981/job/24634460317

It cannot resolve index.html.

@AlphaCraft9658
Copy link

Hi PR is welcome to update docker image

Is there any information on how the docker image is built? I tried setting it up myself, but haven't been able to get it working. Is it the dockerfile and compose file that's already available in the repo?

Yes the files are already available in the repo, the build is not. being published as its failing so you will need to check why the publish docker gh action is failing and fix it

How is it even supposed to be build? I see it's trying to use vite and cross-env. Something makes vite unable to resolve the index.html file.

@Hufschmidt
Copy link

Hufschmidt commented May 7, 2024

Hi PR is welcome to update docker image

Is there any information on how the docker image is built? I tried setting it up myself, but haven't been able to get it working. Is it the dockerfile and compose file that's already available in the repo?

Yes the files are already available in the repo, the build is not. being published as its failing so you will need to check why the publish docker gh action is failing and fix it

How is it even supposed to be build? I see it's trying to use vite and cross-env. Something makes vite unable to resolve the index.html file.

There are various open PRs to fix the docker build, non have been merged so far (for unknown reasons).
See:

Related Issues:

<rant>
Personally I've since moved on to TlDraw which seems to have managed to implemented a working open-source collaboration workflow. Very much unlike ExcaliDraw, where I can see as many as 290 open pull requests , many going back as fas as 2002, as is also this issue by the way. All despite there beeing new releases/tags every now and then...
</rant>

@AlphaCraft9658
Copy link

Hi PR is welcome to update docker image

Is there any information on how the docker image is built? I tried setting it up myself, but haven't been able to get it working. Is it the dockerfile and compose file that's already available in the repo?

Yes the files are already available in the repo, the build is not. being published as its failing so you will need to check why the publish docker gh action is failing and fix it

How is it even supposed to be build? I see it's trying to use vite and cross-env. Something makes vite unable to resolve the index.html file.

There are various open PRs to fix the docker build, non have been merged so far (for unknown reasons).
See:

Related Issues:

<rant>
Personally I've since moved on to TlDraw which seems to have managed to implemented a working open-source collaboration workflow. Very much unlike ExcaliDraw, where I can see as many as 290 open pull requests , many going back as fas as 2002, as is also this issue by the way. All despite there beeing new releases/tags every now and then...
</rant>

TLDraw doesn't have an up-to-date docker image either.

@ad1992
Copy link
Member

ad1992 commented May 7, 2024

Hi PR is welcome to update docker image

Is there any information on how the docker image is built? I tried setting it up myself, but haven't been able to get it working. Is it the dockerfile and compose file that's already available in the repo?

Yes the files are already available in the repo, the build is not. being published as its failing so you will need to check why the publish docker gh action is failing and fix it

How is it even supposed to be build? I see it's trying to use vite and cross-env. Something makes vite unable to resolve the index.html file.

The index.html should be available in the public folder so you will need to update the path correctly

@AlphaCraft9658
Copy link

Hi PR is welcome to update docker image

Is there any information on how the docker image is built? I tried setting it up myself, but haven't been able to get it working. Is it the dockerfile and compose file that's already available in the repo?

Yes the files are already available in the repo, the build is not. being published as its failing so you will need to check why the publish docker gh action is failing and fix it

How is it even supposed to be build? I see it's trying to use vite and cross-env. Something makes vite unable to resolve the index.html file.

The index.html should be available in the public folder so you will need to update the path correctly

There is also an index.html in excalidraw-app. And where would I set this path? There are all kinds of different configs.

@ad1992
Copy link
Member

ad1992 commented May 7, 2024

@AlphaCraft9658 yes you are right, it should be index.html inside the excalidraw-app folder - thats the main entry file responsible for excalidraw.com

@AlphaCraft9658
Copy link

@AlphaCraft9658 yes you are right, it should be index.html inside the excalidraw-app folder - thats the main entry file responsible for excalidraw.com

But do you know how to specify that path? I haven't worked with vite ever. The error happens during the actual build process with vite, which fails due to the "missing" index.html file.

@ad1992
Copy link
Member

ad1992 commented May 7, 2024

@AlphaCraft9658 yes you are right, it should be index.html inside the excalidraw-app folder - thats the main entry file responsible for excalidraw.com

But do you know how to specify that path? I haven't worked with vite ever. The error happens during the actual build process with vite, which fails due to the "missing" index.html file.

can you update the command to 👇🏻 and try

yarn --cwd ./excalidraw-app build

@AlphaCraft9658
Copy link

@AlphaCraft9658 yes you are right, it should be index.html inside the excalidraw-app folder - thats the main entry file responsible for excalidraw.com

But do you know how to specify that path? I haven't worked with vite ever. The error happens during the actual build process with vite, which fails due to the "missing" index.html file.

can you update the command to 👇🏻 and try

yarn --cwd ./excalidraw-app build

That causes a loop, as it seems. But we're talking about another PR which may solve it, you know.

@Mrazator Mrazator added the docker label May 7, 2024
@kiranatious
Copy link

Hi All,
if someone is still looking for a solution to use Excalidraw as a standalone.. its possible.
we need to run Excalidraw inside a docker nginx
2. excalidraw json (for websocket room)
3. firebase account for saving files and scenes

@saghul
Copy link

saghul commented Jul 8, 2024

The concept of standalone is at odds with the Firebase requirement, IMHO.

@Digital39999
Copy link

Honestly its easier to make an app that wraps excalidraw package and selfhost modified excalidraw json for own custom storage.

@hex-m
Copy link

hex-m commented Sep 15, 2024

Nextcloud Whiteboard is based on Excalidraw and should be relatively easy to self-host.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests