Back to Wekan

Multitenancy

docs/Platforms/FOSS/Docker/Meteor3/multitenancy.md

10.414.2 KB
Original Source

Multitenancy

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.

Highest DDP throughput and lowest reactive latency.

Configure each instance with:

  • DDP_TRANSPORT=uws
  • METEOR_REACTIVITY_ORDER=changeStreams,oplog,polling
  • A distinct Meteor.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)
  • The same MongoDB replica set (separate logical databases per tenant)

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:

yaml
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.

Verifying the configuration

From the host (or from inside any container sharing the netns):

sh
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.

If you forget to set a distinct uws.port

The second tenant to start refuses the bind and Meteor throws at startup with an actionable error:

text
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.

b) Alternative — 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.

yaml
- 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.

Reference