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.
- Open
http://your-server:3000in your browser - Create your admin account with email and password
- 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
| Setting | Variable | Default | Description |
|---|---|---|---|
| Server URL | SERVER_URL | — | Must match the public URL exactly |
| Storage type | STORAGE_TYPE | local | local or s3 for file storage |
| App secret | APP_SECRET | — | Encryption key for sessions and tokens |
| DB migrations | DISABLE_DB_MIGRATIONS | false | Set true on worker only |
| Cron jobs | DISABLE_CRON_JOBS_REGISTRATION | false | Set 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
| Resource | Minimum | Recommended |
|---|---|---|
| RAM | 1 GB (server + worker) | 2 GB |
| CPU | 2 cores | 4 cores |
| Disk | 2 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.
Related
Get self-hosting tips in your inbox
Get the Docker Compose configs, hardware picks, and setup shortcuts we don't put in articles. Weekly. No spam.
Comments