Docker一键部署Twenty CRM

Docker一键部署Twenty CRM
💡
Twenty CRM 是一款面向现代化团队的开源客户关系管理(CRM)系统。它采用主流的 React + NestJS + PostgreSQL 技术栈构建,不仅拥有类似 Notion 的高颜值、现代化交互界面,更原生集成了对 AI 自动化(如 MCP 协议)的支持。

一、项目简介


项目简介

Twenty CRM 是一款面向现代化团队的开源客户关系管理(CRM)系统。它采用主流的 TypeScript/React技术栈,拥有类似Notion的高颜值交互界面。作为Salesforce的现代化开源替代方案,Twenty完美融合了AI时代的自动化需求(原生支持MCP协议与AI Agents集成)。项目支持完全的自托管(Self-Hosted)部署,旨在帮助企业彻底摆脱云端SaaS的厂商锁死,实现100% 的客户数据自主权。

技术生态优势

现代化全栈架构:前端基于React + Tailwind CSS确保极致的响应体验,后端采用NestJS + Node.js提供稳健的API服务,底层数据由PostgreSQL与Redis驱动,技术栈成熟且对开发者极其友好。

AI-Ready(AI 原生就绪):区别于传统CRM,Twenty从底层即为AI智能体(Agents)和现代大模型编码工具留出了标准接口,支持通过自然语言直接进行数据检索与流转自动化。

代码即配置(Code-as-Config):系统的对象、字段和业务逻辑允许直接通过TypeScript 代码或SDK进行定义,这意味着可以将CRM的配置纳入Git版本管理,无缝对接企业现有的CI/CD 自动化运维流水线。

原生容器化支持:官方提供完善的Docker部署生态,支持一键实现微服务架构的容器化编排,极大地降低了私有化部署、数据备份与平滑升级的运维门槛。

