156 lines
6.5 KiB
YAML
156 lines
6.5 KiB
YAML
# WeKan multi-tenant deployment on one Linux host
|
|
#
|
|
# Use this compose file when you want to run several WeKan instances
|
|
# (one per customer / domain) on the SAME Linux host, all sharing the
|
|
# host's kernel network namespace (network_mode: "host") and one
|
|
# MongoDB replica set, with separate logical databases per tenant.
|
|
#
|
|
# This file shows the minimal extra configuration needed for the
|
|
# `uws` DDP transport plus `changeStreams` reactivity to work
|
|
# correctly in this topology, namely:
|
|
#
|
|
# - DDP_TRANSPORT=uws on every service
|
|
# - METEOR_REACTIVITY_ORDER=changeStreams,oplog,polling on every service
|
|
# - METEOR_SETTINGS with a DISTINCT uws.port per service (5001, 5002, …)
|
|
#
|
|
# Why the distinct uws.port: the uws DDP transport runs an internal
|
|
# WebSocket proxy server on its own TCP port (default 127.0.0.1:5001)
|
|
# and forwards WS upgrades from the public PORT into it. When two
|
|
# services share a kernel netns and both default to 127.0.0.1:5001,
|
|
# the second one to start fails to bind because the listen socket is
|
|
# opened exclusively. Give each service its own internal port via
|
|
# METEOR_SETTINGS and they coexist cleanly.
|
|
#
|
|
# Reference: see docs/Platforms/FOSS/Docker/Meteor3/multitenancy.md
|
|
# and https://docs.meteor.com/performance/ddp-transport#multitenancy
|
|
#
|
|
# Setup:
|
|
# docker compose -f docker-compose-multitenancy.yml up -d
|
|
#
|
|
# Tear down:
|
|
# docker compose -f docker-compose-multitenancy.yml down -v
|
|
#
|
|
# Adding more tenants:
|
|
# Copy a tenant block, change the service name, PORT, ROOT_URL,
|
|
# MONGO_URL database name, and the uws.port inside METEOR_SETTINGS
|
|
# to a fresh value (5003, 5004, …). The MongoDB service and the
|
|
# reverse proxy in front of WeKan don't need any change.
|
|
|
|
version: '3.8'
|
|
|
|
services:
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Shared MongoDB 7 with replica set (required for changeStreams).
|
|
# All tenants connect to this instance using DIFFERENT logical
|
|
# databases (?authSource if you enable auth).
|
|
# ---------------------------------------------------------------------
|
|
wekandb:
|
|
image: mongo:7
|
|
container_name: wekan-db
|
|
restart: always
|
|
network_mode: "host"
|
|
entrypoint: ["sh", "-c"]
|
|
command:
|
|
- |
|
|
mongod --storageEngine wiredTiger --wiredTigerCacheSizeGB 1 --timeZoneInfo /usr/share/zoneinfo --oplogSize 2048 --replSet rs0 --bind_ip 127.0.0.1 --port 27017 --quiet &
|
|
until mongosh --host 127.0.0.1 --port 27017 --quiet --eval 'try { const s = rs.status(); quit(s.ok === 1 ? 0 : 1); } catch (e) { if (e.codeName === "NotYetInitialized" || /no replset config has been received|not yet initialized/.test(e.message)) { rs.initiate({_id:"rs0", members:[{_id:0, host:"127.0.0.1:27017"}]}); quit(1); } quit(1); }' >/dev/null 2>&1; do
|
|
sleep 2
|
|
done
|
|
wait
|
|
# Report healthy only once the replica set has an elected primary, so the
|
|
# tenant apps (depends_on: condition: service_healthy) do not start before
|
|
# MongoDB is ready and crash with "Topology is closed". start_period gives a
|
|
# large database time to replay its journal after an upgrade.
|
|
healthcheck:
|
|
test: ["CMD", "mongosh", "--host", "127.0.0.1", "--port", "27017", "--quiet", "--eval", "try { quit(rs.status().ok === 1 && db.hello().isWritablePrimary ? 0 : 1) } catch (e) { quit(1) }"]
|
|
interval: 10s
|
|
timeout: 10s
|
|
retries: 30
|
|
start_period: 60s
|
|
volumes:
|
|
- wekan-db:/data/db
|
|
- wekan-db-dump:/dump
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Tenant 1
|
|
#
|
|
# Each tenant needs three things distinct from every other tenant:
|
|
# 1. PORT — public HTTP port (here 8081)
|
|
# 2. MONGO_URL database name — here `wekan_tenant1`
|
|
# 3. uws.port in METEOR_SETTINGS — here 5001
|
|
# ---------------------------------------------------------------------
|
|
wekan-tenant1:
|
|
image: ghcr.io/wekan/wekan:latest
|
|
container_name: wekan-tenant1
|
|
restart: always
|
|
network_mode: "host"
|
|
depends_on:
|
|
wekandb:
|
|
condition: service_healthy
|
|
environment:
|
|
- PORT=8081
|
|
- ROOT_URL=https://tenant1.example.com
|
|
- MONGO_URL=mongodb://127.0.0.1:27017/wekan_tenant1?replicaSet=rs0
|
|
- MONGO_OPLOG_URL=mongodb://127.0.0.1:27017/local?replicaSet=rs0
|
|
- DDP_TRANSPORT=uws
|
|
- METEOR_REACTIVITY_ORDER=changeStreams,oplog,polling
|
|
# Internal uws proxy port — MUST be distinct per service.
|
|
- METEOR_SETTINGS={"packages":{"ddp-server":{"uws":{"port":5001,"host":"127.0.0.1"}}}}
|
|
- WRITABLE_PATH=/data
|
|
- WITH_API=true
|
|
volumes:
|
|
- wekan-tenant1-data:/data
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Tenant 2 — same template, distinct PORT / database / uws.port
|
|
# ---------------------------------------------------------------------
|
|
wekan-tenant2:
|
|
image: ghcr.io/wekan/wekan:latest
|
|
container_name: wekan-tenant2
|
|
restart: always
|
|
network_mode: "host"
|
|
depends_on:
|
|
wekandb:
|
|
condition: service_healthy
|
|
environment:
|
|
- PORT=8082
|
|
- ROOT_URL=https://tenant2.example.com
|
|
- MONGO_URL=mongodb://127.0.0.1:27017/wekan_tenant2?replicaSet=rs0
|
|
- MONGO_OPLOG_URL=mongodb://127.0.0.1:27017/local?replicaSet=rs0
|
|
- DDP_TRANSPORT=uws
|
|
- METEOR_REACTIVITY_ORDER=changeStreams,oplog,polling
|
|
- METEOR_SETTINGS={"packages":{"ddp-server":{"uws":{"port":5002,"host":"127.0.0.1"}}}}
|
|
- WRITABLE_PATH=/data
|
|
- WITH_API=true
|
|
volumes:
|
|
- wekan-tenant2-data:/data
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Add more tenants by copying a block above and bumping:
|
|
# PORT (e.g. 8083, 8084, …)
|
|
# ROOT_URL (the tenant's public domain)
|
|
# MONGO_URL (a new database name)
|
|
# uws.port (5003, 5004, …)
|
|
# ---------------------------------------------------------------------
|
|
|
|
volumes:
|
|
wekan-db:
|
|
wekan-db-dump:
|
|
wekan-tenant1-data:
|
|
wekan-tenant2-data:
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Verifying the uws internal listen sockets are isolated.
|
|
#
|
|
# From the host (or any container sharing the netns):
|
|
#
|
|
# cat /proc/net/tcp | awk '$4 == "0A" {print $2}' | sort | uniq -c
|
|
#
|
|
# You want to see ONE listener per uws port — the hex addresses
|
|
# `0100007F:1389` (5001) and `0100007F:138A` (5002) should each
|
|
# appear with count 1, no duplicates. If you see `2 0100007F:1389`,
|
|
# two services are trying to share the default port; reconfigure
|
|
# their METEOR_SETTINGS.uws.port to distinct values and restart.
|
|
# ---------------------------------------------------------------------
|