Skip to main content

Installation

Warpdrive ships as a self-contained Docker Compose stack: the Next.js app, a WebSocket server, a background worker, Postgres, MinIO object storage, and a Caddy reverse proxy that terminates TLS with automatic Let's Encrypt certificates. One docker compose up -d --build brings up the whole thing.

Prerequisites

  • A Linux box with Docker and the Compose plugin.
  • A domain name with an A/AAAA record pointing at the box's public IP. This is required, not cosmetic: Google OAuth will not redirect to a bare IP address.
  • A second A/AAAA record for an s3. subdomain (for example s3.crm.example.com), also pointing at the box. The browser uploads files (CSV imports, avatars, attachments) directly to MinIO through this hostname, so it needs its own DNS record and certificate. Skipping this breaks every upload.
  • Ports 80 and 443 open. Caddy needs port 80 for the ACME challenge.
  • A Google OAuth client (Workspace), covered below.

1. Configure the environment

cp .env.example .env

Warpdrive reads process.env in exactly one module, which validates every variable with a schema and throws at import time. A misconfigured deployment fails loudly at boot rather than misbehaving hours later.

Deployment values

VariableValue on the box
APP_DOMAINcrm.example.com, the public hostname
ACME_EMAILan address for Let's Encrypt expiry notices
BASE_URLhttps://crm.example.com
NEXT_PUBLIC_WS_URLwss://crm.example.com/_ws
WS_PUBLIC_URLws://ws:8080, the internal Compose address
MINIO_ENDPOINThttps://s3.crm.example.com
DATABASE_URLpostgres://warpdrive:warpdrive@postgres:5432/warpdrive
SEED_ADMIN_EMAILthe first admin's email address, required in production
ALLOW_FIRST_LOGIN_ADMINfalse

Three of these cause silent, confusing failures when set wrongly, so they are worth stating plainly:

  • MINIO_ENDPOINT must be the public https://s3.<domain> URL, not the internal minio Compose alias. The browser POSTs presigned uploads directly to this address, so an internal hostname is unreachable from where it actually gets used. Every upload fails if this is wrong.
  • NEXT_PUBLIC_WS_URL must be set at build time. NEXT_PUBLIC_* variables are inlined into the client bundle when the image is built, not read at runtime. If it is missing, the app still starts and looks healthy, but the board, inbox, notifications, and presence indicators silently never update.
  • ALLOW_FIRST_LOGIN_ADMIN stays false in production. The environment boundary rejects true when running in production, because it would promote whoever signs in first to administrator.

Business values

VariableMeaning
BASE_CURRENCYCurrency deal values are recorded in. Defaults to USD.
MAX_FILE_BYTESUpload size ceiling. Defaults to 26214400 (25 MiB).
GOOGLE_WORKSPACE_DOMAINRestricts sign-in to a single Workspace domain.
APP_VERSIONVersion shown as "current" by the update banner.
DISABLE_UPDATE_CHECKSet true to disable the update banner and its GitHub poll.

Generate the secrets

openssl rand -base64 32 # TOKEN_ENCRYPTION_KEY (32-byte base64 key)
openssl rand -hex 32 # WS_TICKET_SECRET (>= 32 chars)
openssl rand -hex 16 # MINIO_ACCESS_KEY / MINIO_SECRET_KEY

DATABASE_URL, TOKEN_ENCRYPTION_KEY, WS_TICKET_SECRET, GOOGLE_OAUTH_CLIENT_SECRET, and MINIO_SECRET_KEY may each instead be supplied as <VAR>_FILE pointing at a file path, for use with Docker secrets. The app reads the file contents at boot.

2. Set up Google OAuth

Warpdrive uses Google for sign-in and for the Gmail integration. Create an OAuth client in the Google Cloud console for the project that owns your Workspace, then set GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET.

Register both redirect URIs:

  • the application sign-in callback, and
  • https://<your-domain>/api/gmail/oauth/callback for mailbox connection.

Connecting a mailbox needs the openid and email scopes in addition to the Gmail scopes, because the callback binds the returned mailbox to a warpdrive user through the userinfo endpoint. Omitting them makes mailbox connection fail after the consent screen, which reads like a Gmail problem but is not.

See Email sync for connecting mailboxes once the app is running.

3. Bring it up

docker compose up -d --build

Startup order is handled for you. Postgres becomes healthy, a one-shot migrate service applies the schema and createbuckets provisions the MinIO bucket, then app, ws, and worker start. Caddy waits for the app to report healthy, then fetches a TLS certificate on the first request to https://<APP_DOMAIN>.

Check it:

docker compose ps
curl -fsS https://<APP_DOMAIN>/api/health # -> {"ok":true}

4. First admin login

SEED_ADMIN_EMAIL is bootstrapped as the initial administrator. Sign in with that Google account through the normal flow and it is promoted on first login.

From there, invite the rest of your team from Users and teams.

Local development

The base Compose file is production-only: no host ports, TLS through Caddy. For a local run, merge the development overrides, which expose host ports and drop Caddy:

docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build

The app is then at http://localhost:3000, the MinIO console at http://localhost:9001, and Postgres at localhost:5433. Set BASE_URL=http://localhost:3000 and NEXT_PUBLIC_WS_URL=ws://localhost:8080 for this mode.

Operating the deployment

See Updating for upgrades, backups, and day-to-day operation.