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, Garage 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 Garage 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
S3_ENDPOINThttps://s3.crm.example.com
GARAGE_RPC_SECRET32 bytes of hex, required by the Compose file (see below)
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:

  • S3_ENDPOINT must be the public https://s3.<domain> URL, not the internal garage 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.
MCP_ENABLEDDefaults to true. Turns on the MCP server and its OAuth endpoints, and requires OAUTH_SIGNING_KEY. Set false to opt out.
OAUTH_SIGNING_KEYBase64 of exactly 32 bytes. Signs MCP access tokens. Required while MCP_ENABLED is true.
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 # S3_ACCESS_KEY / S3_SECRET_KEY
openssl rand -hex 32 # GARAGE_RPC_SECRET (exactly 32 bytes, hex)
openssl rand -base64 32 # OAUTH_SIGNING_KEY (32-byte base64 key)

The app refuses to start with MCP_ENABLED on (the default) and no valid OAUTH_SIGNING_KEY, so a fresh install needs this key. If you do not want the MCP server, set MCP_ENABLED=false instead and skip it. See MCP server.

DATABASE_URL, TOKEN_ENCRYPTION_KEY, WS_TICKET_SECRET, GOOGLE_OAUTH_CLIENT_SECRET, S3_SECRET_KEY, OAUTH_SIGNING_KEY, and INTERCOM_ACCESS_TOKEN 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.

Garage creates the S3_BUCKET bucket and an access key from S3_ACCESS_KEY and S3_SECRET_KEY on first start. Garage requires an access key of at least 8 characters and a secret of at least 16. The Compose file signs storage requests for region garage, which is what the bundled Garage serves. When S3_ENDPOINT points at a different S3-compatible store, set S3_REGION to that store's region, or leave it empty outside Compose and the client discovers the bucket's region. Installs that still set the older MINIO_* names keep working: each S3_* variable falls back to its MINIO_* counterpart when unset.

Optional integrations​

VariableMeaning
OAUTH_REGISTRATIONopen (default) or disabled. Open lets an MCP client register itself. Set disabled once your clients are connected: existing grants keep working. See MCP server.
INTERCOM_ACCESS_TOKENIntercom access token. Empty by default, which leaves Intercom off. See Intercom.
INTERCOM_REGIONIntercom data region: us, eu, or au. Defaults to us.
X_CLIENT_IDOAuth 2.0 client ID of your X developer app. Empty by default. X direct messages turn on only when both this and X_CLIENT_SECRET are set. See X direct messages.
X_CLIENT_SECRETOAuth 2.0 client secret of the same X app.
POSTHOG_KEYPostHog project ingestion token. Leaving it or POSTHOG_HOST empty disables all telemetry.
POSTHOG_HOSTPostHog host URL.
DISABLE_TELEMETRYSet true to turn telemetry off even when the PostHog values are set. Defaults to false.
TELEMETRY_CONSOLE_FORWARDINGSet true to also forward browser console.warn and console.error messages. Off by default.
APP_COMMITGit SHA of the running build, attached to telemetry events. Empty by default.

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 storagecors sets the bucket's CORS rule for BASE_URL, 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 Garage S3 API at http://localhost:9000 (use it as S3_ENDPOINT), 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.