Install Drawboard

Two ways to run it on your own servers: on Kubernetes with the Helm chart, or with Docker Compose. Either way you need an OIDC provider for sign-in.

Path 1 · Kubernetes (Helm)

One chart, four steps

The chart deploys the backend, the frontend and (optionally) Gotenberg, with NetworkPolicies and hardened pods. The full reference, with every value, is the chart README; this page is the short route.

Prerequisites

  • Kubernetes 1.27 or newer, and Helm 3 or 4.
  • An OIDC provider (Keycloak, Authelia, Authentik, Zitadel, Entra ID, …).
  • An ingress controller. ingress-nginx is the default; others work too.
  • A CNI that enforces NetworkPolicy (Cilium, Calico, …), otherwise the policies are silently ignored.
  • The images ghcr.io/dergeorg/drawboard-backend and ghcr.io/dergeorg/drawboard-frontend (public, linux/amd64).
1

Create the key Secret

The backend does not start without file_url_keys and plugin_pseudonym_key, so create this Secret before the first install. The files are mounted under /run/secrets, never as environment variables.

Keys
kubectl create namespace drawboard
# two different 32-byte keys, the backend does not start without them
head -c 32 /dev/urandom | base64 > file_url_keys
head -c 32 /dev/urandom | base64 > plugin_pseudonym_key
# only when guest links will be enabled: an EC P-256 signing key
openssl ecparam -name prime256v1 -genkey -noout | openssl pkcs8 -topk8 -nocrypt -out guest_signing_keys
kubectl -n drawboard create secret generic drawboard-keys --from-file=file_url_keys --from-file=plugin_pseudonym_key --from-file=guest_signing_keys
# remove the key files from disk again
shred -u file_url_keys plugin_pseudonym_key guest_signing_keys 2>/dev/null || rm -P file_url_keys plugin_pseudonym_key guest_signing_keys

Guest links stay off?

Then leave out the openssl line and the --from-file=guest_signing_keys part. You can add the key later.
2

Create the database Secret

The database password is always a Secret, never a value in a file.

Database password
kubectl -n drawboard create secret generic drawboard-db --from-literal=password="$(openssl rand -base64 32)"
3

Write my-values.yaml

These are only the required values. Everything else has a default.

my-values.yaml
publicUrl: https://draw.example.org
oidc:
issuerUri: https://sso.example.org
audience: drawboard-api
secrets:
existingSecret: drawboard-keys
# pod CIDR(s) of the ingress controller and the frontend pods
trustedProxies: ["10.244.0.0/16"]
ingress:
host: draw.example.org
tls:
- secretName: drawboard-tls
hosts: [draw.example.org]
annotations:
cert-manager.io/cluster-issuer: letsencrypt
postgresql:
external:
host: postgres.db.svc
existingSecret: drawboard-db

Prefer the chart's own PostgreSQL for a lab or a small install? Replace the postgresql block with this. For anything bigger, use an external or operator-managed database and keep backups with a tested restore.

Built-in database
postgresql:
enabled: true
auth:
existingSecret: drawboard-db
persistence:
size: 8Gi

trustedProxies must be your pod CIDR(s)

The backend believes X-Forwarded-For only from the addresses listed here: the pod CIDR(s) of the ingress controller and the frontend pods. Empty means nobody is trusted and the rate limits count all users together; too wide and a client can name its own address. The chart README shows how to find them.

Install

Pick a chart version from the releases of the repository and install. Every chart version names the backend and frontend releases it was checked with.

Install
helm install drawboard oci://ghcr.io/dergeorg/charts/drawboard --version <version> -n drawboard -f my-values.yaml

Then open publicUrl and sign in. The install prints the exact redirect URIs your OIDC client needs; they are also listed on the OIDC page.

Upgrade, rollback, uninstall

Lifecycle
helm upgrade drawboard oci://ghcr.io/dergeorg/charts/drawboard --version <new> -n drawboard -f my-values.yaml
# Flyway migrations are not rolled back
helm rollback drawboard -n drawboard
helm uninstall drawboard -n drawboard

The backend keeps tickets, rooms and sequence numbers in memory and runs as one replica with the Recreate strategy. An upgrade therefore means a short outage; clients reconnect and lose nothing. helm uninstall keeps the database volume and the files volume.

Guest links let people without an account open a board (view, comment or edit, as the link says). Every control is in the release; these conditions are yours as the operator. Do all of them before setting guests.enabled=true.

  • Trusted proxies set and verified: open a guest link with a wrong token and check that the audit event library.share.exchange-failed carries your real client address, not a pod or node address.
  • The signing key (guest_signing_keys) only as a Secret file, never as a value in a file in git.
  • TLS and headers verified end to end: a board page answers with Referrer-Policy: no-referrer and the frontend’s Content-Security-Policy.
  • Backups with a tested restore of the database and the files volume. A guest with an editor link can change and delete content like a signed-in editor.
  • Know what an editor link allows: hand out viewer or comment links unless editing is the point, and revoke links you no longer need.
  • Third-party plugins additionally need plugins.origin on a separate hostname. A plugin can still send what it is shown to its own servers: install only plugins you trust.
Check the headers
curl -sI https://<host>/s/x | grep -iE 'referrer-policy|content-security-policy'
Path 2 · Docker Compose

One host, one compose file

For a single server without Kubernetes. You need Docker with Compose v2, a reverse proxy that terminates TLS in front of it, and an OIDC provider for sign-in. The stack runs the frontend (nginx, the only port), the backend, PostgreSQL and Gotenberg for Office documents.

