name: crm-new # 全新一套:LibreDesk(平台 + 内置 AI)+ Twenty(CRM)+ bridge(业务工具/集成) # 与旧栈 crm/crm-ai-demo 完全隔离:独立 PG/Redis/端口/数据卷。 # # 对外端口: # LibreDesk http://localhost:9000 # Twenty http://localhost:3100 # bridge http://localhost:4100 # Postgres(宿主机) 127.0.0.1:5440 # Redis(宿主机) 127.0.0.1:6390 services: postgres: image: ${POSTGRES_IMAGE:-pgvector/pgvector:pg18} container_name: crmnew-postgres restart: unless-stopped ports: - "127.0.0.1:5440:5432" environment: POSTGRES_USER: ${POSTGRES_USER:-crmnew} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB:-postgres} volumes: # PG18 官方镜像要求挂载 /var/lib/postgresql(数据在子目录) - pg-data:/var/lib/postgresql - ./infra/init-databases.sql:/docker-entrypoint-initdb.d/01-init-databases.sql:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-crmnew} -d postgres"] interval: 5s timeout: 5s retries: 20 redis: image: ${REDIS_IMAGE:-redis:8.8-alpine} container_name: crmnew-redis restart: unless-stopped command: ["redis-server", "--appendonly", "yes"] ports: - "127.0.0.1:6390:6379" volumes: - redis-data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 20 # ---------------------------------------------------------------- LibreDesk libredesk: image: ${LIBREDESK_IMAGE:-libredesk/libredesk:v2.9.0-rc.10} container_name: crmnew-libredesk restart: unless-stopped ports: - "9000:9000" environment: LIBREDESK_SYSTEM_USER_PASSWORD: ${LIBREDESK_SYSTEM_USER_PASSWORD} depends_on: postgres: condition: service_healthy redis: condition: service_healthy volumes: - ./libredesk/uploads:/libredesk/uploads:rw - ./libredesk/config/config.toml:/libredesk/config.toml:ro command: - sh - -c - >- ./libredesk --install --idempotent-install --yes --config /libredesk/config.toml && ./libredesk --upgrade --yes --config /libredesk/config.toml && ./libredesk --config /libredesk/config.toml # ------------------------------------------------------------------- Twenty twenty-server: image: twentycrm/twenty:${TWENTY_TAG:-v2.30.1} container_name: crmnew-twenty-server restart: unless-stopped ports: - "3100:3000" environment: NODE_PORT: 3000 PG_DATABASE_URL: postgres://${POSTGRES_USER:-crmnew}:${POSTGRES_PASSWORD}@postgres:5432/twenty SERVER_URL: ${TWENTY_SERVER_URL:-http://localhost:3100} REDIS_URL: redis://redis:6379/1 DISABLE_DB_MIGRATIONS: "false" DISABLE_CRON_JOBS_REGISTRATION: "false" STORAGE_TYPE: local ENCRYPTION_KEY: ${TWENTY_ENCRYPTION_KEY} FALLBACK_ENCRYPTION_KEY: ${TWENTY_FALLBACK_ENCRYPTION_KEY} APP_SECRET: ${TWENTY_APP_SECRET} IS_BILLING_ENABLED: "false" SIGN_IN_PREFILLED: "false" EMAIL_DRIVER: logger volumes: - twenty-data:/app/packages/twenty-server/.local-storage depends_on: postgres: condition: service_healthy redis: condition: service_healthy healthcheck: test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/healthz"] interval: 10s timeout: 5s retries: 30 start_period: 90s twenty-worker: image: twentycrm/twenty:${TWENTY_TAG:-v2.30.1} container_name: crmnew-twenty-worker restart: unless-stopped command: ["yarn", "worker:prod"] environment: PG_DATABASE_URL: postgres://${POSTGRES_USER:-crmnew}:${POSTGRES_PASSWORD}@postgres:5432/twenty SERVER_URL: ${TWENTY_SERVER_URL:-http://localhost:3100} REDIS_URL: redis://redis:6379/1 DISABLE_DB_MIGRATIONS: "true" DISABLE_CRON_JOBS_REGISTRATION: "true" STORAGE_TYPE: local ENCRYPTION_KEY: ${TWENTY_ENCRYPTION_KEY} FALLBACK_ENCRYPTION_KEY: ${TWENTY_FALLBACK_ENCRYPTION_KEY} APP_SECRET: ${TWENTY_APP_SECRET} volumes: - twenty-data:/app/packages/twenty-server/.local-storage depends_on: twenty-server: condition: service_healthy # IMAP ID 注入代理:LibreDesk 客户端不发 ID,163 会拒绝 SELECT imap-proxy: image: node:24-alpine container_name: crmnew-imap-proxy restart: unless-stopped command: ["node", "/app/server.js"] environment: LISTEN_PORT: 1993 UPSTREAM_HOST: imap.163.com UPSTREAM_PORT: 993 ports: - "127.0.0.1:1993:1993" volumes: - ./services/imap-id-proxy:/app:ro depends_on: - libredesk # 公网网关:仅暴露 WhatsApp webhook + 帮助中心,其余 404 public-gateway: image: node:24-alpine container_name: crmnew-public-gateway restart: unless-stopped command: ["node", "/app/server.js"] environment: PORT: 9001 UPSTREAM: http://libredesk:9000 volumes: - ./services/public-gateway:/app:ro depends_on: - libredesk # Cloudflare 临时隧道:给 Meta 一个公网 HTTPS 回调地址 cloudflared: image: cloudflare/cloudflared:latest container_name: crmnew-cloudflared restart: unless-stopped command: tunnel --no-autoupdate --url http://public-gateway:9001 depends_on: - public-gateway # ------------------------------------------------------------------- bridge bridge: build: context: ./bridge container_name: crmnew-bridge restart: unless-stopped extra_hosts: - "host.docker.internal:host-gateway" ports: - "4100:4100" environment: TOOLS_PORT: 4100 TOOLS_SHARED_KEY: ${TOOLS_SHARED_KEY} TOOLS_PUBLIC_BASE_URL: http://bridge:4100 TOOLS_DEV_BYPASS: ${TOOLS_DEV_BYPASS:-false} RECOMMEND_URL: ${RECOMMEND_URL} ORDER_API_URL: ${ORDER_API_URL} INVENTORY_API_URL: ${INVENTORY_API_URL} MEMBER_RPC_URL: ${MEMBER_RPC_URL} RPC_TIMEOUT_MS: ${RPC_TIMEOUT_MS:-8000} PRODUCT_TIMEOUT_MS: ${PRODUCT_TIMEOUT_MS:-15000} SITE_SEARCH_URL: ${SITE_SEARCH_URL} TWENTY_BASE_URL: http://twenty-server:3000 TWENTY_GRAPHQL_PATH: /graphql TWENTY_WORKSPACE_ID: ${TWENTY_WORKSPACE_ID:-} TWENTY_API_KEY: ${TWENTY_API_KEY:-} LIBREDESK_BASE_URL: ${LIBREDESK_BASE_URL} LIBREDESK_API_KEY: ${LIBREDESK_API_KEY:-} LIBREDESK_API_SECRET: ${LIBREDESK_API_SECRET:-} LIBREDESK_WEBHOOK_SECRET: ${LIBREDESK_WEBHOOK_SECRET:-} LIBREDESK_WEBHOOK_INBOX_IDS: ${LIBREDESK_WEBHOOK_INBOX_IDS:-1} LLM_BASE_URL: ${LLM_BASE_URL:-} LLM_API_KEY: ${LLM_API_KEY:-} LLM_MODEL: ${LLM_MODEL:-qwen-plus-latest} LLM_TIMEOUT_MS: ${LLM_TIMEOUT_MS:-30000} SMTP_HOST: ${SMTP_HOST:-} SMTP_PORT: ${SMTP_PORT:-465} SMTP_USER: ${SMTP_USER:-} SMTP_PASS: ${SMTP_PASS:-} SMTP_FROM: ${SMTP_FROM:-} RAG_PG_URL: ${RAG_PG_URL:-} EMB_BASE_URL: ${EMB_BASE_URL:-} EMB_API_KEY: ${EMB_API_KEY:-} EMB_MODEL: ${EMB_MODEL:-text-embedding-v4} EMB_DIM: ${EMB_DIM:-1024} EMB_TRANSLATE_MODEL: ${EMB_TRANSLATE_MODEL:-qwen-turbo} LIBREDESK_INBOX_UUID: ${LIBREDESK_INBOX_UUID:-} LIBREDESK_INBOX_SECRET: ${LIBREDESK_INBOX_SECRET:-} SITE_JWT_SECRET: ${SITE_JWT_SECRET:-} IDENTITY_DEV_MODE: ${IDENTITY_DEV_MODE:-false} IDENTITY_CORS_ORIGIN: ${IDENTITY_CORS_ORIGIN:-} IDENTITY_TTL_SEC: ${IDENTITY_TTL_SEC:-3600} REDIS_URL: redis://redis:6379/3 DEFAULT_TENANT_ID: ${DEFAULT_TENANT_ID:-2} DEFAULT_LANGUAGE_ID: ${DEFAULT_LANGUAGE_ID:-1} PG_URL: postgres://${POSTGRES_USER:-crmnew}:${POSTGRES_PASSWORD}@postgres:5432/bridge depends_on: postgres: condition: service_healthy redis: condition: service_healthy healthcheck: test: ["CMD", "wget", "-q", "-O", "-", "http://127.0.0.1:4100/healthz"] interval: 10s timeout: 3s retries: 10 networks: default: name: crm-new_default volumes: pg-data: redis-data: twenty-data: