Twenty CRM mit Docker installieren: Modernes Open-Source-CRM als Salesforce-Alternative
Twenty CRM ist eine Open-Source-Alternative zu Salesforce für KMU. Die Anleitung zeigt die Installation per Docker Compose mit PostgreSQL, Redis und Worker, getestet mit Twenty v2.45.0, samt HTTPS, Backup und Updates.
Geprüft am 04.10.2026 · für Twenty 2.45.0
Mit KI erstellt – redaktionelle Prüfung ausstehend
WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Bei Salesforce, HubSpot und ähnlichen Diensten liegen die Kundendaten in fremden Rechenzentren und die Kosten steigen mit jedem Nutzer. Twenty CRM ist eine Open-Source-Alternative mit Kontakt- und Deal-Verwaltung, anpassbaren Objekten, Workflows und GraphQL-API. Technisch setzt Twenty auf NestJS, React, PostgreSQL und Redis, das GitHub-Repository hat rund 57.000 Sterne. Diese Anleitung zeigt die Installation von Twenty CRM v2.45.0 per Docker Compose auf einem Linux-Host, mit Prüfung nach jedem Schritt und Backup.
Voraussetzungen
- Docker Engine (Version >= 20.10) und Docker Compose Plugin v2, Installation siehe Docker und Docker Compose auf Linux installieren (Ubuntu/Debian).
- Linux-Host, VM oder NAS mit Docker
- Mindestens 2 GB RAM laut Herstellerdoku, empfohlen 4 GB: Im Test belegten Server und Worker nach dem Start zusammen rund 2 GB, die Datenbank weitere 50 bis 80 MB. x86_64 oder ARM64, im Test mit 2 CPU-Kernen
- Mindestens 10 GB freier Festplattenspeicher (Images, Datenbankdaten, Uploads)
opensslzur Schlüsselerzeugung- Internetzugang für den Image-Pull
- Optional: eigene Domain mit DNS-Eintrag und Reverse Proxy (nginx, Traefik oder Caddy) für HTTPS. Ohne HTTPS sind Browser-APIs wie die Clipboard-API nicht nutzbar
Schritt 1: Projektordner anlegen
Legen Sie einen eigenen Ordner für den Stack an. Unter /opt brauchen Sie sudo und übergeben den Ordner danach Ihrem Benutzer:
sudo mkdir -p /opt/twenty-crm
sudo chown "$USER": /opt/twenty-crm
cd /opt/twenty-crmOhne Root-Zugriff nutzen Sie ~/twenty-crm. Verwenden Sie den gewählten Pfad durchgehend.
Verifizieren: Der Ordner existiert und ist das aktuelle Arbeitsverzeichnis.
pwd
# Erwartete Ausgabe: /opt/twenty-crmSchritt 2: .env-Datei mit Secrets anlegen
Der ENCRYPTION_KEY ist der wichtigste Wert des Stacks. Er verschlüsselt die Secrets in der Datenbank: OAuth-Tokens, TOTP-Secrets und App-Variablen. Geht er verloren, sind diese Daten laut Herstellerdoku nicht mehr lesbar. Bewahren Sie ihn zusätzlich im Passwortmanager auf.
Erzeugen Sie zwei Zufallswerte:
# Verschlüsselungsschlüssel (Pflicht, sicher aufbewahren)
openssl rand -base64 32
# Datenbankpasswort (nur Buchstaben und Ziffern, keine Sonderzeichen)
openssl rand -hex 16Legen Sie anschließend die .env an:
# Twenty CRM, Umgebungsvariablen
# ACHTUNG: ENCRYPTION_KEY nicht verlieren, Wechsel nur per Schlüsselrotation
TAG=v2.45.0
ENCRYPTION_KEY=IHR_BASE64_SCHLUESSEL
PG_DATABASE_USER=twenty
PG_DATABASE_PASSWORD=IHR_HEX_PASSWORT
# Für lokalen Betrieb:
SERVER_URL=http://localhost:3000
# Für externen Betrieb (Domain anpassen):
# SERVER_URL=https://crm.meinedomain.de
STORAGE_TYPE=localErsetzen Sie IHR_BASE64_SCHLUESSEL und IHR_HEX_PASSWORT durch die erzeugten Werte und beschränken Sie die Rechte:
chmod 600 /opt/twenty-crm/.envVerifizieren: Die Datei enthält keine Platzhalter mehr und ist nur für den Eigentümer lesbar.
ls -la .env
# Erwartete Ausgabe: -rw------- 1 IHR_BENUTZER ... .env
grep -c "IHR_" .env
# Erwartete Ausgabe: 0 (keine Platzhalter mehr)Schritt 3: compose.yaml erstellen
Der Stack besteht aus vier Services. Das offizielle Image twentycrm/twenty enthält Server und Frontend. Das ältere twentycrm/twenty-server ist auf Docker Hub archiviert (letztes Update März 2024).
Der Worker nutzt dasselbe Image mit yarn worker:prod und deaktiviert Migrationen und Cron-Registrierung, damit nichts doppelt läuft. Er bindet wie im offiziellen Compose-Beispiel dasselbe Volume server-local-data ein, damit Hintergrundjobs auf hochgeladene Dateien zugreifen können. Redis startet mit --maxmemory-policy noeviction, damit die Job-Queue bei Speicherdruck keine Keys verliert.
services:
server:
image: twentycrm/twenty:${TAG:-latest}
ports:
- "3000:3000"
volumes:
- server-local-data:/app/packages/twenty-server/.local-storage
environment:
NODE_PORT: 3000
SERVER_URL: ${SERVER_URL:-http://localhost:3000}
REDIS_URL: redis://redis:6379
PG_DATABASE_URL: postgres://${PG_DATABASE_USER:-twenty}:${PG_DATABASE_PASSWORD}@db:5432/default
STORAGE_TYPE: ${STORAGE_TYPE:-local}
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/healthz"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
restart: always
worker:
image: twentycrm/twenty:${TAG:-latest}
volumes:
- server-local-data:/app/packages/twenty-server/.local-storage
command: ["yarn", "worker:prod"]
environment:
SERVER_URL: ${SERVER_URL:-http://localhost:3000}
REDIS_URL: redis://redis:6379
PG_DATABASE_URL: postgres://${PG_DATABASE_USER:-twenty}:${PG_DATABASE_PASSWORD}@db:5432/default
STORAGE_TYPE: ${STORAGE_TYPE:-local}
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
DISABLE_DB_MIGRATIONS: "true"
DISABLE_CRON_JOBS_REGISTRATION: "true"
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: default
POSTGRES_USER: ${PG_DATABASE_USER:-twenty}
POSTGRES_PASSWORD: ${PG_DATABASE_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${PG_DATABASE_USER:-twenty} -d default"]
interval: 10s
timeout: 5s
retries: 5
restart: always
redis:
image: redis
command: ["--maxmemory-policy", "noeviction"]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: always
volumes:
db-data:
server-local-data:PostgreSQL und Redis haben bewusst kein Port-Mapping, nur Port 3000 ist erreichbar.
Verifizieren: Die YAML-Syntax ist korrekt und docker compose erkennt die Datei.
docker compose config --quiet
# Keine Ausgabe = kein SyntaxfehlerSchritt 4: Stack starten
Beim ersten Start lädt Docker die Images (Twenty v2.45.0 komprimiert rund 260 MB, dazu PostgreSQL und Redis). Danach führt der Server die Datenbankmigrationen aus. Im Test war der Server rund 100 Sekunden nach dem Start healthy, danach startete der Worker.
docker compose up -dStartvorgang in den Logs verfolgen:
docker compose logs -f serverSteht dort Nest application successfully started, ist der Server bereit. Beenden Sie die Log-Ansicht mit Strg+C.
Verifizieren: Alle vier Services laufen, Server, Datenbank und Redis mit healthy:
docker compose ps
# Erwartete Ausgabe (gekürzt):
# NAME IMAGE STATUS
# twenty-crm-server twentycrm/twenty:v2.45.0 Up (healthy)
# twenty-crm-worker twentycrm/twenty:v2.45.0 Up
# twenty-crm-db postgres:16 Up (healthy)
# twenty-crm-redis redis Up (healthy)
curl -I http://localhost:3000/healthz
# Erwartete Ausgabe: HTTP/1.1 200 OKSchritt 5: Erst-Einrichtung im Browser
Öffnen Sie http://localhost:3000 (bzw. IP oder Domain Ihres Servers) im Browser. Beim ersten Aufruf legen Sie über den Einrichtungsassistenten das erste Konto an:
- Konto mit E-Mail-Adresse und starkem Passwort anlegen.
- Workspace einrichten (Name des Unternehmens oder Teams).
- Twenty öffnet danach die CRM-Oberfläche, Kontakte, Deals und anpassbare Objekte sind sofort nutzbar.
- Weitere Teammitglieder laden Sie über Settings → Members ein.
SERVER_URLmuss dafür auf die erreichbare Adresse zeigen, weil Einladungs-E-Mails diese URL verwenden.
Verifizieren: Die Anmeldung funktioniert und die Oberfläche zeigt den leeren Workspace. Auf der Kommandozeile liefert der folgende Aufruf den Seitentitel. Ohne den Accept-Header antwortete v2.43.0 im Test mit 404 Cannot GET /, obwohl der Browser die Seite anzeigte.
curl -s -H 'Accept: text/html' http://localhost:3000 | grep -o "<title>.*</title>"
# Erwartete Ausgabe: <title>Twenty</title>Schritt 6: Reverse Proxy und HTTPS einrichten (Produktionsbetrieb)
Sobald Twenty über eine externe Domain erreichbar sein soll, ist HTTPS Pflicht. Ohne TLS funktionieren Browser-APIs wie die Clipboard-API nicht, und OAuth-Integrationen schlagen fehl.
Beschränken Sie zuerst in der compose.yaml den Port auf localhost:
ports:
- "127.0.0.1:3000:3000"Setzen Sie SERVER_URL in der .env auf Ihre HTTPS-Domain:
SERVER_URL=https://crm.meinedomain.deSchalten Sie dann einen Reverse Proxy davor, siehe Caddy als Reverse Proxy einrichten: Anfänger-Anleitung mit automatischem HTTPS. Ohne Portfreigabe eignet sich auch ein Cloudflare Tunnel.
Stack mit der geänderten Konfiguration neu erstellen:
docker compose up -dVerifizieren: Die HTTPS-Adresse antwortet mit gültigem Zertifikat.
curl -I https://crm.meinedomain.de/healthz
# Erwartete Ausgabe: HTTP/2 200Schritt 7: Backup und Updates
Datenbank-Backup
Twenty speichert alle CRM-Daten in PostgreSQL, Uploads im Volume server-local-data. Für die Datenbank genügt in den meisten KMU-Umgebungen ein täglicher pg_dump:
# Einmaliges Backup
docker exec $(docker compose ps -q db) \
pg_dump -U twenty default > backup_$(date +%Y%m%d).sql
# Backup prüfen
wc -l backup_$(date +%Y%m%d).sql
# Im Test: rund 8.000 Zeilen schon bei leerer InstanzEin 3-2-1-Konzept mit Rotation beschreibt MySQL & PostgreSQL Backup automatisieren mit cron: mysqldump, pg_dump, Rotation und rclone-Cloud-Sync.
Version-Update
Legen Sie vor jedem Update ein Backup an, tragen Sie den neuen Tag in die .env ein und aktualisieren Sie den Stack:
# 1. Backup anlegen (wie oben)
# 2. Neue Version in .env eintragen:
# TAG=v2.45.0 (Beispiel)
# 3. Images pullen und Stack neu starten
docker compose pull && docker compose up -dDer Server führt die Migrationen beim Start aus. Laut Upgrade-Guide sind ab v1.23 Sprünge über mehrere Versionen möglich, ab v2.34 ist PostgreSQL 15 Mindestvoraussetzung. Den Stand prüft docker compose exec server yarn command:prod upgrade:status, im Test mit v2.43.0 meldete er Instance: Up to date. Den Sprung von v2.43.0 auf v2.45.0 haben wir mit dem Stack dieser Anleitung getestet: Der Server registrierte die Upgrade-Befehle für 2.44 und 2.45 (cross-upgrade), meldete Nest application successfully started und war nach gut einer Minute wieder healthy. Ein zusätzlicher Aufruf von upgrade:status lief auf dem Testhost mit 3 GB RAM neben Server und Worker nicht durch; planen Sie dafür Speicherreserve ein. Die als Breaking Change markierte Änderung in v2.44 betrifft nur selbst entwickelte Twenty-Apps (Registrierungsvariablen nur noch über das Manifest), nicht den Betrieb per Compose.
Verifizieren: Der neue Tag ist aktiv, alle Services laufen wieder.
docker compose ps
docker inspect $(docker compose ps -q server) | grep -i "image"
# Zeigt den neuen Image-TagEckdaten auf einen Blick
| Eigenschaft | Wert |
|---|---|
| Aktuelles Image | twentycrm/twenty:v2.45.0 (Docker Hub, ca. 260 MB komprimiert, Stand Oktober 2026) |
| Veraltetes Image | twentycrm/twenty-server, archiviert, nicht verwenden |
| Externer Port | 3000 (Web-UI + API) |
| Interne Ports | 5432 (PostgreSQL), 6379 (Redis), kein Mapping nach außen |
| Pflicht-Volume 1 | db-data → PostgreSQL-Datenbankdateien |
| Pflicht-Volume 2 | server-local-data → Uploads/Attachments |
| Pflicht-Env 1 | ENCRYPTION_KEY, openssl rand -base64 32, nur per Rotation wechseln |
| Pflicht-Env 2 | PG_DATABASE_PASSWORD, ohne Sonderzeichen |
| Pflicht-Env 3 | SERVER_URL, vollständige URL mit Schema und ggf. Port |
| Mindest-RAM | 2 GB (empfohlen: 4 GB) |
| GitHub-Stars | rund 57.000 (Stand Oktober 2026) |
Troubleshooting / Typische Fehler
- Container stürzt beim Start ab (OOMKilled): Zu wenig RAM. Prüfen Sie mit
docker statsoderdocker compose logs server. Lösung: mindestens 2 GB, besser 4 GB RAM, notfalls eine Swap-Datei. - Datenbankverbindung schlägt fehl: Meist enthalten
PG_DATABASE_PASSWORDSonderzeichen wie@,#oder/, die in der Verbindungs-URL falsch ankommen. Verwenden Sie nur Buchstaben und Ziffern, etwa peropenssl rand -hex 16. - Worker startet nicht oder hängt in depends_on: Er wartet auf den Server-Healthcheck, beim ersten Start laufen noch Migrationen. Warten Sie 2 bis 3 Minuten oder erhöhen Sie
start_periodim Server-Healthcheck auf 120s. - OAuth-Callbacks schlagen fehl oder Einladungslinks sind falsch:
SERVER_URLpasst nicht zur erreichbaren Adresse. Korrigieren (inklusivehttps://) und Stack neu starten. - Browserfunktionen wie Zwischenablage fehlen: Die Seite läuft ohne HTTPS. Reverse Proxy mit TLS vorschalten (Schritt 6).
- Altes Image
twentycrm/twenty-serververwendet: Frontend fehlt oder ist veraltet. Wechseln Sie auftwentycrm/twenty. - Daten nach
docker compose downverloren: Vermutlich liefdocker compose down -v.-vlöscht die benannten Volumes, ohne-vbleibendb-dataundserver-local-dataerhalten. - ENCRYPTION_KEY verloren: OAuth-Tokens, TOTP-Secrets und App-Variablen sind nicht mehr lesbar, eine Wiederherstellung gibt es nicht. Einen Schlüsselwechsel führen Sie nur über die Schlüsselrotation laut Herstellerdoku durch (alter Schlüssel in
FALLBACK_ENCRYPTION_KEY).
Häufige Fragen
Läuft Twenty CRM auf ARM64 (Apple Silicon, Raspberry Pi)?
Ja. Laut Docker Hub gibt es twentycrm/twenty:v2.45.0 für amd64 und arm64. Getestet wurde nur amd64.
Wie update ich Twenty auf eine neue Version?
Siehe Schritt 7: Backup, neuen Tag in der .env, dann docker compose pull && docker compose up -d.
Was passiert, wenn ich docker compose down ausführe?
docker compose down ohne Optionen entfernt nur die Container, die Volumes db-data und server-local-data bleiben. Erst docker compose down -v löscht sie mit allen Daten.
Wie richte ich S3-Speicher statt lokalem Storage ein?
Setzen Sie in der .env STORAGE_TYPE=s3 sowie STORAGE_S3_REGION, STORAGE_S3_NAME, STORAGE_S3_ENDPOINT und die Zugangsdaten. S3-kompatible Speicher wie Hetzner Object Storage oder Garage funktionieren ebenfalls, siehe S3-Speicher selbst hosten nach dem MinIO-Aus: Garage und RustFS im Vergleich.
Wie setze ich das Admin-Passwort zurück?
Die Login-Seite bietet „Passwort vergessen“ per E-Mail, das setzt einen konfigurierten Mailversand voraus. Ohne Mailserver bleibt der Weg über die Datenbank: docker compose exec db psql -U twenty -d default, die Konten liegen in der Tabelle core."user" (Spalte "passwordHash", bcrypt). Legen Sie vor einem Eingriff ein Backup an.
Wie integriere ich Google-Kalender und Gmail?
Setzen Sie MESSAGING_PROVIDER_GMAIL_ENABLED=true und CALENDAR_PROVIDER_GOOGLE_ENABLED=true und hinterlegen Sie Client-ID und Client-Secret einer Google-OAuth-App aus der Google Cloud Console. Im offiziellen Compose-Beispiel sind diese Variablen auskommentiert, Sie müssen sie in der compose.yaml bei Server und Worker aktivieren. SERVER_URL muss auf eine öffentlich erreichbare HTTPS-Adresse zeigen.
Fazit
Twenty CRM ist eine Alternative zu Salesforce und HubSpot, bei der die Daten auf dem eigenen Server bleiben. Die Installation per Compose ist schnell erledigt, wenn ENCRYPTION_KEY, PG_DATABASE_PASSWORD und SERVER_URL stimmen. Sichern Sie den Encryption Key extern, schalten Sie für den Produktivbetrieb einen Reverse Proxy mit HTTPS davor und automatisieren Sie das Datenbank-Backup.
Weiterführende Anleitungen und Quellen
- Docker und Docker Compose auf Linux installieren (Ubuntu/Debian): die Self-Hosting-Grundlage
- Caddy als Reverse Proxy einrichten: Anfänger-Anleitung mit automatischem HTTPS
- MySQL & PostgreSQL Backup automatisieren mit cron: pg_dump, Rotation und rclone-Cloud-Sync
- Plane mit Docker installieren: Self-Hosted-Projektmanagement (Batch-Geschwister)
- S3-Speicher selbst hosten nach dem MinIO-Aus: Garage und RustFS im Vergleich
Offizielle Quellen: Twenty CRM: Docker-Compose-Dokumentation · Upgrade-Guide · GitHub-Repository twentyhq/twenty · Docker Hub: twentycrm/twenty