Start here

Try it in five minutes

Everything runs on your machine, with a bundled Keycloak and three users. Nothing to configure.

Local trial
git clone https://github.com/DerGeorg/drawboard && cd drawboard/deploy/compose
./generate-keys.sh
docker compose -f docker-compose.yml -f compose.trial.yaml --env-file trial/trial.env up -d

Open http://localhost:8080 and sign in as anna / anna (admin), ben / ben or chris / chris. Open a second browser or a private window as another user to see live collaboration.

Local only

The passwords are the user names and it runs on plain HTTP, so never expose it to others. The first start takes a minute or two (database migrations); on a Mac with Apple silicon it takes longer, because the images are built for linux/amd64 and run emulated.

Stop it again:

Stop the trial
docker compose -f docker-compose.yml -f compose.trial.yaml --env-file trial/trial.env down
# add -v to delete the data as well

Install on a server

Prerequisites

  • A Linux server with Docker and Compose v2.
  • A domain, and a reverse proxy that terminates TLS in front of the stack (nginx, Caddy, ...).
  • An OIDC provider (Keycloak, Authelia, Authentik, ...).
1

Get the files

Take the compose folder of the repository onto the server. Three files matter: docker-compose.yml, .env.example and generate-keys.sh.

Get the files
git clone https://github.com/DerGeorg/drawboard
cp -r drawboard/deploy/compose /opt/drawboard && cd /opt/drawboard
# docker-compose.yml, .env.example and generate-keys.sh are all you need
2

Generate the keys

The script creates keys/file_url_keys, keys/plugin_pseudonym_key and keys/guest_signing_keys. It never overwrites existing ones.

Keys
./generate-keys.sh

Keep the keys

New keys end every signed file URL and every guest session. Back them up together with the data.
3

Configure .env

Copy the example and set at least PUBLIC_URL, OIDC_ISSUER_URI and DB_PASSWORD.

Create .env
cp .env.example .env
# a database password:
openssl rand -base64 32
.env (essentials)
# where users open Drawboard (your reverse proxy terminates TLS)
PUBLIC_URL=https://draw.example.org
# the frontend port on this host; 127.0.0.1 = only a proxy on this host reaches it
HTTP_BIND=127.0.0.1
HTTP_PORT=8080
OIDC_ISSUER_URI=https://sso.example.org/realms/main
OIDC_CLIENT_ID=drawboard
OIDC_AUDIENCE=drawboard-api
# the database password, e.g. from: openssl rand -base64 32
DB_PASSWORD=
# the frontend network and every proxy in front of it, comma separated
WEB_SUBNET=172.30.37.0/24
TRUSTED_PROXIES=172.30.37.0/24

.env.example has more options with comments. The identity provider is described below.

4

Put a reverse proxy in front

Forward https://<your host> to http://127.0.0.1:8080 (the HTTP_PORT), with WebSocket upgrade allowed and X-Forwarded-For appended. For nginx:

nginx
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade; # needs a map, see below
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 60m;
}
nginx (http block)
# in the http { } block
map $http_upgrade $connection_upgrade { default upgrade; "" close; }

With Caddy one line does all of that by itself:

Caddyfile
reverse_proxy 127.0.0.1:8080
5

Start it

Start
docker compose up -d
# repeat until every service is healthy
docker compose ps

Sign in

Open PUBLIC_URL and sign in. Users are created on their first sign-in; members of ADMIN_GROUP are instance admins.

Identity provider

Drawboard speaks plain OpenID Connect. The provider needs a public client drawboard with authorization code and PKCE (S256), the exact redirect URIs <PUBLIC_URL>/auth/callback and <PUBLIC_URL>/silent-renew.html, and signed JWT access tokens whose audience is drawboard-api. If the backend reaches the provider under another address than the issuer, set OIDC_JWK_SET_URI. The full list, with notes for Keycloak, Authelia and Authentik.

Trusted proxies

The backend believes X-Forwarded-For only from TRUSTED_PROXIES. It must list the frontend's network (WEB_SUBNET) and, if your reverse proxy runs on another host or in another Docker network, its address. Wrong values make every user look like one address, and the rate limits then count all users together. Check it once: open PUBLIC_URL/s/x (a made-up guest link) and find the audit event library.share.exchange-failed in the backend log; its ip must be your real client address. If WEB_SUBNET overlaps a network on your host, change both values.

Update and backup

Migrations run on start and there is no automatic rollback, so back up first.

Update
# set DRAWBOARD_BACKEND_VERSION and DRAWBOARD_FRONTEND_VERSION in .env, then:
docker compose pull && docker compose up -d

Back up the database (or the volume drawboard_db-data), the volume drawboard_files, and keys/ and .env. Test a restore.

Database dump
docker compose exec db pg_dump -U drawboard drawboard > drawboard.sql

Before enabling guest links

GUESTS_ENABLED=true lets people without an account open boards through shared links. Do these first: trusted proxies verified (above); keys/guest_signing_keys kept secret; TLS and the headers Referrer-Policy: no-referrer and Content-Security-Policy checked through your reverse proxy; tested backups. Guests on editor links can change content, so hand out viewer or comment links unless editing is the point. Third-party plugins additionally need their own hostname (PLUGIN_ORIGIN, proxied to the same frontend port). The Kubernetes checklist above has the details.

Check the headers
curl -sI https://<host>/s/x

Next: connect your sign-in

Drawboard speaks standard OIDC. See what your provider needs.