docs/Platforms/FOSS/Docker/Meteor3/multitenancy.md
Run several WeKan instances (one per customer / domain) on the same Linux host. Two supported topologies, pick whichever fits your deployment.
Both run one WeKan Node.js server per tenant, and that is still the topology to pick when customers must not share a process.
One server can also serve every domain, with Organizations as the tenants:
each Organization claims its own hostnames, carries its own branding and theme, has
its own administrators, and can be backed up and restored on its own. It is enabled
with MULTITENANCY=true (and MULTITENANCY_TRUST_PROXY_HOST=true when a trusted
proxy sets X-Forwarded-Host). What it isolates — and what it deliberately does not,
which is why the per-process topology below stays supported — is
Multitenancy design, option D.
uws + changeStreamsHighest DDP throughput and lowest reactive latency.
Configure each instance with:
DDP_TRANSPORT=uwsMETEOR_REACTIVITY_ORDER=changeStreams,oplog,pollingMeteor.settings.packages['ddp-server'].uws.port
per instance (5001, 5002, 5003, … in the example below)network_mode: "host" (or any setup that shares one kernel
network namespace)Why the distinct uws.port: the uws DDP transport runs its own
internal WebSocket proxy server on a dedicated TCP port (default
127.0.0.1:5001) and forwards public WebSocket upgrades into it.
The internal listen socket is opened exclusively, so each instance
in the same kernel netns must declare its own internal port.
Working example: see docker-compose-multitenancy.yml
in the repository root.
Sketch of the per-tenant environment block:
environment:
- PORT=8081 # public HTTP port
- ROOT_URL=https://tenant1.example.com
- MONGO_URL=mongodb://127.0.0.1:27017/wekan_tenant1?replicaSet=rs0
- DDP_TRANSPORT=uws
- METEOR_REACTIVITY_ORDER=changeStreams,oplog,polling
- METEOR_SETTINGS={"packages":{"ddp-server":{"uws":{"port":5001,"host":"127.0.0.1"}}}}
For tenant 2, bump PORT, ROOT_URL, the MongoDB database name, and
the uws.port (e.g. 5002). The reverse proxy in front of WeKan
keeps routing customer domains to each tenant's public PORT
exactly as before — the internal uws.port is never exposed.
From the host (or from inside any container sharing the netns):
cat /proc/net/tcp | awk '$4 == "0A" {print $2}' | sort | uniq -c
Each uws.port should appear exactly once. 0x1389 is 5001,
0x138A is 5002, etc. If you see 2 0100007F:1389, two tenants
are trying to share the default port; reconfigure their
METEOR_SETTINGS.uws.port to distinct values and restart.
uws.portThe second tenant to start refuses the bind and Meteor throws at startup with an actionable error:
Error: uWebSockets.js: failed to listen on 127.0.0.1:5001 (address already in use).
Another Meteor instance in this network namespace is already bound to this port.
Set a distinct Meteor.settings.packages["ddp-server"].uws.port (or .host) for each instance.
Add a METEOR_SETTINGS line with a free port to the failing
service, restart.
sockjs + oplog (maximum proxy compatibility)Use this if your reverse proxy / load balancer cannot guarantee WebSocket upgrade pass-through, or if some clients sit on networks that block raw WebSocket. SockJS will fall back to HTTP long-polling on those clients.
- DDP_TRANSPORT=sockjs
- METEOR_REACTIVITY_ORDER=oplog,polling
No per-tenant uws.port configuration is needed because sockjs
does not run a separate internal listen socket. The trade-off is
lower DDP throughput compared to uws.