How to Self-Host Twenty CRM — Open Source HubSpot Alternative 2026
What Is Twenty CRM?
Twenty is a modern open source CRM built with a product philosophy closer to Notion or Linear than to Salesforce. It's extensible via a custom objects system, has a clean kanban + table view interface, and is designed to be a platform you can build on top of — not just use.
HubSpot's CRM Suite costs $90/month for 2 users (Starter) and scales steeply. Salesforce starts at $25/user/month but quickly reaches hundreds. Twenty is free when self-hosted — just server costs (~$10/month).
Key features:
- Contacts, Companies, Deals pipeline
- Custom objects (extend the data model without code)
- Kanban, table, and timeline views
- Activity timeline (emails, calls, meetings)
- Workflow automation (beta)
- Developer API (GraphQL)
- Zapier-like automation via integrations
- GitHub Stars: 25k+ (one of the fastest-growing OSS CRMs)
Prerequisites
- VPS with 2 vCPU, 2GB RAM (Hetzner CX32 ~€5.49/month)
- Docker + Docker Compose v2
- Domain name pointing to your server
- SMTP for email (optional but recommended)
Docker Compose Deployment
1. Create Project Directory
mkdir twenty && cd twenty
2. Create docker-compose.yaml
version: "3.8"
networks:
twenty:
volumes:
twenty-db-data:
twenty-server-local-data:
services:
twenty-change-detect:
image: twentycrm/twenty:latest
restart: unless-stopped
networks:
- twenty
depends_on:
twenty-db:
condition: service_healthy
env_file: .env
command: ["yarn", "command:prod", "upgrade"]
twenty-server:
image: twentycrm/twenty:latest
restart: unless-stopped
networks:
- twenty
ports:
- "3000:3000"
depends_on:
twenty-change-detect:
condition: service_completed_successfully
env_file: .env
volumes:
- twenty-server-local-data:/app/packages/twenty-server/.local-storage
twenty-worker:
image: twentycrm/twenty:latest
restart: unless-stopped
networks:
- twenty
depends_on:
twenty-server:
condition: service_started
env_file: .env
command: ["yarn", "command:prod", "worker"]
twenty-db:
image: twentycrm/twenty-postgres-spilo:latest
restart: unless-stopped
networks:
- twenty
volumes:
- twenty-db-data:/home/postgres/pgdata
environment:
- PGUSER_SUPERUSER=postgres
- PGPASSWORD_SUPERUSER=your-postgres-password
- ALLOW_NOSSL=true
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres"]
interval: 5s
timeout: 5s
retries: 10
redis:
image: redis:7-alpine
restart: unless-stopped
networks:
- twenty
3. Create .env
# .env
# App
NODE_ENV=production
SERVER_URL=https://crm.yourdomain.com
# Database
PG_DATABASE_URL=postgres://postgres:your-postgres-password@twenty-db:5432/default
# Storage
STORAGE_TYPE=local # or 's3'
# Redis
REDIS_URL=redis://redis:6379
# Secrets — generate strong random values
APP_SECRET=$(openssl rand -hex 32)
ACCESS_TOKEN_SECRET=$(openssl rand -hex 32)
LOGIN_TOKEN_SECRET=$(openssl rand -hex 32)
REFRESH_TOKEN_SECRET=$(openssl rand -hex 32)
FILE_TOKEN_SECRET=$(openssl rand -hex 32)
# Email (optional)
EMAIL_FROM_ADDRESS=noreply@yourdomain.com
EMAIL_FROM_NAME=Twenty CRM
SMTP_HOST=smtp.yourdomain.com
SMTP_PORT=587
SMTP_USER=your-smtp-user
SMTP_PASSWORD=your-smtp-password
# Auth providers (optional — defaults to email/password)
# AUTH_GOOGLE_ENABLED=true
# AUTH_GOOGLE_CLIENT_ID=your-client-id
# AUTH_GOOGLE_CLIENT_SECRET=your-secret
# AUTH_GOOGLE_CALLBACK_URL=https://crm.yourdomain.com/auth/google/redirect
4. Start Twenty
# Start the database first (migrations run automatically)
docker compose up -d
# Watch logs
docker compose logs -f twenty-server
# Look for: "Server ready at http://localhost:3000"
Configure Caddy Reverse Proxy
# /etc/caddy/Caddyfile
crm.yourdomain.com {
reverse_proxy localhost:3000
request_body {
max_size 20MB
}
}
systemctl reload caddy
Initial Setup: Create Your Workspace
Visit https://crm.yourdomain.com:
1. Click "Create account"
2. Enter your work email and set a password
3. Create your workspace:
- Workspace name: "Acme Corp"
- Subdomain: acme (used in workspace URLs)
4. Invite team members via email
Twenty CRM Core Concepts
Objects = Your Data Model
Twenty has standard objects and lets you create custom ones:
Standard Objects:
├── People (contacts)
├── Companies (accounts)
├── Opportunities (deals)
├── Activities (tasks, notes, calls)
└── Workspace Members (CRM users)
Custom Objects (create via Settings → Objects):
├── Customers (if you want to separate from Contacts)
├── Projects
├── Support Tickets
└── Any domain-specific entity
Creating a Custom Object
Settings → Objects → Add Custom Object
→ Name: "Partnership"
→ Fields:
- name (text)
- partner_company (relation → Companies)
- revenue_share (number, %)
- signed_date (date)
- status (select: Active, Negotiating, Archived)
→ Save
The new object immediately appears in the left nav with table and kanban views.
Data Import
Import Contacts from CSV
People → Import → Upload CSV
Field mapping:
CSV Column → Twenty Field
"First Name" → First Name
"Last Name" → Last Name
"Email" → Email
"Company" → Company (creates or links)
"Phone" → Phone
"City" → City
Import from HubSpot
1. HubSpot: Contacts → Export → CSV (all properties)
2. Twenty: People → Import → Upload CSV
3. Map HubSpot's property names to Twenty fields
For Companies:
1. HubSpot: Companies → Export → CSV
2. Twenty: Companies → Import
3. After import, re-run contacts import to link company relations
Pipeline (Opportunities)
Twenty's opportunities work like a simple Pipedrive:
Opportunities
├── Qualification
├── Demo Scheduled
├── Proposal Sent
├── Negotiation
└── Closed Won / Closed Lost
Each opportunity:
- Amount
- Close date
- Contact (many-to-many with People)
- Company
- Assignee
- Notes and activity timeline
Kanban vs Table View
View toggle: Table | Kanban | Board | Calendar
Kanban: drag opportunities between stages
Table: sort, filter, bulk edit
Calendar: close date view
Email Integration
Twenty can connect to Gmail to log emails automatically:
Settings → Integrations → Gmail
→ Connect Google account
→ Choose which emails to sync:
- From/to specific domains
- With specific contacts in your CRM
Emails appear in the contact/company activity timeline.
Automation (Beta)
Twenty has a workflow builder (in beta):
Workflows → New Workflow
Trigger: "When Opportunity stage changes to Closed Won"
Action:
→ Send email notification to Account Owner
→ Create task: "Send thank you gift"
→ Update People field: status = Customer
GraphQL API
Twenty exposes a full GraphQL API:
# Get all opportunities
query {
opportunities(
filter: { stage: { eq: "NEGOTIATION" } }
orderBy: { amount: DescNullsLast }
) {
edges {
node {
id
name
amount { amountMicros currencyCode }
closeDate
pointOfContactId
company { name }
}
}
}
}
# Create a new contact
mutation {
createPerson(data: {
name: { firstName: "Alice", lastName: "Smith" }
emails: { primaryEmail: "alice@example.com" }
companyId: "company-uuid"
}) {
id
name { firstName lastName }
}
}
Access the API playground at https://crm.yourdomain.com/api.
Use S3 for File Storage
For production teams with file attachments:
# .env
STORAGE_TYPE=s3
STORAGE_S3_REGION=us-east-1
STORAGE_S3_NAME=twenty-crm-files
STORAGE_S3_ENDPOINT= # blank for AWS; set for MinIO/R2
STORAGE_S3_ACCESS_KEY_ID=your-key
STORAGE_S3_SECRET_ACCESS_KEY=your-secret
Backup
#!/bin/bash
# backup-twenty.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backups/twenty"
mkdir -p $BACKUP_DIR
# Database
docker compose exec -T twenty-db pg_dump \
-U postgres default | gzip > $BACKUP_DIR/db_$DATE.sql.gz
# Local storage (attachments)
STORAGE_PATH=$(docker volume inspect twenty_twenty-server-local-data \
--format '{{.Mountpoint}}')
tar -czf $BACKUP_DIR/storage_$DATE.tar.gz "$STORAGE_PATH"
# 30 day retention
find $BACKUP_DIR -mtime +30 -delete
echo "Twenty CRM backed up: $DATE"
Twenty vs HubSpot vs Pipedrive
| Feature | Twenty (self-hosted) | HubSpot (Starter) | Pipedrive |
|---|---|---|---|
| Price (5 seats) | ~$10/mo server | $450/mo | $70/mo |
| Self-hosted | ✅ | ❌ | ❌ |
| Custom objects | ✅ | 💰 Professional+ | ❌ |
| API | ✅ GraphQL | ✅ REST | ✅ REST |
| Email sync | ✅ Gmail | ✅ | ✅ |
| Workflow automation | ⚠️ Beta | ✅ | ✅ |
| AI features | ❌ | ✅ | ✅ |
| Mobile app | ❌ | ✅ | ✅ |
| Data ownership | ✅ Full | ❌ | ❌ |
| White-label | ✅ | ❌ | ❌ |
Troubleshooting
"Database not ready" on startup:
docker compose ps twenty-db
# Wait for healthy status — the postgres-spilo image has a slower startup
# Usually resolves in 30-60 seconds
Worker not processing jobs:
docker compose logs twenty-worker | tail -20
# Common: Redis connection issues — check REDIS_URL in .env
File uploads fail:
docker compose logs twenty-server | grep "storage\|upload"
# Check STORAGE_TYPE and permissions on .local-storage volume
Twenty CRM is a top HubSpot alternative on OSSAlt — explore all open source CRM options.