Ctrl K
ComunidadGuías…

docker-compose-multitenancy.yml

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# 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=sockjs 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/Container/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.

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=sockjs
      - 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
      # SAML overrides: Admin Panel / People / SAML
      #- SAML_ENABLED=false
      #- SAML_PROVIDER=
      #- SAML_ENTRYPOINT=
      #- SAML_ISSUER=
      #- SAML_CERT=
      #- SAML_IDPSLO_REDIRECTURL=
      #- SAML_PRIVATE_KEYFILE=
      #- SAML_PUBLIC_CERTFILE=
      #- SAML_IDENTIFIER_FORMAT=
      # SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE : assertion attribute whose value becomes
      # the WeKan username (e.g. username). Empty = the email / NameID. With
      # SAML_MERGE_EXISTING_USERS=true an existing account is found by that
      # username, or - when the value is the assertion's email - by a verified
      # email, so an LDAP account with its own short username can be linked.
      # Linking an existing account ALSO requires the assertion to carry the
      # attribute email_verified=true (SAML has no standard one: add it at the
      # IdP only if the IdP verifies addresses) and that email to be verified on
      # the local account. A refused link logs its reason in the server log.
      # docs/Features/Login-Page/SAML.md
      #- SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE=
      # SAML_ATTRIBUTES : not used by login at the moment; attributes are read
      # from the validated assertion as named (email, username, displayName).
      #- SAML_ATTRIBUTES=
      #- SAML_MERGE_EXISTING_USERS=false
      #- SAML_IDP_PROFILE=standard
      #- SAML_WANT_RESPONSE_SIGNED=
      #- SAML_WANT_ASSERTIONS_SIGNED=
      #- SAML_LOGIN_FLOW=
    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=sockjs
      - 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
      # SAML overrides: Admin Panel / People / SAML
      #- SAML_ENABLED=false
      #- SAML_PROVIDER=
      #- SAML_ENTRYPOINT=
      #- SAML_ISSUER=
      #- SAML_CERT=
      #- SAML_IDPSLO_REDIRECTURL=
      #- SAML_PRIVATE_KEYFILE=
      #- SAML_PUBLIC_CERTFILE=
      #- SAML_IDENTIFIER_FORMAT=
      # SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE : assertion attribute whose value becomes
      # the WeKan username (e.g. username). Empty = the email / NameID. With
      # SAML_MERGE_EXISTING_USERS=true an existing account is found by that
      # username, or - when the value is the assertion's email - by a verified
      # email, so an LDAP account with its own short username can be linked.
      # Linking an existing account ALSO requires the assertion to carry the
      # attribute email_verified=true (SAML has no standard one: add it at the
      # IdP only if the IdP verifies addresses) and that email to be verified on
      # the local account. A refused link logs its reason in the server log.
      # docs/Features/Login-Page/SAML.md
      #- SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE=
      # SAML_ATTRIBUTES : not used by login at the moment; attributes are read
      # from the validated assertion as named (email, username, displayName).
      #- SAML_ATTRIBUTES=
      #- SAML_MERGE_EXISTING_USERS=false
      #- SAML_IDP_PROFILE=standard
      #- SAML_WANT_RESPONSE_SIGNED=
      #- SAML_WANT_ASSERTIONS_SIGNED=
      #- SAML_LOGIN_FLOW=
    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.
# ---------------------------------------------------------------------