Twenty

Twenty is an open source CRM and alternative to Salesforce and HubSpot

Updated

Twenty is an open source CRM built as an Nx monorepo: a NestJS API server that also serves the React frontend, plus a background worker. It needs PostgreSQL and Redis. Upstream only ships Docker images, so the steps below are derived from packages/twenty-docker/twenty/Dockerfile and its entrypoint.sh.

Requirements

Create a PostgreSQL database and a Redis database from the Databases tab. When attaching the PostgreSQL database, name the variable PG_DATABASE_URL instead of DATABASE_URL. Keep the Redis variable named REDIS_URL.

Twenty creates the uuid-ossp, unaccent and citext extensions in its database on first boot, so the PostgreSQL contrib package must be installed on the database server (it is on a standard Hatchbox database server).

Building the frontend is memory hungry: upstream runs it with NODE_OPTIONS=--max-old-space-size=8192, and the build peaks above 8 GB, so use a server with 16 GB of RAM, or 8 GB with swap added.

The database migration step uses psql, so install the postgresql-client package on the cron server if it is missing.

Repository

Fork the upstream repository, add the scripts below to your fork, and deploy the fork:

https://github.com/twentyhq/twenty.git

Upstream recommends running a tagged release rather than main. Create a branch in your fork from the latest twenty/vX.Y.Z tag (for example twenty/v2.44.0), commit the scripts there, and set that branch on your Hatchbox app. Node (.nvmrc) and Yarn (packageManager) versions are picked up automatically.

Environment Variables

NODE_ENV=production
SERVER_URL=https://crm.example.com
ENCRYPTION_KEY=<openssl rand -base64 32>
STORAGE_TYPE=local

SERVER_URL is the public URL of your app. ENCRYPTION_KEY encrypts secrets at rest; losing it means losing every stored OAuth token, so keep a copy. PG_DATABASE_URL and REDIS_URL come from the attached databases. To store uploads in S3 instead of on disk, set STORAGE_TYPE=S_3 and the STORAGE_S3_* variables from the self-hosting docs.

Build Scripts

.hatchbox/pre-build

Uploads are written to packages/twenty-server/.local-storage, so point that at a shared directory:

#!/usr/bin/env bash
set -e

mkdir -p $DIR/shared/local-storage
ln -s $DIR/shared/local-storage packages/twenty-server/.local-storage

.hatchbox/post-build

Hatchbox runs yarn install. This script builds the server and frontend, copies the frontend into the server's dist/front folder the way the Docker image does, and then runs the same database setup the Docker entrypoint runs, on the cron server only:

#!/usr/bin/env bash
set -e

npx nx run twenty-server:lingui:extract
npx nx run twenty-server:lingui:compile
npx nx run twenty-emails:lingui:extract
npx nx run twenty-emails:lingui:compile
npx nx run twenty-server:build

npx nx run twenty-front:lingui:extract
npx nx run twenty-front:lingui:compile
NODE_OPTIONS="--max-old-space-size=8192" npx nx build twenty-front

rm -rf packages/twenty-server/dist/front
cp -r packages/twenty-front/build packages/twenty-server/dist/front

if [ "$CRON" = "true" ]; then
cd packages/twenty-server

has_schema=$(psql -X -tAc "SELECT EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = 'core')" "$PG_DATABASE_URL")
if [ "$has_schema" = "f" ]; then
yarn database:init:prod
fi

yarn command:prod cache:flush || true
yarn command:prod upgrade
yarn command:prod cache:flush || true
yarn command:prod cron:register:all
fi

Processes

Twenty reads its port from NODE_PORT, so pass Hatchbox's PORT through in the command.

  1. web, web servers: cd packages/twenty-server && NODE_PORT=$PORT node dist/main
  2. worker, worker servers: cd packages/twenty-server && node dist/queue-worker/queue-worker

First Login

Open your domain and sign up. The first user becomes the admin of the workspace with access to the admin panel, and new sign-ups are closed once that first workspace exists.

Notes

Local uploads live in shared/local-storage, so keep the app on a single server or switch to S3 storage before adding more. The worker shares that same directory, so run it on the web server when using local storage.

Each deploy rebuilds both packages, which takes several minutes. To upgrade, merge the next release tag into your branch and deploy; the post-build script runs Twenty's upgrade command for you.