Self-Hosted 3D Virtual Tabletop

Your table, on your own server

TerrainTale is not only a hosted product — it is a small, ordinary Node app you can run yourself: one process, one database, one deploy. No dependency on the hosted site's accounts or plans.

One process to run it

`npm run build` (bakes SITE_URL into canonical URLs and the sitemap) then `npm start` runs `node server/index.mjs`, which serves the built app, the API and the sign-in gate together. Put it behind any reverse proxy that passes WebSocket upgrades through (Caddy and nginx both do) for TLS. A minimal deploy: clone the repository onto a small server, install dependencies, set SITE_URL to your domain, build, and start the server under a process manager such as systemd so it comes back after a reboot. The 3D rendering happens in each player's browser, not on the server, so a modest machine handles a group. Library models are served with the app, so players need nothing installed.

Data stays where you put it

Everything lives in Postgres: a real `DATABASE_URL` in production, or PGlite (Postgres in-process) under `data/pg/` with none set, so a fresh checkout needs no external database. Uploaded models go to a blob store, Cloudflare R2 or a plain directory (`data/blobs/`). Back up with `pg_dump` plus the bucket or directory. The database holds accounts and saved scenes. The blob store holds uploaded model files and token pictures, which grow quickly if your group imports photoscans, so put that directory on a disk with room. A nightly database dump plus a copy of the blob directory on another machine is a complete backup. Restoring is loading the dump and putting the files back.

Sign-in without a hosted account

Email + password, one-time email links, and Google / Facebook / Apple / GitHub / Discord sign-in are all handled by the same server, with no third-party SDK. `ALLOW_SIGNUP=0` or `ALLOWED_EMAILS=…` turns a public copy into a private table for just your group. For a private group, the simplest setup is an invite list with your players' addresses and one-time email links for sign-in. Mail goes out through Resend when its key is set; without it, the messages are printed to the server console, which is enough to test with. Each social sign-in provider needs its own client ID and secret from that provider.

No billing unless you turn it on

With no Creem key, no mock and `PLANS_ENFORCED` unset, billing is off entirely: every account is unlimited, the paywall dialog never appears, and the account page reports 200 scenes free, the self-hoster's default. The whole product, including your own model imports, is open on every plan on a self-hosted copy. If you do want to charge, for example to cover a server for a community, billing runs through Creem and the same limits as the hosted site apply: one saved scene on free, 200 on Premium. Shared rooms, the fog brush, the dice tray and the initiative tracker work the same either way.

Questions

Is the code open source?

Yes, TerrainTale is self-hostable under the repository's licence: clone it, build it, and run it yourself. Read the licence file before redistributing a modified copy or running it as a service for others.

Do I need a separate database server?

No. Without `DATABASE_URL` it runs on PGlite, Postgres in-process, under `data/pg/`; set `DATABASE_URL` to point at a real Postgres for production. PGlite is fine for one group on one machine. Move to a real Postgres when you want managed backups or a database you already run.

Are Premium features gated on a self-hosted copy?

Only if you turn billing on. By default there is no billing at all, so importing your own models and keeping many saved scenes are open on every account. Turning it on takes a Creem key, the billing mock, or PLANS_ENFORCED set.

Does shared-room play (WebSockets) work behind my proxy?

Yes, as long as the reverse proxy passes WebSocket upgrades through; Caddy and nginx do this by default. If players connect but the table never goes live, check the proxy's WebSocket setting first.

Ready to put a map on the table?

Open the builder and start, no account needed. Sixteen starter scenes are on the welcome screen; sign up free when you want to save one or play it with your group.