二、环境准备


  • VPS一台 最低配置2核4G 带宽1M即可
  • 服务器系统:本文演示的系统是Ubuntu-24.04.1(LTS)
  • 是否备案:如果服务器在国外,站点无须备案;如果服务器在国内,网站必须备案。
  • 域名一个
  • 图床一个(又拍云、阿里云oss、腾讯云cos、七牛云等)
  • 为了便于后期的升级维护以及卸载,利用docker+Nginx Proxy Manager部署
  • 安装好Docker、Docker-compose、Nginx Proxy Manager(安装教程
  • SSH工具:FinalShell官网
  • 项目地址:Github官网
  • 官方软硬件配置要求请参考以下链接:Twenty官网

三、部署过程


系统初始化,更新索引

#更新索引库
sudo apt-get update

创建部署目录

# 创建Twenty部署目录
mkdir -p /root/data/docker_workspace/twenty
# 进入目录
cd /root/data/docker_workspace/twenty

获取 docker-compose.yml和env环境配置文件

# 获取compose文件
curl -o docker-compose.yml \
https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml
# 获取.env环境配置文件
curl -o .env \
https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-docker/.env.example

编辑环境变量文件

# 编辑环境变量文件
nano .env

修改成以下内容

TAG=latest

PG_DATABASE_USER=postgres
PG_DATABASE_PASSWORD=INSERT_GENERATED_SECRET_HERE
PG_DATABASE_HOST=db
PG_DATABASE_PORT=5432
REDIS_URL=redis://redis:6379

SERVER_URL=http://localhost:3000

# Use openssl rand -base64 32 for each secret
ENCRYPTION_KEY=INSERT_GENERATED_SECRET_HERE
FALLBACK_ENCRYPTION_KEY=INSERT_GENERATED_SECRET_HERE
APP_SECRET=INSERT_GENERATED_SECRET_HERE

STORAGE_TYPE=local

# STORAGE_S3_REGION=eu-west3
# STORAGE_S3_NAME=my-bucket
# STORAGE_S3_ENDPOINT=
# STORAGE_S3_ACCESS_KEY_ID=
# STORAGE_S3_SECRET_ACCESS_KEY=

其中四个密码:PG_DATABASE_PASSWORDENCRYPTION_KEYFALLBACK_ENCRYPTION_KEYAPP_SECRET均使用openssl rand -base64 32命令随机生成,首次部署的时候,FALLBACK_ENCRYPTION_KEY密码为空。其他三个密码必须是独立的。

效果如下

TAG=latest

PG_DATABASE_USER=postgres
PG_DATABASE_PASSWORD=hLR5qvru8VgX2m+5rw2mpEcb2y1iB09lXqOfSdO0jAI=
PG_DATABASE_HOST=db
PG_DATABASE_PORT=5432
REDIS_URL=redis://redis:6379

SERVER_URL=http://47.168.92.48:3000    # 改为你的服务器IP

DISABLE_DB_MIGRATIONS=false
DISABLE_CRON_JOBS_REGISTRATION=false

# Use openssl rand -base64 32 for each secret
ENCRYPTION_KEY=r8SOZy9wu2P1k6jwkOhlNDXf11/dW7cHvLTmTGQgBH0=
FALLBACK_ENCRYPTION_KEY=
APP_SECRET=+JWPNq8lmvxo45hMsHbtZ9GJBQltOmp6qSHr1DaeYbE=

STORAGE_TYPE=local

# STORAGE_S3_REGION=eu-west3
# STORAGE_S3_NAME=my-bucket
# STORAGE_S3_ENDPOINT=
# STORAGE_S3_ACCESS_KEY_ID=
# STORAGE_S3_SECRET_ACCESS_KEY=

请在服务器内部和云平台的安全组中开启3000端口。如果是生产环境中,.env中的SERVER_URL值请改为自己已经解析好的域名

官网的docker-compose.yml文件里面关于healthy的检测太过严格,启动容器的时候经常会导致失败

放宽 healthcheck,修改 docker-compose.yml如下,可复制直接替换

name: twenty

services:
  server:
    image: twentycrm/twenty:${TAG:-latest}
    volumes:
      - server-local-data:/app/packages/twenty-server/.local-storage
    ports:
      - "3000:3000"
    environment:
      NODE_PORT: 3000
      PG_DATABASE_URL: postgres://${PG_DATABASE_USER:-postgres}:${PG_DATABASE_PASSWORD:-postgres}@${PG_DATABASE_HOST:-db}:${PG_DATABASE_PORT:-5432}/default
      SERVER_URL: ${SERVER_URL}
      REDIS_URL: ${REDIS_URL:-redis://redis:6379}
      DISABLE_DB_MIGRATIONS: ${DISABLE_DB_MIGRATIONS}
      DISABLE_CRON_JOBS_REGISTRATION: ${DISABLE_CRON_JOBS_REGISTRATION}

      STORAGE_TYPE: ${STORAGE_TYPE}
      STORAGE_S3_REGION: ${STORAGE_S3_REGION}
      STORAGE_S3_NAME: ${STORAGE_S3_NAME}
      STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT}

      ENCRYPTION_KEY: ${ENCRYPTION_KEY}
      FALLBACK_ENCRYPTION_KEY: ${FALLBACK_ENCRYPTION_KEY}
      APP_SECRET: ${APP_SECRET:-}
      # MESSAGING_PROVIDER_GMAIL_ENABLED: ${MESSAGING_PROVIDER_GMAIL_ENABLED}
      # CALENDAR_PROVIDER_GOOGLE_ENABLED: ${CALENDAR_PROVIDER_GOOGLE_ENABLED}
      # AUTH_GOOGLE_CLIENT_ID: ${AUTH_GOOGLE_CLIENT_ID}
      # AUTH_GOOGLE_CLIENT_SECRET: ${AUTH_GOOGLE_CLIENT_SECRET}
      # AUTH_GOOGLE_CALLBACK_URL: ${AUTH_GOOGLE_CALLBACK_URL}
      # AUTH_GOOGLE_APIS_CALLBACK_URL: ${AUTH_GOOGLE_APIS_CALLBACK_URL}

      # CALENDAR_PROVIDER_MICROSOFT_ENABLED: ${CALENDAR_PROVIDER_MICROSOFT_ENABLED}
      # MESSAGING_PROVIDER_MICROSOFT_ENABLED: ${MESSAGING_PROVIDER_MICROSOFT_ENABLED}
      # AUTH_MICROSOFT_ENABLED: ${AUTH_MICROSOFT_ENABLED}
      # AUTH_MICROSOFT_CLIENT_ID: ${AUTH_MICROSOFT_CLIENT_ID}
      # AUTH_MICROSOFT_CLIENT_SECRET: ${AUTH_MICROSOFT_CLIENT_SECRET}
      # AUTH_MICROSOFT_CALLBACK_URL: ${AUTH_MICROSOFT_CALLBACK_URL}
      # AUTH_MICROSOFT_APIS_CALLBACK_URL: ${AUTH_MICROSOFT_APIS_CALLBACK_URL}

      # EMAIL_FROM_ADDRESS: ${EMAIL_FROM_ADDRESS:-contact@yourdomain.com}
      # EMAIL_FROM_NAME: ${EMAIL_FROM_NAME:-"John from YourDomain"}
      # EMAIL_DRIVER: ${EMAIL_DRIVER:-smtp}
      # EMAIL_SMTP_HOST: ${EMAIL_SMTP_HOST:-smtp.gmail.com}
      # EMAIL_SMTP_PORT: ${EMAIL_SMTP_PORT:-465}
      # EMAIL_SMTP_USER: ${EMAIL_SMTP_USER:-}
      # EMAIL_SMTP_PASSWORD: ${EMAIL_SMTP_PASSWORD:-}

    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: curl -f http://localhost:3000/healthz || exit 1
      interval: 20s
      timeout: 10s
      retries: 10
      start_period: 120s
    restart: always

  worker:
    image: twentycrm/twenty:${TAG:-latest}
    volumes:
      - server-local-data:/app/packages/twenty-server/.local-storage
    command: ["yarn", "worker:prod"]
    environment:
      PG_DATABASE_URL: postgres://${PG_DATABASE_USER:-postgres}:${PG_DATABASE_PASSWORD:-postgres}@${PG_DATABASE_HOST:-db}:${PG_DATABASE_PORT:-5432}/default
      SERVER_URL: ${SERVER_URL}
      REDIS_URL: ${REDIS_URL:-redis://redis:6379}
      DISABLE_DB_MIGRATIONS: "true" # it already runs on the server
      DISABLE_CRON_JOBS_REGISTRATION: "true" # it already runs on the server

      STORAGE_TYPE: ${STORAGE_TYPE}
      STORAGE_S3_REGION: ${STORAGE_S3_REGION}
      STORAGE_S3_NAME: ${STORAGE_S3_NAME}
      STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT}

      ENCRYPTION_KEY: ${ENCRYPTION_KEY}
      FALLBACK_ENCRYPTION_KEY: ${FALLBACK_ENCRYPTION_KEY}
      APP_SECRET: ${APP_SECRET:-}
      # MESSAGING_PROVIDER_GMAIL_ENABLED: ${MESSAGING_PROVIDER_GMAIL_ENABLED}
      # CALENDAR_PROVIDER_GOOGLE_ENABLED: ${CALENDAR_PROVIDER_GOOGLE_ENABLED}
      # AUTH_GOOGLE_CLIENT_ID: ${AUTH_GOOGLE_CLIENT_ID}
      # AUTH_GOOGLE_CLIENT_SECRET: ${AUTH_GOOGLE_CLIENT_SECRET}
      # AUTH_GOOGLE_CALLBACK_URL: ${AUTH_GOOGLE_CALLBACK_URL}
      # AUTH_GOOGLE_APIS_CALLBACK_URL: ${AUTH_GOOGLE_APIS_CALLBACK_URL}

      # CALENDAR_PROVIDER_MICROSOFT_ENABLED: ${CALENDAR_PROVIDER_MICROSOFT_ENABLED}
      # MESSAGING_PROVIDER_MICROSOFT_ENABLED: ${MESSAGING_PROVIDER_MICROSOFT_ENABLED}
      # AUTH_MICROSOFT_ENABLED: ${AUTH_MICROSOFT_ENABLED}
      # AUTH_MICROSOFT_CLIENT_ID: ${AUTH_MICROSOFT_CLIENT_ID}
      # AUTH_MICROSOFT_CLIENT_SECRET: ${AUTH_MICROSOFT_CLIENT_SECRET}
      # AUTH_MICROSOFT_CALLBACK_URL: ${AUTH_MICROSOFT_CALLBACK_URL}
      # AUTH_MICROSOFT_APIS_CALLBACK_URL: ${AUTH_MICROSOFT_APIS_CALLBACK_URL}

      # EMAIL_FROM_ADDRESS: ${EMAIL_FROM_ADDRESS:-contact@yourdomain.com}
      # EMAIL_FROM_NAME: ${EMAIL_FROM_NAME:-"John from YourDomain"}
      # EMAIL_DRIVER: ${EMAIL_DRIVER:-smtp}
      # EMAIL_SMTP_HOST: ${EMAIL_SMTP_HOST:-smtp.gmail.com}
      # EMAIL_SMTP_PORT: ${EMAIL_SMTP_PORT:-465}
      # EMAIL_SMTP_USER: ${EMAIL_SMTP_USER:-}
      # EMAIL_SMTP_PASSWORD: ${EMAIL_SMTP_PASSWORD:-}

    depends_on:
      db:
        condition: service_healthy
      server:
        condition: service_healthy
    restart: always

  db:
    image: postgres:16
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: ${PG_DATABASE_NAME:-default}
      POSTGRES_PASSWORD: ${PG_DATABASE_PASSWORD:-postgres}
      POSTGRES_USER: ${PG_DATABASE_USER:-postgres}
    healthcheck:
      test: pg_isready -U ${PG_DATABASE_USER:-postgres} -h localhost -d postgres
      interval: 5s
      timeout: 5s
      retries: 10
    restart: always

  redis:
    image: redis
    restart: always
    command: ["--maxmemory-policy", "noeviction"]
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 10

volumes:
  db-data:
  server-local-data:

启动 Docker 容器

docker compose up -d

使用你的服务器地址:3000端口访问

使用电子邮件注册登录后初始化数据库

域名访问

可参考下面的文章去Nginx Proxy Manager里设置域名访问

Ubuntu搭建docker部署wordpress博客
Title:在Ubuntu上使用Docker部署WordPress博客:详细教程与Nginx Proxy Manager配置 Description:学习如何在Ubuntu服务器上使用Docker搭建WordPress博客。通过详细步骤,安装Docker、Nginx Proxy Manager(NPM)和WordPress,并在NPM管理界面进行域名绑定和证书申请,实现用自定义域名访问博客。 Tags:Ubuntu Docker WordPress, Nginx Proxy Manager, 自定义域名, 证书申请, 配置教程.

卸载


进入目录

# 进入自己的项目路径
cd /root/data/docker_workspace/twenty

# 停止并删除所有容器
docker compose down

# Twenty的数据都在下面两个volume里,分别删除
docker volume rm twenty_db-data
docker volume rm twenty_server-local-data

# 删除镜像
docker rmi twentycrm/twenty:latest

# 回到目录上层
cd /root/data/docker_workspace

# 删除项目文件
rm -rf twenty