Skip to documentation

Self-hosting

Self-hosted Convex

Use the optional dashboard and migrate CLI to operate your backend.

Open the optional dashboard

Run commands from the installation directory, where Compose reads .env. For source installs, add --env-file .env.docker after docker compose.

bash
docker compose --profile debug up -d dashboard

The dashboard binds to 127.0.0.1:6791; DASHBOARD_PORT changes the host port. On a remote server, use an SSH tunnel to reach it. Its NEXT_PUBLIC_DEPLOYMENT_URL comes from CONVEX_PUBLIC_URL. Log in with CONVEX_SELF_HOSTED_ADMIN_KEY from .env. Dashboard access grants administration of all stored data, so keep it private.

Use the migrate CLI

With no arguments, migrate pushes non-empty environment settings and deploys functions, then exits. With arguments, it passes them directly to the bundled Convex CLI. It uses http://convex:3210 and the admin key from Compose; it does not select a cloud deployment.

bash
docker compose run --rm migrate logs --history 50
docker compose run --rm migrate env list

To set a backend value directly, use docker compose run --rm migrate env set NAME VALUE. Keep values also supplied in .env in sync: the next deployment reapplies those non-empty values. An empty .env value does not erase an existing backend setting. See environment variables.

Export a snapshot

The CLI writes paths inside the migrate container. Compose does not mount a host backup directory by default. Keep a named container until you copy the export out:

bash
docker compose run --name opensend-backup migrate export --include-file-storage --path /tmp/backup.zip
docker cp opensend-backup:/tmp/backup.zip ./backup.zip
docker rm opensend-backup

Include file storage to recover attachments and received mail. A snapshot includes component data. Save .env securely alongside it. Pause writes for the backup; a consistent whole-volume copy also requires stopping Convex.

Restore a snapshot

Restore into a separate installation and volume using the backup's release files, images, and secrets. Choose a different COMPOSE_PROJECT_NAME, ports, and public URLs to isolate it from production. Start its Convex service and deploy the matching functions to create the component tree. Then mount the host directory containing backup.zip read-only for import:

bash
docker compose up -d convex
docker compose run --rm migrate
docker compose run --rm -v "$PWD:/backup:ro" migrate import --replace-all /backup/backup.zip
docker compose up -d --wait

The default import requires empty tables. Here, --replace-all restores the snapshot across the target deployment and clears data absent from the snapshot. The CLI asks for confirmation when deleting existing documents. Use a separate target; do not test a restore against production. Verify users, teams, encrypted settings, and file downloads before switching traffic. See upgrading.

Re-mint the admin key

The admin key is derived from INSTANCE_NAME and INSTANCE_SECRET by the pinned backend image. If the key is lost but those values remain, generate it again without changing them:

bash
CONVEX_IMAGE=$(docker compose config --images convex)
INSTANCE_NAME=$(awk -F= '$1 == "INSTANCE_NAME" { print $2; exit }' .env)
INSTANCE_SECRET=$(awk -F= '$1 == "INSTANCE_SECRET" { print $2; exit }' .env)
docker run --rm --entrypoint ./generate_key "$CONVEX_IMAGE" "$INSTANCE_NAME" "$INSTANCE_SECRET"
unset INSTANCE_SECRET

Replace CONVEX_SELF_HOSTED_ADMIN_KEY in .env with the result and keep it secret. These reads match the installer's unquoted generated values. Do not rotate INSTANCE_SECRET to recover a lost key. The installer never rotates existing secrets on rerun.

Develop from source

The product repository also provides pnpm backend, which reads .env.docker. For direct CLI/editor use, set CONVEX_SELF_HOSTED_URL and CONVEX_SELF_HOSTED_ADMIN_KEY in .env.local as .env.example shows. Do not also set CONVEX_DEPLOYMENT. OPENSEND_ENV_FILE overrides the source helper's environment file.