Docs

Self-hosting Convex

Run Convoy fully on your own infrastructure with the bundled Docker Compose setup — walkthrough, environment matrix, and what you give up versus managed Convex.

Convoy's backend is Convex, and Convex ships an official self-hosted build. The repo bundles a Docker Compose setup under self-hosted/ that runs the whole stack on one machine:

  • backend — self-hosted Convex (ghcr.io/get-convex/convex-backend); database, functions, scheduler, file storage. Client API on port 3210, HTTP actions (/api/mcp, /api/agent/*) on port 3211.
  • dashboard — the Convex dashboard (ghcr.io/get-convex/convex-dashboard) on port 6791; data browser, logs, function runner.
  • web — the Convoy Next.js app, built locally from your checkout, on port 3000.

What still lives in the cloud

There is no fully offline, single-image Convoy. Two things cannot be bundled:

  • Clerk (required). All authentication goes through Clerk, which has no self-hosted offering. A free-tier Clerk app works fine for a local deployment; the login flow needs internet access to *.clerk.accounts.dev.
  • Resend (optional). Invitation and notification emails go through Resend. Skip it and everything else works — invitations can be accepted via link instead of email.

A single pull-and-run image is also ruled out by Next.js itself: the NEXT_PUBLIC_* values (Convex URL, Clerk publishable key) are baked into the JavaScript bundle at build time, so the web image must be built per installation. docker compose build handles that.

Quick start

Prerequisites: Docker with Compose, and a Clerk application set up per Auth and Clerk (create the convex JWT template).

./self-hosted/start.sh

The script asks for your two Clerk API keys on the first run and does the rest: writes .env, generates the instance secret and admin key, derives the Clerk JWT issuer domain from the publishable key, starts the backend and dashboard, builds the web image, deploys Convoy's functions from inside Docker (no Node.js needed on the host), and starts the web app. Re-running it is always safe.

Manual walkthrough

The same steps by hand — useful for understanding the moving pieces or for customized setups. Requires Node 20+ on the host for the npx convex deploy step.

cd self-hosted
cp .env.example .env

Fill in .env: INSTANCE_SECRET (openssl rand -hex 32), NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, and CLERK_SECRET_KEY.

1. Start the Convex backend and dashboard:

docker compose up -d backend dashboard

2. Generate an admin key (the self-hosted equivalent of a deploy key) and paste it into .env as CONVEX_SELF_HOSTED_ADMIN_KEY:

docker compose exec backend ./generate_admin_key.sh

3. Deploy Convoy's functions and schema. The standard Convex CLI targets a self-hosted backend when CONVEX_SELF_HOSTED_URL and CONVEX_SELF_HOSTED_ADMIN_KEY are set:

cd ../apps/web
set -a; source ../../self-hosted/.env; set +a
# If apps/web/.env.local exists (dev checkout), its CONVEX_DEPLOYMENT
# conflicts with self-hosted deploys — clear it for these commands:
export CONVEX_DEPLOYMENT=
npx convex env set CLERK_JWT_ISSUER_DOMAIN "https://<your-clerk-issuer-domain>"
# Optional, for invitation emails:
# npx convex env set RESEND_API_KEY "re_..."
# npx convex env set EMAIL_FROM "Convoy <noreply@yourdomain>"
npx convex deploy

4. Build and start the web app:

cd ../../self-hosted
docker compose up -d --build web

Open http://127.0.0.1:3000, sign in through Clerk, and create your first organization. The Convex dashboard is at http://127.0.0.1:6791 (log in with the admin key).

5. Connect the CLI. Nothing changes for CLI users — convoy setup against http://127.0.0.1:3000 works as usual. The CLI derives the HTTP actions endpoint from the Convex URL: *.convex.cloud*.convex.site on managed Convex, port :3210:3211 on self-hosted. Keep that port convention if you put a reverse proxy in front.

Serving beyond localhost

The defaults assume one machine. To serve a team:

  • Set CONVEX_CLOUD_ORIGIN, CONVEX_SITE_ORIGIN, and APP_URL in .env to URLs reachable from users' browsers and CLI machines (these origins are embedded in tokens and the web bundle — they must match exactly).
  • Put TLS in front (Caddy/nginx/Traefik). Preserve the :3210/:3211 pairing, e.g. convex.example.com:3210 and convex.example.com:3211.
  • Rebuild the web image after changing .env (docker compose up -d --build web) — the values are baked in at build.

Operations

  • Upgrades: docker compose pull && docker compose up -d, then re-run npx convex deploy after pulling a new Convoy version. Pin image tags instead of latest for reproducibility, and follow convex-backend releases.
  • Backups: data lives in the convex_data Docker volume (embedded SQLite + file storage). Snapshot the volume, or use npx convex export for a logical export. You own backups now — there is no managed restore.
  • Scaling: the self-hosted image is a single-node build. Vertical scaling only; for real load you can move the database to Postgres/MySQL (DATABASE_URL) and file storage to S3, but the backend itself stays one process.
  • Optional backend flags: docker-compose.yml ships with a few commented-out variables. The backend rejects empty values at boot, so each one must be uncommented in the compose file and given a value in .env: DATABASE_URL (external Postgres/MySQL instead of the embedded SQLite volume), DO_NOT_REQUIRE_SSL=true (when that database connection has no TLS — typical for a same-host Postgres), and REDACT_LOGS_TO_CLIENT=true (hide server error details from connected clients — recommended once real users are on the instance).

What you lose versus managed Convex

Self-hosted Convex has feature parity with the cloud free tier for the things Convoy uses — realtime queries, mutations, actions, HTTP actions, scheduler/crons, file storage, full-text search, and components (Convoy uses migrations and resend) all work. What you give up is the managed layer around it:

CapabilityManaged ConvexSelf-hosted
Realtime, scheduler, file storage, components
ScalingAutomatic, multi-serviceSingle node, vertical only
Backups / restoreManaged snapshots, point-in-timeYour volume snapshots + npx convex export
Log streaming (Axiom, Datadog, …)Not sent; use docker logs / dashboard
Preview & per-developer cloud deploymentsOne deployment; run more compose stacks yourself
Dashboard access controlTeam accountsSingle shared admin key
Availability, upgrades, on-callConvex's problemYour problem

None of these remove Convoy features. Everything in the product — tasks, realtime updates, attachments, invitations (with Resend), MCP server, CLI agent threads — runs on self-hosted Convex. The differences are operational.

Validation status

This recipe is new. The self-hosted/ compose file follows the official Convex self-hosting guide and Convoy's URL handling supports the self-hosted port convention, but if you hit issues the most likely suspects are the ones any self-host deployer should verify first: Clerk JWT validation from inside your network, file uploads, scheduled jobs (task hard-delete, stream watchdogs), and CLI agent registration over :3211.

On this page