Self-Hosting Twenty CRM with Docker Compose

What Is Twenty?

Twenty is a modern open-source CRM that aims to be the Salesforce alternative the world actually needs. Built with a clean UI inspired by Notion, it handles contacts, companies, deals, tasks, and email integration — without the enterprise bloat or per-seat pricing that makes Salesforce cost thousands per month.

Updated February 2026: Verified with latest Docker images and configurations.

Twenty stands out from other open-source CRMs like SuiteCRM by being developer-friendly, API-first, and visually modern. It’s early-stage but moving fast, with a growing community and regular releases.

Prerequisites

  • A Linux server (Ubuntu 22.04+ recommended)
  • Docker and Docker Compose installed (guide)
  • 2 GB of free RAM (minimum)
  • 5 GB of free disk space
  • A domain name (recommended for production)

Docker Compose Configuration

Create a docker-compose.yml file:

services:
  twenty-server:
    image: twentycrm/twenty:v1.18.1
    container_name: twenty-server
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    ports:
      - "3000:3000"
    environment:
      - SERVER_URL=${SERVER_URL}
      - APP_SECRET=${APP_SECRET}
      - PG_DATABASE_URL=postgres://twenty:${DB_PASSWORD}@db:5432/twenty
      - REDIS_URL=redis://redis:6379
      - STORAGE_TYPE=local
      - NODE_PORT=3000
    volumes:
      - twenty-data:/app/packages/twenty-server/.local-storage
    healthcheck:
      test: ["CMD", "curl", "--fail", "http://localhost:3000/healthz"]
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 60s

  twenty-worker:
    image: twentycrm/twenty:v1.18.1
    container_name: twenty-worker
    restart: unless-stopped
    command: ["yarn", "worker:prod"]
    depends_on:
      db:
        condition: service_healthy
      twenty-server:
        condition: service_healthy
    environment:
      - SERVER_URL=${SERVER_URL}
      - APP_SECRET=${APP_SECRET}
      - PG_DATABASE_URL=postgres://twenty:${DB_PASSWORD}@db:5432/twenty
      - REDIS_URL=redis://redis:6379
      - STORAGE_TYPE=local
      - DISABLE_DB_MIGRATIONS=true
      - DISABLE_CRON_JOBS_REGISTRATION=true
    volumes:
      - twenty-data:/app/packages/twenty-server/.local-storage

  db:
    image: postgres:16
    container_name: twenty-db
    restart: unless-stopped
    environment:
      - POSTGRES_DB=twenty
      - POSTGRES_USER=twenty
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - twenty-db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U twenty -d twenty"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 20s

  redis:
    image: redis:7-alpine
    container_name: twenty-redis
    restart: unless-stopped
    command: redis-server --maxmemory-policy noeviction
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3

volumes:
  twenty-data:
  twenty-db:

Create a .env file alongside:

# URL where Twenty will be accessed (include protocol, no trailing slash)
SERVER_URL=http://localhost:3000

# Application secret — generate with: openssl rand -base64 32
APP_SECRET=CHANGE_ME_GENERATE_A_REAL_SECRET

# Database password — change this
DB_PASSWORD=change-me-strong-password

Start the stack:

docker compose up -d

Initial Setup

First boot takes 1-2 minutes while the server runs database migrations. Watch progress with docker logs -f twenty-server.

  1. Open http://your-server:3000 in your browser
  2. Create your admin account with email and password
  3. Walk through the onboarding wizard — it sets up your workspace name and invites

The UI is immediately familiar if you’ve used Notion or modern SaaS tools. The left sidebar shows People, Companies, Opportunities, and custom objects.

Configuration

SettingVariableDefaultDescription
Server URLSERVER_URLMust match the public URL exactly
Storage typeSTORAGE_TYPElocallocal or s3 for file storage
App secretAPP_SECRETEncryption key for sessions and tokens
DB migrationsDISABLE_DB_MIGRATIONSfalseSet true on worker only
Cron jobsDISABLE_CRON_JOBS_REGISTRATIONfalseSet true on worker only

Email Integration

Configure SMTP for notifications and email sync:

