Skip to main content

Excalidraw Common — Shared Application Configuration

Excalidraw_Common is the shared application layer for Excalidraw. It is not deployed on its own; instead it supplies the Excalidraw-specific configuration that both Excalidraw_GKE and Excalidraw_CloudRun build on, so the two platform variants behave identically where it matters. End users never configure this layer directly — it has no deployment UI inputs of its own — but understanding what it provides explains the defaults you see in the platform docs.

Excalidraw is an open-source (MIT) virtual whiteboard for sketching hand-drawn-style diagrams. The self-hosted distribution is a static single-page application served by nginx — there is no backend, no database, no user accounts, and no server-side persistence. Drawings live in the visitor's own browser (local storage) and are exported/imported as .excalidraw files. This makes the module unusually thin: no secrets, no Cloud SQL, no object storage, and no cache. Several optional features — live collaboration, "Export to link", the AI diagram features and the shape-library browser — call Excalidraw's own hosted services when a user invokes them; those URLs are compiled into the upstream bundle and cannot be redirected by this module.

For the infrastructure that actually provisions and runs Excalidraw, see the platform guides (Excalidraw_GKE, Excalidraw_CloudRun) and the foundation guides (App_GKE, App_CloudRun, App_Common).


1. What this layer provides​

AreaProvided by Excalidraw_CommonWhere it surfaces
Container imageThin custom build FROM excalidraw/excalidraw:<version>; the build only mirrors the static SPA into Artifact Registrycontainer_image output of the platform deployment
Container portFixes port 80 — the nginx listener inside the image§Networking in the platform guides
Database engineNone (database_type = "NONE"). Excalidraw has no backend and stores no data server-sideNo Cloud SQL instance is created
SecretsNone — secret_ids and secret_values are empty mapsNothing is written to Secret Manager
Object storageNone — storage_buckets and gcs_volumes are emptyNo GCS bucket is provisioned
Cache / queueNone — no Redis, no message queue—
Database bootstrapNone — there are no initialization jobs (initialization_jobs = [])initialization_jobs output is empty
Version pinningSets an app-specific EXCALIDRAW_VERSION build ARG so the Foundation-injected APP_VERSION cannot overwrite the tag — but application_version = "latest" still resolves to "latest" (no pinning; pinned_excalidraw_version is itself "latest")container_build_config.build_args
Health checksSupplies default startup / liveness / readiness probes targeting the root path /§Observability in the platform guides

2. Container image​

The image is a thin custom build rather than a straight prebuilt reference. The Dockerfile is two lines of substance:

ARG EXCALIDRAW_VERSION=latest
FROM excalidraw/excalidraw:${EXCALIDRAW_VERSION}
EXPOSE 80
  • Base image: excalidraw/excalidraw — the official static nginx SPA published on Docker Hub. There is no application server: nginx serves the compiled frontend bundle on port 80.
  • Why a custom build at all: the build exists to mirror the upstream image into the project's Artifact Registry (enable_image_mirroring = true by default) so the deployment does not depend on Docker Hub availability or pull quotas, and so Binary Authorization / CMEK policies apply to a project-local image.
  • Version pinning gotcha (why EXCALIDRAW_VERSION, not APP_VERSION): the Foundation injects APP_VERSION = application_version into build_args and wins any merge, so a Common-level APP_VERSION would be silently overwritten with latest. Excalidraw's base tag is therefore derived from an app-specific EXCALIDRAW_VERSION build ARG that the Foundation does not touch. Unlike some sibling modules, this does not pin latest to a known-good tag: main.tf sets pinned_excalidraw_version = "latest", so application_version = "latest" (the campaign default) resolves right back to "latest" and the build tracks Docker Hub's rolling excalidraw/excalidraw:latest tag. Set an explicit tag (e.g. v1.11.86) to actually pin a production release.

Inspect the resolved build args without deploying:

# From within Excalidraw_CloudRun or Excalidraw_GKE:
tofu console
> module.excalidraw_app.config.container_build_config.build_args

3. Database, secrets, and object storage — intentionally empty​

Because the self-hosted Excalidraw frontend is entirely client-side, this layer declares none of the stateful primitives the other application modules use:

  • database_type = "NONE" — no Cloud SQL instance, no db-init job, no schema.
  • secret_ids = {} / secret_values = {} — nothing is written to Secret Manager. There are no encryption keys, JWT secrets, or database passwords to protect, and therefore none to rotate.
  • storage_buckets = [] / gcs_volumes = [] — no GCS bucket is provisioned and no GCS Fuse volume is mounted.
  • initialization_jobs = [] — there is no first-deploy bootstrap step; the service is ready as soon as nginx starts.

You can confirm the empty outputs from the platform module:

tofu console
> module.excalidraw_app.secret_ids # {}
> module.excalidraw_app.storage_buckets # []

Consequently the CLI commands you would normally use to inspect a database, list secrets, or browse a bucket for this app will return nothing — that is expected, not a misconfiguration.


4. Runtime configuration and vestigial variables​

Excalidraw needs no per-deployment runtime configuration — the same image serves correctly anywhere. The Common layer forwards a plain environment_variables map (default empty) for optional overrides, but the static frontend reads none of them at runtime.

Note — vestigial Matrix/Element inputs. This module was scaffolded from the Element template, so the two platform variants still declare homeserver_url and homeserver_name inputs and inject them as HOMESERVER_URL / HOMESERVER_NAME environment variables. The excalidraw/excalidraw static SPA does not read these values — they are inert carry-over and can be left at their defaults. Likewise, some variable descriptions in the module still reference "the leading Matrix web client"; the deployed artefact is the Excalidraw whiteboard image, as the Dockerfile FROM line confirms.


5. Health probe behaviour​

All three probes (startup, liveness, readiness) are HTTP GET on the root path /, which nginx serves with a 200 as soon as the container starts. Because there is no backend to initialise and no migrations to run, the service becomes healthy almost immediately — the generous startup window in the other application modules is unnecessary here.

  • Startup probe: HTTP /, 10-second initial delay, 6-retry window.
  • Liveness probe: HTTP /, 15-second initial delay, checked every 30 seconds.
  • Readiness probe: HTTP /, 10-second initial delay, checked every 10 seconds.

The per-variant Excalidraw_CloudRun / Excalidraw_GKE inputs can override these, but the root-path default is correct for the static SPA and should rarely be changed.


For the Excalidraw-specific, user-facing configuration (variables by group, outputs, and how to explore each service from the Console and CLI), see the platform guides: Excalidraw_GKE and Excalidraw_CloudRun.

Need RAD to do something it does not do yet? Request it on the roadmap, or vote on what is already there.