Mastering Self-Hosted Convex: A Complete Deployment Guide
So, you've decided to take control of your data and self-host Convex. Great choice! But if you've tried pushing your functions to a local or private server, you might have run into cryptic errors about "Relative import paths" or "Missing modules."
The secret? You don't need custom deployment scripts. In fact, you shouldn't use them. The official Convex CLI is actually powerful enough to handle self-hosted deployments perfectly—if you know which buttons to press.
In this guide, I’ll show you exactly how to configure your project and deploy your functions like a pro.
1. Before You Start
Before we dive into the code, make sure you have the following ready:
- Your Convex Instance: Whether you're using Dokploy, Docker, or a raw VPS, your backend should be up and running.
- The Admin Key: You'll need the master key for your server.
- Tip: If you're using Dokploy, you can usually generate this by running
./generate_admin_key.shinside your backend container's terminal.
- Tip: If you're using Dokploy, you can usually generate this by running
-
The CLI: Make sure you have the latest version of Convex installed locally:
npm install convex
2. Preparing Your Project
For a smooth deployment, your convex/ folder needs to be a bit more "independent."
Step 1: Give Convex its own Identity
Create a package.json inside your convex/ directory. This ensures the CLI knows exactly which dependencies to bundle when it prepares your code for the server.
{
"name": "my-convex-backend",
"private": true,
"type": "module",
"dependencies": {
"convex": "^1.16.0",
"typescript": "^5.0.0"
}
}
Step 2: Fix your Config
Ensure your convex/convex.config.ts is using the modern defineApp export. The server expects this structure to understand your app's layout.
import { defineApp } from "convex/server";
export default defineApp();
3. The Magic Environment Variables
The Convex CLI is "Cloud-first" by default, but we can tell it to target our own server by setting two specific environment variables.
| Variable | What it does |
|---|---|
CONVEX_SELF_HOSTED_URL |
Points the CLI to your backend URL (e.g., https://api.myapp.com). |
CONVEX_SELF_HOSTED_ADMIN_KEY |
Your secret key. Crucial: It must include the `convex-self-hosted |
4. The Big Moment: Deploying
Now, let's push your code. We're going to use the official {% raw %}deploy command.
Run this from your root or convex directory:
CONVEX_SELF_HOSTED_URL="https://your-api-url.com" \
CONVEX_SELF_HOSTED_ADMIN_KEY="convex-self-hosted|your-key-here" \
npx convex deploy --typecheck disable
Why this works where other scripts fail:
When you use a custom fetch script to send files, you're just sending raw text. The server has no idea where convex/server is.
The CLI is smarter. It uses esbuild to "bundle" your code into a single, self-contained JavaScript package. It finds all your imports and bakes them right into the file so the server can run them instantly.
5. Pro Tip: Automate Everything
Don't type those long variables every time. Add a script to your convex/package.json:
"scripts": {
"deploy": "CONVEX_SELF_HOSTED_URL=... CONVEX_SELF_HOSTED_ADMIN_KEY=... npx convex deploy --typecheck disable"
}
Now, all it takes is a simple:
npm run deploy
Troubleshooting common "Gotchas"
"Relative import path 'convex/server' not found"
If you see this, you likely bypassed the CLI. Always use npx convex deploy so the bundler can do its job!
"BadAdminKey"
Double-check your key. Most people forget the convex-self-hosted| prefix. Without it, the server will reject your request every time.
Missing _generated files in your frontend
Run npx convex codegen while your CONVEX_SELF_HOSTED_URL is set to update your local TypeScript types to match what you just deployed.
Happy hosting! Your Convex backend is now truly yours.
Top comments (0)