Open Source CRM
    Postgres + Redis

    Deploy Twenty CRM on a VPS

    Self-host Twenty, an open-source CRM alternative to Salesforce and HubSpot, on a RamNode VPS with Docker Compose, Postgres, Redis, and nginx TLS.

    Twenty is an open-source CRM (a modern Salesforce/HubSpot alternative). The self-hosted stack is a Postgres database, Redis, and the Twenty server + worker containers, served through a single reverse-proxied entrypoint. It's a comparatively light stack and runs comfortably on a small VPS.

    1. Prerequisites

    • RamNode VPS on Ubuntu 24.04 LTS
    • Minimum 4 GB RAM, 2 vCPUs, 25 GB disk for a small team; bump RAM if you'll have dozens of concurrent users
    • DNS A record for the subdomain, e.g. crm.yourdomain.com
    • Root/sudo SSH access

    2. Base server setup

    shell
    apt update && apt -y upgrade
    apt -y install curl git ufw
    
    ufw allow OpenSSH
    ufw allow 80/tcp
    ufw allow 443/tcp
    ufw enable

    3. Install Docker

    shell
    curl -fsSL https://get.docker.com | sh
    apt -y install docker-compose-plugin
    systemctl enable --now docker

    4. Set up Twenty

    shell
    mkdir -p /opt/twenty && cd /opt/twenty
    curl -fsSL https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml -o docker-compose.yml
    curl -fsSL https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/.env.example -o .env

    Edit .env:

    • PG_DATABASE_PASSWORD — strong unique password
    • APP_SECRET — generate with openssl rand -base64 32
    • SERVER_URL — set to https://crm.yourdomain.com (this must match the public URL exactly — Twenty uses it for auth callbacks, invite links, and file URLs)
    • STORAGE_TYPElocal is fine for a single VPS; switch to s3 and fill in the corresponding vars if you want uploads on external object storage
    • Mail settings (EMAIL_FROM_ADDRESS, EMAIL_DRIVER, SMTP credentials) — required for invite emails and password resets to actually send

    5. Start the stack

    shell
    docker compose up -d

    This starts Postgres, Redis, the server container (API + frontend), and a worker container (background jobs — emails, imports, workflow automation). Watch logs:

    shell
    docker compose logs -f server

    The server listens on port 3000 by default.

    6. nginx reverse proxy + TLS

    shell
    apt -y install nginx certbot python3-certbot-nginx

    /etc/nginx/sites-available/twenty:

    shell
    server {
        listen 80;
        server_name crm.yourdomain.com;
    
        client_max_body_size 50M;   # file/attachment uploads
    
        location / {
            proxy_pass http://127.0.0.1:3000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";   # GraphQL subscriptions / real-time updates
        }
    }
    shell
    ln -s /etc/nginx/sites-available/twenty /etc/nginx/sites-enabled/
    nginx -t && systemctl reload nginx
    certbot --nginx -d crm.yourdomain.com

    7. First login

    Visit https://crm.yourdomain.com and complete the workspace creation wizard — this creates the first admin user. From there, invite teammates via Settings → Members (requires working SMTP from step 4).

    8. Backups

    Only Postgres and, if using local storage, the uploads volume need regular backup:

    shell
    docker exec twenty-db-1 pg_dump -U postgres default > /root/backups/twenty-pg-$(date +%F).sql
    
    docker run --rm -v twenty_server-local-data:/data -v /root/backups:/backup alpine \
      tar czf /backup/twenty-uploads-$(date +%F).tar.gz -C /data .

    Cron this nightly and copy backups off the VPS.

    9. Updating

    shell
    cd /opt/twenty
    docker compose down
    docker compose pull
    docker compose up -d

    Twenty runs its own DB migrations automatically on server container startup — check the changelog before major version jumps, since CRM schema migrations can take a few minutes on larger datasets.

    Troubleshooting notes

    • Invite/reset emails never arrive → almost always an SMTP config issue in .env; test with a known-good SMTP relay before assuming the app is broken.
    • Login redirects loop or fail after TLS setup → check SERVER_URL matches the actual public HTTPS URL exactly (scheme + host, no trailing slash mismatch).
    • Background jobs (imports, automations) not running → confirm the worker container is up alongside server; they're separate containers and only server being healthy doesn't mean jobs are processing.