4.1 KiB
TwentyCRM Docker Compose Deployment
Channel: Core VPS, served at https://crm.debtrecoveryexperts.com
Port: 3001 (DocuSeal occupies 3000)
Image: twentycrm/twenty:latest (v2.20.0 pre-release as of 2026-07-07)
Stack dir: ~/docker/twenty/
Services (4 containers)
| Container | Image | Purpose | Depends on |
|---|---|---|---|
twenty-server-1 |
twentycrm/twenty:latest |
NestJS API + frontend SPA on port 3000 | db (healthy), redis (healthy) |
twenty-worker-1 |
twentycrm/twenty:latest |
Background job processor (BullMQ) | db, server (healthy) |
twenty-db-1 |
postgres:16 |
PostgreSQL database | — |
twenty-redis-1 |
redis (noeviction policy) |
Queue + cache | — |
docker-compose.yml
Located at ~/docker/twenty/docker-compose.yml. Based on upstream's packages/twenty-docker/docker-compose.yml with changes:
- Host port 3000 → 3001 to avoid conflict with DocuSeal.
- Added
REACT_APP_SERVER_BASE_URL: ${SERVER_URL}to server container environment.
.env
Based on packages/twenty-docker/.env.example. Key values:
TAG=latest
PG_DATABASE_USER=postgres
PG_DATABASE_PASSWORD=<generated>
PG_DATABASE_HOST=db
PG_DATABASE_PORT=5432
PG_DATABASE_NAME=default
REDIS_URL=redis://redis:6379
SERVER_URL=https://crm.debtrecoveryexperts.com
ENCRYPTION_KEY=<openssl rand -base64 32>
STORAGE_TYPE=local
CRITICAL: SERVER_URL must match the public domain exactly. Setting it to http://localhost:3000 or http://localhost:3001 will cause API calls to fail from the browser because the frontend uses SERVER_URL (via REACT_APP_SERVER_BASE_URL) for GraphQL requests.
Generate the encryption key
openssl rand -base64 32
Warning: Losing ENCRYPTION_KEY means losing access to every secret stored in the database (OAuth tokens, TOTP secrets, etc.). Back it up.
Initial deployment
git clone https://github.com/twentyhq/twenty.git ~/docker/twenty
cp ~/docker/twenty/packages/twenty-docker/docker-compose.yml ~/docker/twenty/
cp ~/docker/twenty/packages/twenty-docker/.env.example ~/docker/twenty/.env
# Edit .env with encryption key + password + SERVER_URL
# Adjust port in docker-compose.yml if needed
# ADD REACT_APP_SERVER_BASE_URL: ${SERVER_URL} to server environment
cd ~/docker/twenty && docker compose up -d
PITFALL: REACT_APP_SERVER_BASE_URL required for reverse proxy
TwentyCRM's Docker image serves both the React frontend and NestJS backend
from the same container. The React build includes a runtime env injection step
(inject-runtime-env.sh) that reads REACT_APP_SERVER_BASE_URL from the
container environment.
Without this env var, the frontend defaults API requests to http://localhost:3000,
which breaks when accessed through Caddy/nginx because the browser sends API
requests to localhost instead of the public domain.
Fix: Add to docker-compose.yml server environment:
environment:
NODE_PORT: 3000
PG_DATABASE_URL: ...
SERVER_URL: ${SERVER_URL}
REACT_APP_SERVER_BASE_URL: ${SERVER_URL} # MUST ADD
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
After adding, restart: docker compose up -d server
Caddy Reverse Proxy
crm.debtrecoveryexperts.com {
reverse_proxy 127.0.0.1:3001
}
First-time setup (browser)
- Navigate to https://crm.debtrecoveryexperts.com/
- Click "Continue with Email"
- Create workspace admin account (sign-up form)
- Configure workspace name, timezone, currency
- Optionally set up Google/Microsoft calendar + email integrations
Verification
# All 4 containers healthy
docker ps --filter name=twenty --format '{{.Names}} {{.Status}}'
# Frontend serving
curl -sI https://crm.debtrecoveryexperts.com/ | head -3
# Backend health
curl -s https://crm.debtrecoveryexperts.com/healthz
# GraphQL API responding
curl -s https://crm.debtrecoveryexperts.com/graphql \
-H 'Content-Type: application/json' \
-d '{"query":"{__typename}"}'
# Console: check env config injected in HTML
curl -sL https://crm.debtrecoveryexperts.com/ | grep -A3 'twenty-env-config'
Backup
docker exec twenty-db-1 pg_dump -U postgres default > backup_$(date +%Y%m%d).sql