# 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= 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= 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 ```bash 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 ```bash 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: ```yaml 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 ```caddy crm.debtrecoveryexperts.com { reverse_proxy 127.0.0.1:3001 } ``` ## First-time setup (browser) 1. Navigate to https://crm.debtrecoveryexperts.com/ 2. Click "Continue with Email" 3. Create workspace admin account (sign-up form) 4. Configure workspace name, timezone, currency 5. Optionally set up Google/Microsoft calendar + email integrations ## Verification ```bash # 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 ```bash docker exec twenty-db-1 pg_dump -U postgres default > backup_$(date +%Y%m%d).sql ```