environment:
  - EMAIL_DRIVER=smtp
  - EMAIL_SMTP_HOST=smtp.gmail.com
  - EMAIL_SMTP_PORT=465
  - [email protected]
  - EMAIL_SMTP_PASSWORD=your-app-password
  - [email protected]
  - EMAIL_FROM_NAME=Twenty CRM

Google OAuth

Enable Google sign-in by adding to the server environment:

environment:
  - AUTH_GOOGLE_ENABLED=true
  - AUTH_GOOGLE_CLIENT_ID=your-client-id
  - AUTH_GOOGLE_CLIENT_SECRET=your-client-secret
  - AUTH_GOOGLE_CALLBACK_URL=${SERVER_URL}/auth/google/redirect

Advanced Configuration

S3-Compatible Storage

For production file storage with MinIO or AWS S3:

environment:
  - STORAGE_TYPE=s3
  - STORAGE_S3_REGION=us-east-1
  - STORAGE_S3_NAME=twenty-uploads
  - STORAGE_S3_ENDPOINT=https://s3.amazonaws.com

Reverse Proxy

Twenty listens on port 3000 (HTTP only). For HTTPS, use a reverse proxy. Update SERVER_URL to your HTTPS domain.

With Nginx Proxy Manager, proxy to twenty-server:3000. WebSocket support is recommended for real-time features.

See Reverse Proxy Setup for full instructions.

Backup

Critical data to back up:

  • twenty-db — PostgreSQL database with all CRM data
  • twenty-data — uploaded files and local storage

Database backup:

docker exec twenty-db pg_dump -U twenty twenty > twenty-backup.sql

See Backup Strategy for a complete approach.

Troubleshooting

Server Stuck on “Migrating Database”

Symptom: Server container keeps restarting, logs show migration errors. Fix: Check PostgreSQL is healthy first: docker exec twenty-db pg_isready -U twenty. If the DB is fine, the migration may have partially completed. Reset with: docker compose down -v and start fresh (data loss — only for new installs).

Worker Not Processing Jobs

Symptom: Emails not sending, background tasks stuck. Fix: Verify the worker has DISABLE_DB_MIGRATIONS=true set. Check worker logs: docker logs twenty-worker. The worker must be able to reach both PostgreSQL and Redis.

”Cannot Connect to Redis” Error

Symptom: Server crashes with Redis connection errors. Fix: Ensure the Redis container is running and healthy. The --maxmemory-policy noeviction flag is required — without it, Redis may evict keys needed by Twenty.

Blank Page After OAuth Setup

Symptom: White screen after Google OAuth redirect. Fix: The AUTH_GOOGLE_CALLBACK_URL must exactly match what’s configured in your Google Cloud Console OAuth credentials. Include the full URL with protocol.

Resource Requirements

ResourceMinimumRecommended
RAM1 GB (server + worker)2 GB
CPU2 cores4 cores
Disk2 GB (app)10 GB+ (with attachments)

PostgreSQL adds ~200 MB RAM. Redis adds ~50 MB. Total stack: ~1.5 GB minimum.

Verdict

Twenty wins on modern UX and developer experience. If you’re building a team CRM and want something that doesn’t look like it was designed in 2008, Twenty is the clear choice over SuiteCRM. The trade-off is maturity — Twenty is younger and has fewer integrations than SuiteCRM’s 15+ years of ecosystem.

For personal relationship tracking (friends, family, networking), use Monica instead. Twenty is built for sales pipelines and business contacts.

FAQ

How does Twenty compare to Salesforce?

Twenty covers the core CRM features — contacts, companies, deals, tasks, email — without Salesforce’s enterprise complexity or $25-300/user/month pricing. It lacks Salesforce’s massive app ecosystem and advanced automation, but for small teams, that’s often a feature, not a bug.

Can I migrate from another CRM to Twenty?

Twenty supports CSV import for contacts and companies. For Salesforce migrations, export your data as CSV and import through Twenty’s UI. Custom field mapping is supported.

Does Twenty support custom objects?

Yes. Twenty’s data model is extensible — you can create custom objects with custom fields, relationships, and views through the settings UI. This is one of its strongest features.

Comments