coder-convex-setup
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCoder-Convex-Setup: Initial Convex Workspace Setup in Coder
Coder-Convex-Setup:在Coder中完成Convex初始工作区设置
You are an expert at initial setup and configuration of self-hosted Convex in Coder workspaces. This skill is ONLY for the one-time setup of a new Convex workspace. For everyday Convex development, use the skill instead.
coder-convex你是在Coder工作区中进行自托管Convex初始设置与配置的专家。该技能仅适用于新Convex工作区的一次性设置。日常Convex开发请使用技能。
coder-convexWhen to Use This Skill
何时使用该技能
Use this skill when:
- Setting up Convex in a new Coder workspace for the first time
- Configuring a self-hosted Convex deployment
- Setting up Docker-based Convex backend
- Configuring environment variables for Convex
- Generating admin keys and deployment URLs
DO NOT use this skill for:
- Everyday Convex development (use instead)
coder-convex - Writing queries, mutations, or actions (use instead)
coder-convex - Schema modifications (use instead)
coder-convex - React integration issues (use instead)
coder-convex
在以下场景使用本技能:
- 首次在新Coder工作区中设置Convex
- 配置自托管Convex部署
- 设置基于Docker的Convex后端
- 配置Convex的环境变量
- 生成管理密钥与部署URL
请勿在以下场景使用本技能:
- 日常Convex开发(请使用)
coder-convex - 编写查询、变更或操作(请使用)
coder-convex - 修改Schema(请使用)
coder-convex - React集成问题(请使用)
coder-convex
Prerequisites
前置条件
Before setting up Convex in a Coder workspace, ensure:
-
Node.js and a package manager are installed:bash
node --version # Should be v18+ # Check for package manager: pnpm, yarn, npm, or bun pnpm --version # Or: yarn --version, npm --version, bun --version -
Docker is available:bash
docker --version docker compose version -
Project has package.json with Convex dependency:json
{ "dependencies": { "convex": "^1.31.3" } }
在Coder工作区中设置Convex前,请确保:
-
已安装Node.js与包管理器:bash
node --version # 版本应为v18+ # 检查包管理器:pnpm、yarn、npm或bun pnpm --version # 或:yarn --version, npm --version, bun --version -
Docker可用:bash
docker --version docker compose version -
项目的package.json中包含Convex依赖:json
{ "dependencies": { "convex": "^1.31.3" } }
Coder Workspace Services Overview
Coder工作区服务概述
In a Coder workspace, Convex is exposed through multiple services. Understanding these is critical:
| Slug | Display Name | Internal URL | Port | Hidden | Purpose |
|---|---|---|---|---|---|
| Convex Dashboard | | 6791 | No | Admin dashboard |
| Convex API | | 3210 | Yes | Main API endpoints |
| Convex Site | | 3211 | Yes | Site Proxy (Auth) |
在Coder工作区中,Convex通过多个服务对外暴露。理解这些服务至关重要:
| 标识 | 显示名称 | 内部URL | 端口 | 是否隐藏 | 用途 |
|---|---|---|---|---|---|
| Convex控制台 | | 6791 | 否 | 管理控制台 |
| Convex API | | 3210 | 是 | 主API端点 |
| Convex站点代理 | | 3211 | 是 | 站点代理(身份验证用) |
Step 1: Install Convex Dependencies
步骤1:安装Convex依赖
bash
undefinedbash
undefinedInstall Convex package
安装Convex包
[package-manager] add convex
[package-manager] add convex
Install auth dependencies (required for Coder workspaces)
安装身份验证依赖(Coder工作区必需)
[package-manager] add @convex-dev/auth
[package-manager] add @convex-dev/auth
Install dev dependencies if not present
若未安装则安装开发依赖
[package-manager] add -D @types/node typescript
undefined[package-manager] add -D @types/node typescript
undefinedStep 2: Create Convex Directory Structure
步骤2:创建Convex目录结构
Create the following directory structure:
bash
mkdir -p convex/libThe structure should look like:
convex/
├── lib/ # Internal utilities (optional)
├── schema.ts # Database schema (required)
├── auth.ts # Auth setup (required for Coder)
├── router.ts # HTTP routes (required for auth endpoints)
└── http.ts # HTTP exports with auth routes (required for Coder)创建如下目录结构:
bash
mkdir -p convex/lib最终结构应如下所示:
convex/
├── lib/ # 内部工具(可选)
├── schema.ts # 数据库Schema(必需)
├── auth.ts # 身份验证设置(Coder工作区必需)
├── router.ts # HTTP路由(身份验证端点必需)
└── http.ts # 包含身份验证路由的HTTP导出(Coder工作区必需)Step 3: Create Initial Schema
步骤3:创建初始Schema
Create convex/schema.ts:
typescript
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
import { authTables } from "@convex-dev/auth/server";
// Your application tables
const applicationTables = {
// Add your tables here
tasks: defineTable({
title: v.string(),
status: v.string(),
}).index("by_status", ["status"]),
};
export default defineSchema({
...authTables,
...applicationTables,
});Key Schema Rules:
- Always include from
...authTablesfor Coder workspaces@convex-dev/auth/server - Never manually add or
_id- they're automatic_creationTime - Index names should be descriptive:
by_fieldName - All indexes automatically include as the last field
_creationTime - Don't use - it's built-in
.index("by_creation_time", ["_creationTime"])
创建convex/schema.ts:
typescript
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
import { authTables } from "@convex-dev/auth/server";
// 你的应用表
const applicationTables = {
// 在此添加你的表
tasks: defineTable({
title: v.string(),
status: v.string(),
}).index("by_status", ["status"]),
};
export default defineSchema({
...authTables,
...applicationTables,
});Schema关键规则:
- 对于Coder工作区,必须包含来自的
@convex-dev/auth/server...authTables - 请勿手动添加或
_id- 它们会自动生成_creationTime - 索引名称应具有描述性:
by_fieldName - 所有索引会自动将作为最后一个字段
_creationTime - 请勿使用- 这是内置的
.index("by_creation_time", ["_creationTime"])
Step 4: Create Auth Configuration
步骤4:创建身份验证配置
Note: Modern(v0.0.90+) uses the@convex-dev/authfunction directly. A separateconvexAuth()file is no longer required.auth.config.ts
Create convex/auth.ts:
typescript
import { convexAuth, getAuthUserId } from "@convex-dev/auth/server";
import { Password } from "@convex-dev/auth/providers/Password";
import { Anonymous } from "@convex-dev/auth/providers/Anonymous";
import { query } from "./_generated/server";
// Configure auth with providers
export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
providers: [Password, Anonymous],
});
// Query to get the current user
export const currentUser = query({
args: {},
handler: async (ctx) => {
const userId = await getAuthUserId(ctx);
if (!userId) {
return null;
}
return await ctx.db.get(userId);
},
});Create convex/router.ts:
typescript
import { httpRouter } from "convex/server";
const http = httpRouter();
export default http;Create convex/http.ts:
typescript
import { auth } from "./auth";
import router from "./router";
const http = router;
// CRITICAL: Add auth routes to the HTTP router
auth.addHttpRoutes(http);
export default http;Critical: The call is required for auth endpoints () to be accessible. Without this, authentication will not work.
auth.addHttpRoutes(http)/auth/*注意:新版(v0.0.90+)直接使用@convex-dev/auth函数,不再需要单独的convexAuth()文件。auth.config.ts
创建convex/auth.ts:
typescript
import { convexAuth, getAuthUserId } from "@convex-dev/auth/server";
import { Password } from "@convex-dev/auth/providers/Password";
import { Anonymous } from "@convex-dev/auth/providers/Anonymous";
import { query } from "./_generated/server";
// 使用提供程序配置身份验证
export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
providers: [Password, Anonymous],
});
// 获取当前用户的查询
export const currentUser = query({
args: {},
handler: async (ctx) => {
const userId = await getAuthUserId(ctx);
if (!userId) {
return null;
}
return await ctx.db.get(userId);
},
});创建convex/router.ts:
typescript
import { httpRouter } from "convex/server";
const http = httpRouter();
export default http;创建convex/http.ts:
typescript
import { auth } from "./auth";
import router from "./router";
const http = router;
// 关键操作:将身份验证路由添加到HTTP路由器
auth.addHttpRoutes(http);
export default http;关键提示:调用是身份验证端点()可访问的必要条件。没有此调用,身份验证将无法正常工作。
auth.addHttpRoutes(http)/auth/*Step 5: Create Coder Setup Script
步骤5:创建Coder设置脚本
Create scripts/setup-convex.sh:
bash
#!/bin/bash创建scripts/setup-convex.sh:
bash
#!/bin/bashDetect Coder workspace environment
检测Coder工作区环境
Check for both CODER and CODER_WORKSPACE_NAME to confirm we're in a Coder workspace
同时检查CODER和CODER_WORKSPACE_NAME以确认是否在Coder工作区中
if [ -n "$CODER" ] && [ -n "$CODER_WORKSPACE_NAME" ]; then
Running in Coder workspace
Extract protocol and domain from CODER_URL (e.g., https://coder.hahomelabs.com)
CODER_PROTOCOL="${CODER_URL%%://}"
CODER_DOMAIN="${CODER_URL#//}"
WORKSPACE_NAME="${CODER_WORKSPACE_NAME}"
USERNAME="${CODER_WORKSPACE_OWNER_NAME:-$USER}"
Generate Coder-specific URLs
Format: <protocol>://<service>--<workspace>--<owner>.<coder-domain>
CONVEX_API_URL="${CODER_PROTOCOL}://convex-api--${WORKSPACE_NAME}--${USERNAME}.${CODER_DOMAIN}"
CONVEX_SITE_URL="${CODER_PROTOCOL}://convex-site--${WORKSPACE_NAME}--${USERNAME}.${CODER_DOMAIN}"
CONVEX_DASHBOARD_URL="${CODER_PROTOCOL}://convex--${WORKSPACE_NAME}--${USERNAME}.${CODER_DOMAIN}"
else
Local development
CONVEX_API_URL="http://localhost:3210"
CONVEX_SITE_URL="http://localhost:3211"
CONVEX_DASHBOARD_URL="http://localhost:6791"
fi
if [ -n "$CODER" ] && [ -n "$CODER_WORKSPACE_NAME" ]; then
在Coder工作区中运行
从CODER_URL中提取协议和域名(例如,https://coder.hahomelabs.com)
CODER_PROTOCOL="${CODER_URL%%://}"
CODER_DOMAIN="${CODER_URL#//}"
WORKSPACE_NAME="${CODER_WORKSPACE_NAME}"
USERNAME="${CODER_WORKSPACE_OWNER_NAME:-$USER}"
生成Coder专属URL
格式:<protocol>://<service>--<workspace>--<owner>.<coder-domain>
CONVEX_API_URL="${CODER_PROTOCOL}://convex-api--${WORKSPACE_NAME}--${USERNAME}.${CODER_DOMAIN}"
CONVEX_SITE_URL="${CODER_PROTOCOL}://convex-site--${WORKSPACE_NAME}--${USERNAME}.${CODER_DOMAIN}"
CONVEX_DASHBOARD_URL="${CODER_PROTOCOL}://convex--${WORKSPACE_NAME}--${USERNAME}.${CODER_DOMAIN}"
else
本地开发环境
CONVEX_API_URL="http://localhost:3210"
CONVEX_SITE_URL="http://localhost:3211"
CONVEX_DASHBOARD_URL="http://localhost:6791"
fi
Determine PostgreSQL URL from environment
从环境变量中获取PostgreSQL URL
Priority order: DATABASE_URL → POSTGRES_URI → POSTGRES_URL
优先级:DATABASE_URL → POSTGRES_URI → POSTGRES_URL
Note: We strip the database name from the URL since Convex appends INSTANCE_NAME automatically
注意:我们会从URL中移除数据库名称,因为Convex会自动追加INSTANCE_NAME
E.g., "postgres://...:5432/app" becomes "postgres://...:5432"
例如:"postgres://...:5432/app" 变为 "postgres://...:5432"
_RAW_POSTGRES_URL="${DATABASE_URL:-${POSTGRES_URI:-${POSTGRES_URL:-}}}"
if [ -n "$_RAW_POSTGRES_URL" ]; then
Remove trailing database name (e.g., /app) if present
_STRIPPED_URL="${_RAW_POSTGRES_URL%/[^/]*}"
For Coder PostgreSQL with self-signed certificates, use sslmode=disable
Note: Rust postgres crate may not accept sslmode parameter in URL, depends on version
if [[ "$_STRIPPED_URL" == "?" ]]; then
# URL already has query parameters
POSTGRES_URL="${_STRIPPED_URL}&sslmode=disable"
else
# Add query parameters - use disable for self-signed certs
POSTGRES_URL="${_STRIPPED_URL}?sslmode=disable"
fi
else
Fallback to default for local development
POSTGRES_URL="postgresql://convex:convex@localhost:5432/convex?sslmode=disable"
fi
_RAW_POSTGRES_URL="${DATABASE_URL:-${POSTGRES_URI:-${POSTGRES_URL:-}}}"
if [ -n "$_RAW_POSTGRES_URL" ]; then
移除末尾的数据库名称(例如/app)(若存在)
_STRIPPED_URL="${_RAW_POSTGRES_URL%/[^/]*}"
对于使用自签名证书的Coder PostgreSQL,使用sslmode=disable
注意:Rust postgres crate可能不支持URL中的sslmode参数,取决于版本
if [[ "$_STRIPPED_URL" == "?" ]]; then
# URL已包含查询参数
POSTGRES_URL="${_STRIPPED_URL}&sslmode=disable"
else
# 添加查询参数 - 自签名证书使用disable
POSTGRES_URL="${_STRIPPED_URL}?sslmode=disable"
fi
else
本地开发环境回退到默认值
POSTGRES_URL="postgresql://convex:convex@localhost:5432/convex?sslmode=disable"
fi
Verify PostgreSQL URL is configured
验证PostgreSQL URL已配置
if [ -z "$POSTGRES_URL" ]; then
echo "❌ POSTGRES_URL is not set"
echo " Please set DATABASE_URL or POSTGRES_URL in your environment"
echo ""
echo " In Coder workspaces, these variables are automatically provided."
echo " For local development, ensure PostgreSQL is running and set the variable."
exit 1
fi
if [ -z "$POSTGRES_URL" ]; then
echo "❌ POSTGRES_URL未设置"
echo " 请在环境中设置DATABASE_URL或POSTGRES_URL"
echo ""
echo " 在Coder工作区中,这些变量会自动提供。"
echo " 本地开发时,请确保PostgreSQL已运行并设置该变量。"
exit 1
fi
Admin key will be generated by the container on first start
管理密钥将在容器首次启动时生成
The container's generate_admin_key.sh script is the proper way to generate keys
容器的generate_admin_key.sh脚本是生成密钥的正确方式
We'll retrieve it after the container starts
我们会在容器启动后获取它
CONVEX_ADMIN_KEY="${CONVEX_ADMIN_KEY:-}"
CONVEX_ADMIN_KEY="${CONVEX_ADMIN_KEY:-}"
Generate JWT private key for auth (PKCS#8 format)
生成身份验证用JWT私钥(PKCS#8格式)
if [ ! -f jwt_private_key.pem ]; then
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out jwt_private_key.pem 2>/dev/null
fi
if [ ! -f jwt_private_key.pem ]; then
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out jwt_private_key.pem 2>/dev/null
fi
Create .env.convex.local (only if missing or incomplete)
创建.env.convex.local(仅当缺失或不完整时)
ENV_FILE=".env.convex.local"
ENV_FILE_MISSING=0
if [ ! -f "$ENV_FILE" ]; then
echo "📝 Creating $ENV_FILE..."
ENV_FILE_MISSING=1
else
Check if required variables are present
if ! grep -q "^POSTGRES_URL=" "$ENV_FILE" 2>/dev/null; then
echo "📝 $ENV_FILE exists but missing POSTGRES_URL, updating..."
ENV_FILE_MISSING=1
fi
if ! grep -q "^CONVEX_CLOUD_ORIGIN=" "$ENV_FILE" 2>/dev/null && [ -n "$CONVEX_API_URL" ]; then
echo "📝 $ENV_FILE exists but missing Convex URLs, updating..."
ENV_FILE_MISSING=1
fi
fi
if [ $ENV_FILE_MISSING -eq 1 ]; then
Create or update the env file
cat > "$ENV_FILE" << ENVEOF
ENV_FILE=".env.convex.local"
ENV_FILE_MISSING=0
if [ ! -f "$ENV_FILE" ]; then
echo "📝 创建$ENV_FILE..."
ENV_FILE_MISSING=1
else
检查是否存在必需变量
if ! grep -q "^POSTGRES_URL=" "$ENV_FILE" 2>/dev/null; then
echo "📝 $ENV_FILE已存在但缺少POSTGRES_URL,正在更新..."
ENV_FILE_MISSING=1
fi
if ! grep -q "^CONVEX_CLOUD_ORIGIN=" "$ENV_FILE" 2>/dev/null && [ -n "$CONVEX_API_URL" ]; then
echo "📝 $ENV_FILE已存在但缺少Convex URL,正在更新..."
ENV_FILE_MISSING=1
fi
fi
if [ $ENV_FILE_MISSING -eq 1 ]; then
创建或更新环境文件
cat > "$ENV_FILE" << ENVEOF
Self-hosted Convex configuration
自托管Convex配置
Auto-generated by setup-convex.sh
由setup-convex.sh自动生成
PostgreSQL connection URL
PostgreSQL连接URL
POSTGRES_URL=$POSTGRES_URL
POSTGRES_URL=$POSTGRES_URL
Convex Cloud Origin - External URL for Convex API access
Convex Cloud Origin - Convex API访问用外部URL
CONVEX_CLOUD_ORIGIN=$CONVEX_CLOUD_ORIGIN
CONVEX_CLOUD_ORIGIN=$CONVEX_CLOUD_ORIGIN
Convex Site Origin - HTTP actions endpoint (for auth)
Convex Site Origin - HTTP操作端点(身份验证用)
CONVEX_SITE_ORIGIN=$CONVEX_SITE_ORIGIN
CONVEX_SITE_ORIGIN=$CONVEX_SITE_ORIGIN
Convex Site URL - Used by @convex-dev/auth for provider domain
Convex Site URL - @convex-dev/auth用于提供程序域名
CONVEX_SITE_URL=$CONVEX_API_URL
CONVEX_SITE_URL=$CONVEX_API_URL
Convex Deployment URL
Convex部署URL
CONVEX_DEPLOYMENT_URL=$CONVEX_DEPLOYMENT_URL
CONVEX_DEPLOYMENT_URL=$CONVEX_DEPLOYMENT_URL
Frontend Configuration
前端配置
VITE_CONVEX_URL=$CONVEX_CLOUD_ORIGIN
VITE_CONVEX_URL=$CONVEX_CLOUD_ORIGIN
Admin Key will be retrieved from container after it starts
管理密钥将在容器启动后从容器中获取
CONVEX_ADMIN_KEY and CONVEX_SELF_HOSTED_ADMIN_KEY are set by the container
CONVEX_ADMIN_KEY和CONVEX_SELF_HOSTED_ADMIN_KEY由容器设置
JWT Configuration (for auth)
JWT配置(身份验证用)
JWT_ISSUER should match CONVEX_SITE_ORIGIN for proper auth validation
JWT_ISSUER应与CONVEX_SITE_ORIGIN匹配以确保身份验证有效
JWT_ISSUER=$CONVEX_SITE_ORIGIN
ENVEOF
echo "✅ Created $ENV_FILE"
fi
JWT_ISSUER=$CONVEX_SITE_ORIGIN
ENVEOF
echo "✅ 创建$ENV_FILE完成"
fi
Source environment variables from the (now existing) file
从(现已存在的)文件中加载环境变量
echo "📦 Loading environment variables from $ENV_FILE"
set -a
source "$ENV_FILE"
set +a
echo "Convex environment configured!"
echo "API URL: ${CONVEX_API_URL}"
echo "Site URL: ${CONVEX_SITE_URL}"
echo "Dashboard URL: ${CONVEX_DASHBOARD_URL}"
echo "Admin Key: ${CONVEX_ADMIN_KEY:0:20}..."
Make it executable and run:
```bash
chmod +x scripts/setup-convex.sh
./scripts/setup-convex.shecho "📦 从$ENV_FILE加载环境变量"
set -a
source "$ENV_FILE"
set +a
echo "Convex环境配置完成!"
echo "API URL: ${CONVEX_API_URL}"
echo "站点URL: ${CONVEX_SITE_URL}"
echo "控制台URL: ${CONVEX_DASHBOARD_URL}"
echo "管理密钥: ${CONVEX_ADMIN_KEY:0:20}..."
设置可执行权限并运行:
```bash
chmod +x scripts/setup-convex.sh
./scripts/setup-convex.shStep 6: Create Custom Entrypoint Script
步骤6:创建自定义入口脚本
Create convex-backend-entrypoint.sh:
bash
#!/bin/bash创建convex-backend-entrypoint.sh:
bash
#!/bin/bashWrapper script to start Convex backend with JWT_PRIVATE_KEY from file
用于从文件加载JWT_PRIVATE_KEY后启动Convex后端的包装脚本
Based on the original run_backend.sh but with JWT_PRIVATE_KEY loading
基于原始run_backend.sh但添加了JWT_PRIVATE_KEY加载逻辑
set -e
export DATA_DIR=${DATA_DIR:-/convex/data}
export TMPDIR=${TMPDIR:-"$DATA_DIR/tmp"}
export STORAGE_DIR=${STORAGE_DIR:-"$DATA_DIR/storage"}
export SQLITE_DB=${SQLITE_DB:-"$DATA_DIR/db.sqlite3"}
set -e
export DATA_DIR=${DATA_DIR:-/convex/data}
export TMPDIR=${TMPDIR:-"$DATA_DIR/tmp"}
export STORAGE_DIR=${STORAGE_DIR:-"$DATA_DIR/storage"}
export SQLITE_DB=${SQLITE_DB:-"$DATA_DIR/db.sqlite3"}
Database driver flags
数据库驱动标志
POSTGRES_DB_FLAGS=(--db postgres-v5)
MYSQL_DB_FLAGS=(--db mysql-v5)
mkdir -p "$TMPDIR" "$STORAGE_DIR"
POSTGRES_DB_FLAGS=(--db postgres-v5)
MYSQL_DB_FLAGS=(--db mysql-v5)
mkdir -p "$TMPDIR" "$STORAGE_DIR"
NOTE: INSTANCE_NAME and INSTANCE_SECRET are set via Docker environment variables
注意:INSTANCE_NAME和INSTANCE_SECRET通过Docker环境变量设置
in docker-compose.convex.yml. They are NOT sourced from a credentials script.
在docker-compose.convex.yml中定义,而非从凭据脚本中获取
IMPORTANT: Set JWT_PRIVATE_KEY BEFORE sourcing anything else
重要提示:在加载其他内容之前设置JWT_PRIVATE_KEY
This environment variable MUST be set before the Convex backend starts
此环境变量必须在Convex后端启动前设置
for it to be available in the isolate workers
以便隔离工作进程可以访问它
if [ -f /jwt_private_key.pem ]; then
echo "Loading JWT_PRIVATE_KEY from /jwt_private_key.pem..."
DECODED_KEY=$(cat /jwt_private_key.pem)
echo "JWT_PRIVATE_KEY loaded (length: ${#DECODED_KEY})"
export JWT_PRIVATE_KEY="$DECODED_KEY"
echo "JWT_PRIVATE_KEY exported successfully"
echo "Verifying: ${#JWT_PRIVATE_KEY} characters"
elif [ -n "$JWT_PRIVATE_KEY_BASE64" ]; then
echo "Loading JWT_PRIVATE_KEY from JWT_PRIVATE_KEY_BASE64..."
DECODED_KEY=$(echo "$JWT_PRIVATE_KEY_BASE64" | base64 -d)
echo "JWT_PRIVATE_KEY loaded (length: ${#DECODED_KEY})"
export JWT_PRIVATE_KEY="$DECODED_KEY"
echo "JWT_PRIVATE_KEY exported successfully"
echo "Verifying: ${#JWT_PRIVATE_KEY} characters"
fi
if [ -f /jwt_private_key.pem ]; then
echo "从/jwt_private_key.pem加载JWT_PRIVATE_KEY..."
DECODED_KEY=$(cat /jwt_private_key.pem)
echo "JWT_PRIVATE_KEY加载完成(长度: ${#DECODED_KEY})"
export JWT_PRIVATE_KEY="$DECODED_KEY"
echo "JWT_PRIVATE_KEY导出成功"
echo "验证: ${#JWT_PRIVATE_KEY}个字符"
elif [ -n "$JWT_PRIVATE_KEY_BASE64" ]; then
echo "从JWT_PRIVATE_KEY_BASE64加载JWT_PRIVATE_KEY..."
DECODED_KEY=$(echo "$JWT_PRIVATE_KEY_BASE64" | base64 -d)
echo "JWT_PRIVATE_KEY加载完成(长度: ${#DECODED_KEY})"
export JWT_PRIVATE_KEY="$DECODED_KEY"
echo "JWT_PRIVATE_KEY导出成功"
echo "验证: ${#JWT_PRIVATE_KEY}个字符"
fi
Make JWT_PRIVATE_KEY available to child processes via env file
使JWT_PRIVATE_KEY可通过环境文件供子进程访问
if [ -n "$JWT_PRIVATE_KEY" ]; then
Export to a file that will be sourced by child processes
This is necessary because Convex isolate workers don't inherit all environment variables
echo "export JWT_PRIVATE_KEY="$JWT_PRIVATE_KEY"" > /convex/jwt_env.sh
echo "JWT environment written to /convex/jwt_env.sh"
Source it ourselves for good measure
. /convex/jwt_env.sh
fi
if [ -n "$JWT_PRIVATE_KEY" ]; then
导出到将被子进程加载的文件
这是必要的,因为Convex隔离工作进程不会继承所有环境变量
echo "export JWT_PRIVATE_KEY=\"$JWT_PRIVATE_KEY\"" > /convex/jwt_env.sh
echo "JWT环境已写入/convex/jwt_env.sh"
我们自己也加载它以确保生效
. /convex/jwt_env.sh
fi
Determine database configuration
确定数据库配置
if [ -n "$POSTGRES_URL" ]; then
DB_SPEC="$POSTGRES_URL"
DB_FLAGS=("${POSTGRES_DB_FLAGS[@]}")
elif [ -n "$MYSQL_URL" ]; then
DB_SPEC="$MYSQL_URL"
DB_FLAGS=("${MYSQL_DB_FLAGS[@]}")
elif [ -n "$DATABASE_URL" ]; then
echo "Warning: DATABASE_URL is deprecated."
DB_SPEC="$DATABASE_URL"
DB_FLAGS=("${POSTGRES_DB_FLAGS[@]}")
else
DB_SPEC="$SQLITE_DB"
DB_FLAGS=()
fi
if [ -n "$POSTGRES_URL" ]; then
DB_SPEC="$POSTGRES_URL"
DB_FLAGS=("${POSTGRES_DB_FLAGS[@]}")
elif [ -n "$MYSQL_URL" ]; then
DB_SPEC="$MYSQL_URL"
DB_FLAGS=("${MYSQL_DB_FLAGS[@]}")
elif [ -n "$DATABASE_URL" ]; then
echo "警告: DATABASE_URL已弃用。"
DB_SPEC="$DATABASE_URL"
DB_FLAGS=("${POSTGRES_DB_FLAGS[@]}")
else
DB_SPEC="$SQLITE_DB"
DB_FLAGS=()
fi
Use local storage (S3 not configured)
使用本地存储(未配置S3)
STORAGE_FLAGS=(--local-storage "$STORAGE_DIR")
STORAGE_FLAGS=(--local-storage "$STORAGE_DIR")
Run the Convex backend with JWT_PRIVATE_KEY explicitly set in the environment
在环境中显式设置JWT_PRIVATE_KEY后运行Convex后端
Using env to ensure the variable is passed to the child process
使用env确保变量传递给子进程
exec env JWT_PRIVATE_KEY="$JWT_PRIVATE_KEY" "$@" ./convex-local-backend
--instance-name "$INSTANCE_NAME"
--instance-secret "$INSTANCE_SECRET"
--port 3210
--site-proxy-port 3211
--convex-origin "$CONVEX_CLOUD_ORIGIN"
--convex-site "$CONVEX_SITE_ORIGIN"
--beacon-tag "self-hosted-docker"
${DISABLE_BEACON:+--disable-beacon}
${REDACT_LOGS_TO_CLIENT:+--redact-logs-to-client}
${DO_NOT_REQUIRE_SSL:+--do-not-require-ssl}
"${DB_FLAGS[@]}"
"${STORAGE_FLAGS[@]}"
"$DB_SPEC"
--instance-name "$INSTANCE_NAME"
--instance-secret "$INSTANCE_SECRET"
--port 3210
--site-proxy-port 3211
--convex-origin "$CONVEX_CLOUD_ORIGIN"
--convex-site "$CONVEX_SITE_ORIGIN"
--beacon-tag "self-hosted-docker"
${DISABLE_BEACON:+--disable-beacon}
${REDACT_LOGS_TO_CLIENT:+--redact-logs-to-client}
${DO_NOT_REQUIRE_SSL:+--do-not-require-ssl}
"${DB_FLAGS[@]}"
"${STORAGE_FLAGS[@]}"
"$DB_SPEC"
Make it executable:
```bash
chmod +x convex-backend-entrypoint.shexec env JWT_PRIVATE_KEY="$JWT_PRIVATE_KEY" "$@" ./convex-local-backend \
--instance-name "$INSTANCE_NAME" \
--instance-secret "$INSTANCE_SECRET" \
--port 3210 \
--site-proxy-port 3211 \
--convex-origin "$CONVEX_CLOUD_ORIGIN" \
--convex-site "$CONVEX_SITE_ORIGIN" \
--beacon-tag "self-hosted-docker" \
${DISABLE_BEACON:+--disable-beacon} \
${REDACT_LOGS_TO_CLIENT:+--redact-logs-to-client} \
${DO_NOT_REQUIRE_SSL:+--do-not-require-ssl} \
"${DB_FLAGS[@]}" \
"${STORAGE_FLAGS[@]}" \
"$DB_SPEC"
设置可执行权限:
```bash
chmod +x convex-backend-entrypoint.shStep 7: Create Docker Compose Configuration
步骤7:创建Docker Compose配置
Create docker-compose.convex.yml:
yaml
services:
convex-backend:
image: ghcr.io/get-convex/convex-backend:latest
container_name: convex-backend-local
env_file:
- .env.convex.local
stop_grace_period: 10s
stop_signal: SIGINT
ports:
- "3210:3210" # Convex API port
- "3211:3211" # Convex site proxy port (for auth)
volumes:
- convex-data:/convex/data
- ./convex-backend-entrypoint.sh:/convex-backend-entrypoint.sh:ro
- ./jwt_private_key.pem:/jwt_private_key.pem:ro
entrypoint: ["/bin/bash", "/convex-backend-entrypoint.sh"]
environment:
# Convex Cloud Origin - External URL for Convex API access
# For local development, defaults to http://localhost:3210
# In Coder workspaces, set via .env.convex.local
- CONVEX_CLOUD_ORIGIN=${CONVEX_CLOUD_ORIGIN:-http://localhost:3210}
# Convex Site Origin - HTTP actions endpoint (for auth)
# For local development, defaults to http://localhost:3211
# In Coder workspaces, set via .env.convex.local
- CONVEX_SITE_ORIGIN=${CONVEX_SITE_ORIGIN:-http://localhost:3211}
# PostgreSQL Database URL (required)
- POSTGRES_URL=${POSTGRES_URL}
# Instance name for identification (matches PostgreSQL database name)
- INSTANCE_NAME=app
# Admin key for authentication (generated on first start)
- CONVEX_ADMIN_KEY=${CONVEX_ADMIN_KEY:-}
# Logging
- RUST_LOG=info,convex=debug
# Lower document retention for development
- DOCUMENT_RETENTION_DELAY=172800
# Disable SSL requirement for local development
- DO_NOT_REQUIRE_SSL=true
# Auth configuration for @convex-dev/auth
# Note: JWT_PRIVATE_KEY is set by convex-backend-entrypoint.sh from mounted file
- JWT_ISSUER=${JWT_ISSUER:-http://localhost:3211}
# Instance secret (auto-generated by backend if not set)
- INSTANCE_SECRET=${INSTANCE_SECRET:-}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3210/version"]
interval: 5s
start_period: 10s
timeout: 5s
retries: 3
convex-dashboard:
image: ghcr.io/get-convex/convex-dashboard:latest
container_name: convex-dashboard-local
env_file:
- .env.convex.local
stop_grace_period: 10s
stop_signal: SIGINT
ports:
- "6791:6791" # Dashboard port
environment:
# Deployment URL for dashboard
- NEXT_PUBLIC_DEPLOYMENT_URL=${CONVEX_DEPLOYMENT_URL:-http://localhost:3210}
depends_on:
convex-backend:
condition: service_healthy
volumes:
convex-data:
driver: localCritical Configuration Explained:
- Custom Entrypoint: Loads from mounted file before starting backend
JWT_PRIVATE_KEY - Volume Mount: is mounted at
jwt_private_key.pem/jwt_private_key.pem:ro - Ports: 3210 (API), 3211 (site proxy for auth), 6791 (dashboard)
- : External URL for the API (for internal Convex communication)
CONVEX_CLOUD_ORIGIN - : External URL for the site proxy (for auth provider discovery, set via
CONVEX_SITE_ORIGIN)npx convex env set - : Points to site proxy URL
JWT_ISSUER - Healthcheck: Ensures backend is ready before dashboard starts
创建docker-compose.convex.yml:
yaml
services:
convex-backend:
image: ghcr.io/get-convex/convex-backend:latest
container_name: convex-backend-local
env_file:
- .env.convex.local
stop_grace_period: 10s
stop_signal: SIGINT
ports:
- "3210:3210" # Convex API端口
- "3211:3211" # Convex站点代理端口(身份验证用)
volumes:
- convex-data:/convex/data
- ./convex-backend-entrypoint.sh:/convex-backend-entrypoint.sh:ro
- ./jwt_private_key.pem:/jwt_private_key.pem:ro
entrypoint: ["/bin/bash", "/convex-backend-entrypoint.sh"]
environment:
# Convex Cloud Origin - Convex API访问用外部URL
# 本地开发默认值为http://localhost:3210
# Coder工作区中通过.env.convex.local设置
- CONVEX_CLOUD_ORIGIN=${CONVEX_CLOUD_ORIGIN:-http://localhost:3210}
# Convex Site Origin - HTTP操作端点(身份验证用)
# 本地开发默认值为http://localhost:3211
# Coder工作区中通过.env.convex.local设置
- CONVEX_SITE_ORIGIN=${CONVEX_SITE_ORIGIN:-http://localhost:3211}
# PostgreSQL数据库URL(必需)
- POSTGRES_URL=${POSTGRES_URL}
# 用于标识的实例名称(与PostgreSQL数据库名称匹配)
- INSTANCE_NAME=app
# 身份验证用管理密钥(首次启动时生成)
- CONVEX_ADMIN_KEY=${CONVEX_ADMIN_KEY:-}
# 日志配置
- RUST_LOG=info,convex=debug
# 开发环境缩短文档保留时间
- DOCUMENT_RETENTION_DELAY=172800
# 本地开发禁用SSL要求
- DO_NOT_REQUIRE_SSL=true
# @convex-dev/auth用身份验证配置
# 注意:JWT_PRIVATE_KEY由convex-backend-entrypoint.sh从挂载文件设置
- JWT_ISSUER=${JWT_ISSUER:-http://localhost:3211}
# 实例密钥(后端未设置时自动生成)
- INSTANCE_SECRET=${INSTANCE_SECRET:-}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3210/version"]
interval: 5s
start_period: 10s
timeout: 5s
retries: 3
convex-dashboard:
image: ghcr.io/get-convex/convex-dashboard:latest
container_name: convex-dashboard-local
env_file:
- .env.convex.local
stop_grace_period: 10s
stop_signal: SIGINT
ports:
- "6791:6791" # 控制台端口
environment:
# 控制台用部署URL
- NEXT_PUBLIC_DEPLOYMENT_URL=${CONVEX_DEPLOYMENT_URL:-http://localhost:3210}
depends_on:
convex-backend:
condition: service_healthy
volumes:
convex-data:
driver: local关键配置说明:
- 自定义入口点:在启动后端前从挂载文件加载
JWT_PRIVATE_KEY - 卷挂载:以只读方式挂载到/jwt_private_key.pem
jwt_private_key.pem - 端口:3210(API)、3211(身份验证用站点代理)、6791(控制台)
- :API用外部URL(Convex内部通信用)
CONVEX_CLOUD_ORIGIN - :站点代理用外部URL(身份验证提供程序发现用,通过
CONVEX_SITE_ORIGIN设置)npx convex env set - :指向站点代理URL
JWT_ISSUER - 健康检查:确保后端就绪后再启动控制台
Step 8: Create Startup Script
步骤8:创建启动脚本
Create start-convex-backend.sh:
bash
#!/bin/bash创建start-convex-backend.sh:
bash
#!/bin/bashLoad environment
加载环境变量
if [ -f .env.convex.local ]; then
set -a
source .env.convex.local
set +a
fi
if [ -f .env.convex.local ]; then
set -a
source .env.convex.local
set +a
fi
Start Docker services
启动Docker服务
docker compose -f docker-compose.convex.yml up -d
echo "Waiting for Convex backend to be healthy..."
until curl -s http://localhost:3210/version > /dev/null 2>&1; do
echo "Waiting for Convex API..."
sleep 2
done
echo "Convex backend is running!"
echo "Dashboard: ${CONVEX_DASHBOARD_URL:-http://localhost:6791}"
> **CRITICAL DEPLOYMENT ORDER**: The startup sequence must follow this order:
> 1. Start Docker services (backend becomes healthy)
> 2. **Initialize deployment environment variables** (`npx convex env set`) - These MUST be set before deployment!
> 3. **Then deploy functions** (`npx convex deploy --yes`)
>
> Why this order: Auth-related environment variables (like `CONVEX_SITE_ORIGIN`, `JWT_ISSUER`, `JWKS`) must be set **before** deploying functions. If you deploy first, the deployment may fail or auth may not work properly.docker compose -f docker-compose.convex.yml up -d
echo "等待Convex后端就绪..."
until curl -s http://localhost:3210/version > /dev/null 2>&1; do
echo "等待Convex API..."
sleep 2
done
echo "Convex后端已运行!"
echo "控制台: ${CONVEX_DASHBOARD_URL:-http://localhost:6791}"
> **关键部署顺序**:启动序列必须遵循以下顺序:
> 1. 启动Docker服务(后端变为健康状态)
> 2. **初始化部署环境变量**(`npx convex env set`)- 这些必须在部署前设置!
> 3. **然后部署函数**(`npx convex deploy --yes`)
>
> 原因:身份验证相关环境变量(如`CONVEX_SITE_ORIGIN`、`JWT_ISSUER`、`JWKS`)必须在**部署函数前**设置。如果先部署,可能会导致部署失败或身份验证无法正常工作。Step 9: Add NPM Scripts
步骤9:添加NPM脚本
Add these scripts to your package.json:
json
{
"scripts": {
"dev": "npm-run-all --parallel dev:frontend convex:start",
"dev:frontend": "vite",
"dev:backend": "convex dev --local --once",
"convex:start": "./scripts/setup-convex.sh",
"convex:stop": "docker compose -f docker-compose.convex.yml down",
"convex:logs": "docker compose -f docker-compose.convex.yml logs -f",
"convex:status": "docker compose -f docker-compose.convex.yml ps",
"deploy:functions": "npx convex deploy --yes"
}
}Script explanations:
- - Starts both frontend and Convex backend in parallel
dev - - Runs the frontend development server (Vite, Next.js, etc.)
dev:frontend - - Runs Convex in development mode against local backend, then exits
dev:backend - - Sets up environment and starts Docker services
convex:start - - Stops Docker services
convex:stop - - Shows Convex backend logs
convex:logs - - Shows status of Docker containers
convex:status - - Deploys Convex functions to the self-hosted backend
deploy:functions
将以下脚本添加到你的package.json:
json
{
"scripts": {
"dev": "npm-run-all --parallel dev:frontend convex:start",
"dev:frontend": "vite",
"dev:backend": "convex dev --local --once",
"convex:start": "./scripts/setup-convex.sh",
"convex:stop": "docker compose -f docker-compose.convex.yml down",
"convex:logs": "docker compose -f docker-compose.convex.yml logs -f",
"convex:status": "docker compose -f docker-compose.convex.yml ps",
"deploy:functions": "npx convex deploy --yes"
}
}脚本说明:
- - 并行启动前端和Convex后端
dev - - 运行前端开发服务器(Vite、Next.js等)
dev:frontend - - 针对本地后端以开发模式运行Convex,然后退出
dev:backend - - 设置环境并启动Docker服务
convex:start - - 停止Docker服务
convex:stop - - 查看Convex后端日志
convex:logs - - 查看Docker容器状态
convex:status - - 将Convex函数部署到自托管后端
deploy:functions
Step 10: Initialize Convex Deployment
步骤10:初始化Convex部署
bash
undefinedbash
undefinedSetup environment and start backend
设置环境并启动后端
[package-manager] run convex:start
[package-manager] run convex:start
Initialize Convex (creates schema, generates types)
初始化Convex(创建Schema,生成类型定义)
[package-manager] run dev:backend
This will:
1. Generate Coder-specific environment variables
2. Start Docker services with correct configuration
3. Create the database schema
4. Generate type definitions in `convex/_generated/`[package-manager] run dev:backend
此操作将:
1. 生成Coder专属环境变量
2. 使用正确配置启动Docker服务
3. 创建数据库Schema
4. 在`convex/_generated/`中生成类型定义Step 11: Initialize Deployment Environment Variables
步骤11:初始化部署环境变量
IMPORTANT: Run this step BEFORE deploying functions. Auth environment variables must be set first.
Create scripts/init-convex-env.sh:
bash
#!/bin/bash重要提示:在部署函数前运行此步骤。身份验证环境变量必须先设置。
创建scripts/init-convex-env.sh:
bash
#!/bin/bashInitialize Convex deployment environment variables
初始化Convex部署环境变量
Reads from .env.convex.deployment and sets variables via npx convex env set
从.env.convex.deployment读取并通过npx convex env set设置变量
set -e
DEPLOYMENT_ENV_FILE=".env.convex.deployment"
CONTAINER_ENV_FILE=".env.convex.local"
echo "🔐 Initializing Convex deployment environment variables..."
set -e
DEPLOYMENT_ENV_FILE=".env.convex.deployment"
CONTAINER_ENV_FILE=".env.convex.local"
echo "🔐 初始化Convex部署环境变量..."
Create deployment env file if it doesn't exist
若不存在则创建部署环境文件
if [ ! -f "$DEPLOYMENT_ENV_FILE" ]; then
echo "📝 Creating $DEPLOYMENT_ENV_FILE..."
cat > "$DEPLOYMENT_ENV_FILE" << 'EOF'
if [ ! -f "$DEPLOYMENT_ENV_FILE" ]; then
echo "📝 创建$DEPLOYMENT_ENV_FILE..."
cat > "$DEPLOYMENT_ENV_FILE" << 'EOF'
Convex Deployment Environment Variables
Convex部署环境变量
These variables are set via npx convex env set and appear in the dashboard
这些变量通过npx convex env set设置并显示在控制台中
This file should be gitignored (contains secrets)
此文件应被Git忽略(包含机密信息)
=== AUTO-GENERATED VARIABLES (do not edit manually) ===
=== 自动生成变量(请勿手动编辑) ===
These are managed by scripts/init-convex-env.sh
由scripts/init-convex-env.sh管理
Multi-line values are stored as base64 for safe env file storage
多行值以base64格式存储以确保环境文件安全
JWT_PRIVATE_KEY_BASE64=""
JWT_ISSUER=""
JWKS=""
JWT_PRIVATE_KEY_BASE64=""
JWT_ISSUER=""
JWKS=""
=== USER VARIABLES (add your own below) ===
=== 用户自定义变量(在下方添加) ===
Add your environment variables here, one per line
在此添加你的环境变量,每行一个
Example:
示例:
OPENAI_API_KEY=sk-...
OPENAI_API_KEY=sk-...
STRIPE_SECRET_KEY=sk_live_...
STRIPE_SECRET_KEY=sk_live_...
ANTHROPIC_API_KEY=sk-ant-...
ANTHROPIC_API_KEY=sk-ant-...
EOF
fi
EOF
fi
Source container env file to get CONVEX_SITE_ORIGIN
从容器环境文件加载CONVEX_SITE_ORIGIN
set -a
source "$CONTAINER_ENV_FILE"
set +a
set -a
source "$CONTAINER_ENV_FILE"
set +a
Check if JWT key file exists and has content
检查JWT密钥文件是否存在且有内容
JWT_KEY_FILE="jwt_private_key.pem"
if [ -f "$JWT_KEY_FILE" ] && [ -s "$JWT_KEY_FILE" ]; then
# Read existing key from file
JWT_PRIVATE_KEY=$(cat "$JWT_KEY_FILE")
echo "📂 Using existing JWT key from $JWT_KEY_FILE"
else
# Generate a new key
echo "🔑 Generating new JWT private key..."
JWT_PRIVATE_KEY=$(openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 2>/dev/null | openssl pkcs8 -topk8 -nocrypt -outform PEM 2>/dev/null)
if [ -z "$JWT_PRIVATE_KEY" ]; then
echo "❌ Failed to generate JWT private key"
exit 1
fi
echo "✅ Generated new JWT private key"
# Write the key to the host file for persistence
echo "$JWT_PRIVATE_KEY" > "$JWT_KEY_FILE"
echo "📝 Saved key to $JWT_KEY_FILE"
echo ""
echo "⚠️ Note: The convex-backend container will use this key on next restart."
echo " To restart: docker compose -f docker-compose.convex.yml restart convex-backend"fi
JWT_KEY_FILE="jwt_private_key.pem"
if [ -f "$JWT_KEY_FILE" ] && [ -s "$JWT_KEY_FILE" ]; then
# 从文件读取现有密钥
JWT_PRIVATE_KEY=$(cat "$JWT_KEY_FILE")
echo "📂 使用$JWT_KEY_FILE中的现有JWT密钥"
else
# 生成新密钥
echo "🔑 生成新的JWT私钥..."
JWT_PRIVATE_KEY=$(openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 2>/dev/null | openssl pkcs8 -topk8 -nocrypt -outform PEM 2>/dev/null)
if [ -z "$JWT_PRIVATE_KEY" ]; then
echo "❌ 生成JWT私钥失败"
exit 1
fi
echo "✅ 已生成新的JWT私钥"
# 将密钥写入主机文件以持久化
echo "$JWT_PRIVATE_KEY" > "$JWT_KEY_FILE"
echo "📝 密钥已保存到$JWT_KEY_FILE"
echo ""
echo "⚠️ 注意: convex-backend容器将在下次重启时使用此密钥。"
echo " 重启命令: docker compose -f docker-compose.convex.yml restart convex-backend"fi
Generate JWKS from private key
从私钥生成JWKS
echo "🔑 Generating JWKS from private key..."
JWKS=$(node -e "
const crypto = require('crypto');
const privateKey = `$JWT_PRIVATE_KEY`;
const publicKey = crypto.createPublicKey(privateKey);
const jwk = publicKey.export({ format: 'jwk' });
// JWKS format requires {"keys": [...]} wrapper
const jwks = { keys: [{ use: 'sig', ...jwk }] };
console.log(JSON.stringify(jwks));
")
echo "🔑 从私钥生成JWKS..."
JWKS=$(node -e "
const crypto = require('crypto');
const privateKey = \;
const publicKey = crypto.createPublicKey(privateKey);
const jwk = publicKey.export({ format: 'jwk' });
// JWKS格式需要{\"keys\": [...]}包装
const jwks = { keys: [{ use: 'sig', ...jwk }] };
console.log(JSON.stringify(jwks));
")
$JWT_PRIVATE_KEY\\Update auto-generated variables in the deployment env file
更新部署环境文件中的自动生成变量
echo "📝 Updating auto-generated variables in $DEPLOYMENT_ENV_FILE..."
TEMP_FILE=$(mktemp)
echo "📝 更新$DEPLOYMENT_ENV_FILE中的自动生成变量..."
TEMP_FILE=$(mktemp)
Encode the multi-line JWT private key as base64 for safe env file storage
将多行JWT私钥编码为base64以安全存储在环境文件中
JWT_PRIVATE_KEY_BASE64=$(echo "$JWT_PRIVATE_KEY" | base64 -w 0)
JWT_PRIVATE_KEY_BASE64=$(echo "$JWT_PRIVATE_KEY" | base64 -w 0)
Process the file and update auto-generated variables
处理文件并更新自动生成变量
while IFS= read -r line || [ -n "$line" ]; do
if [[ "$line" =~ ^JWT_PRIVATE_KEY_BASE64= ]]; then
echo "JWT_PRIVATE_KEY_BASE64="$JWT_PRIVATE_KEY_BASE64""
elif [[ "$line" =~ ^JWT_ISSUER= ]]; then
echo "JWT_ISSUER="$CONVEX_SITE_ORIGIN""
elif [[ "$line" =~ ^JWKS= ]]; then
echo "JWKS="$JWKS""
else
echo "$line"
fi
done < "$DEPLOYMENT_ENV_FILE" > "$TEMP_FILE"
mv "$TEMP_FILE" "$DEPLOYMENT_ENV_FILE"
while IFS= read -r line || [ -n "$line" ]; do
if [[ "$line" =~ ^JWT_PRIVATE_KEY_BASE64= ]]; then
echo "JWT_PRIVATE_KEY_BASE64=\"$JWT_PRIVATE_KEY_BASE64\""
elif [[ "$line" =~ ^JWT_ISSUER= ]]; then
echo "JWT_ISSUER=\"$CONVEX_SITE_ORIGIN\""
elif [[ "$line" =~ ^JWKS= ]]; then
echo "JWKS=\"$JWKS\""
else
echo "$line"
fi
done < "$DEPLOYMENT_ENV_FILE" > "$TEMP_FILE"
mv "$TEMP_FILE" "$DEPLOYMENT_ENV_FILE"
Now set all variables via npx convex env set
现在通过npx convex env set设置所有变量
echo "📤 Setting deployment environment variables..."
echo "📤 设置部署环境变量..."
Set JWT_PRIVATE_KEY (multi-line value, use stdin)
设置JWT_PRIVATE_KEY(多行值,使用标准输入)
echo " Setting JWT_PRIVATE_KEY..."
if ! echo "$JWT_PRIVATE_KEY" | npx convex env set JWT_PRIVATE_KEY; then
echo "❌ Failed to set JWT_PRIVATE_KEY"
exit 1
fi
echo " 设置JWT_PRIVATE_KEY..."
if ! echo "$JWT_PRIVATE_KEY" | npx convex env set JWT_PRIVATE_KEY; then
echo "❌ 设置JWT_PRIVATE_KEY失败"
exit 1
fi
Set CONVEX_SITE_ORIGIN (required for auth provider discovery)
设置CONVEX_SITE_ORIGIN(身份验证提供程序发现必需)
echo " Setting CONVEX_SITE_ORIGIN..."
if ! npx convex env set CONVEX_SITE_ORIGIN "$CONVEX_SITE_ORIGIN"; then
echo "❌ Failed to set CONVEX_SITE_ORIGIN"
exit 1
fi
echo " 设置CONVEX_SITE_ORIGIN..."
if ! npx convex env set CONVEX_SITE_ORIGIN "$CONVEX_SITE_ORIGIN"; then
echo "❌ 设置CONVEX_SITE_ORIGIN失败"
exit 1
fi
Set JWT_ISSUER
设置JWT_ISSUER
echo " Setting JWT_ISSUER..."
if ! npx convex env set JWT_ISSUER "$CONVEX_SITE_ORIGIN"; then
echo "❌ Failed to set JWT_ISSUER"
exit 1
fi
echo " 设置JWT_ISSUER..."
if ! npx convex env set JWT_ISSUER "$CONVEX_SITE_ORIGIN"; then
echo "❌ 设置JWT_ISSUER失败"
exit 1
fi
Set JWKS (multi-line value, use stdin)
设置JWKS(多行值,使用标准输入)
echo " Setting JWKS..."
if ! echo "$JWKS" | npx convex env set JWKS; then
echo "❌ Failed to set JWKS"
exit 1
fi
echo " 设置JWKS..."
if ! echo "$JWKS" | npx convex env set JWKS; then
echo "❌ 设置JWKS失败"
exit 1
fi
Now set user variables from the deployment env file
现在从部署环境文件设置用户自定义变量
Parse only the user section (after the USER VARIABLES comment)
仅处理用户部分(USER VARIABLES注释之后)
USER_SECTION=false
while IFS= read -r line || [ -n "$line" ]; do
# Start processing after USER VARIABLES comment
if [[ "$line" == "USER VARIABLES" ]]; then
USER_SECTION=true
continue
fi
# Only process user variables
[ "$USER_SECTION" = false ] && continue
# Skip comments and empty lines
[[ "$line" == \#* ]] && continue
[ -z "$line" ] && continue
# Extract variable name and value
VAR_NAME="${line%%=*}"
VAR_VALUE="${line#*=}"
# Skip empty values
[ -z "$VAR_VALUE" ] && continue
echo " Setting $VAR_NAME..."
npx convex env set "$VAR_NAME" "$VAR_VALUE"done < "$DEPLOYMENT_ENV_FILE"
echo "✅ Convex deployment environment variables initialized"
echo " Verify in dashboard: Environment Variables section"
Make it executable:
```bash
chmod +x scripts/init-convex-env.shRun the script to initialize deployment environment variables:
bash
bash scripts/init-convex-env.shWhat this script does:
- Creates file for tracking deployment variables
.env.convex.deployment - Generates or reads existing JWT private key from
jwt_private_key.pem - Generates JWKS from the private key using Node.js crypto API
- Sets ,
JWT_PRIVATE_KEY,CONVEX_SITE_ORIGIN, andJWT_ISSUERviaJWKSnpx convex env set - Sets any user-defined variables from the deployment env file
Note: Thefile uses.env.convex.deploymentfor safe storage of the multi-line key as a single-line value. The script decodes it before setting in Convex.JWT_PRIVATE_KEY_BASE64
USER_SECTION=false
while IFS= read -r line || [ -n "$line" ]; do
# 在USER VARIABLES注释后开始处理
if [[ "$line" == "USER VARIABLES" ]]; then
USER_SECTION=true
continue
fi
# 仅处理用户变量
[ "$USER_SECTION" = false ] && continue
# 跳过注释和空行
[[ "$line" == \\#* ]] && continue
[ -z "$line" ] && continue
# 提取变量名和值
VAR_NAME="${line%%=*}"
VAR_VALUE="${line#*=}"
# 跳过空值
[ -z "$VAR_VALUE" ] && continue
echo " 设置$VAR_NAME..."
npx convex env set "$VAR_NAME" "$VAR_VALUE"done < "$DEPLOYMENT_ENV_FILE"
echo "✅ Convex部署环境变量初始化完成"
"
Step 12: Deploy Functions
—
Now that environment variables are initialized, deploy your Convex functions:
bash
[package-manager] run deploy:functionsThis deploys your Convex functions to the self-hosted backend.
Why this order matters: Auth-related environment variables (,CONVEX_SITE_ORIGIN,JWT_ISSUER) must be set before deploying functions. If you deploy first, the deployment may fail or authentication may not work properly.JWKS
—
Step 13: Create Frontend Integration
—
Create or update src/main.tsx:
typescript
import { ConvexReactClient } from "convex/react";
import { ConvexProviderWithAuth } from "convex/react";
import React from "react";
import ReactDOM from "react-dom/client";
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<ConvexProviderWithAuth client={convex}>
<App />
</ConvexProviderWithAuth>
</React.StrictMode>
);Create src/App.tsx:
typescript
import { useQuery } from "convex/react";
import { api } from "../convex/_generated/api";
import { SignInButton, SignOutButton, useAuth } from "@convex-dev/auth/react";
export default function App() {
const { isAuthenticated } = useAuth();
const tasks = useQuery(api.tasks.list) || [];
return (
<main>
<h1>Convex in Coder</h1>
{isAuthenticated ? (
<>
<p>Welcome!</p>
<SignOutButton />
<ul>
{tasks.map(task => (
<li key={task._id}>{task.title}</li>
))}
</ul>
</>
) : (
<SignInButton />
)}
</main>
);
}—
Verification Checklist
—
After setup, verify:
- exists with correct Coder URLs
.env.convex.local - directory exists with type definitions
convex/_generated/ - includes
convex/schema.ts...authTables - uses
convex/auth.tswith providersconvexAuth() - calls
convex/http.tsauth.addHttpRoutes(http) - Docker services are running:
docker ps - Can access API:
curl http://localhost:3210/version - Can access site proxy:
curl http://localhost:3211/ - Can run without errors
[package-manager] run dev:backend - Can run successfully
[package-manager] run deploy:functions - Frontend can import from
convex/_generated/api
—
Troubleshooting Setup Issues
—
Issue: Authentication fails
—
Solution: Verify your environment variables:
bash
grep "CONVEX_SITE" .env.convex.local—
CONVEX_SITE_ORIGIN should point to convex-site URL (port 3211)
—
JWT_ISSUER should match CONVEX_SITE_ORIGIN
—
undefined—
Issue: CONVEX_SITE_ORIGIN not set in deployment
CONVEX_SITE_ORIGIN not set in deployment—
Solution: Run to regenerate environment.
./scripts/setup-convex.sh—
Issue: Port 3211 not accessible
—
Solution: Verify Docker is running the site proxy:
bash
docker ps | grep 3211
curl http://localhost:3211/—
Issue: Docker container not starting
—
Solution:
bash
undefined—
Check container logs
—
[package-manager] run convex:logs
—
Check if ports are already in use
—
lsof -i :3210
lsof -i :3211
lsof -i :6791
—
Recreate container
—
[package-manager] run convex:stop
[package-manager] run convex:start
undefined—
Issue: Type definitions not generating
—
Solution:
bash
undefined—
Clear Convex cache
—
rm -rf convex/_generated
—
Re-run dev backend
—
[package-manager] run dev:backend
—
Or explicitly deploy
—
[package-manager] run deploy:functions
undefined—
Issue: Cannot connect to Convex deployment
—
Solution:
bash
undefined—
Verify Docker services are running
—
docker ps
—
Check deployment URL is correct
—
grep CONVEX .env.convex.local
—
Test connection
—
curl $CONVEX_CLOUD_ORIGIN/version
curl $CONVEX_SITE_ORIGIN/
undefined—
Coder Workspace URL Patterns
—
Internal (Localhost)
—
| Service | URL |
|---|---|
| Convex API | |
| Site Proxy (Auth) | |
| Dashboard | |
—
External (Coder Proxy)
—
| Service | URL Pattern | Example |
|---|---|---|
| Convex API | | |
| Convex Site | | |
| Convex Dashboard | | |
—
Environment Variables Reference
—
Required for Coder Convex
—
bash
undefined—
Coder Workspace URLs (auto-generated by setup script)
—
CONVEX_CLOUD_ORIGIN=<convex-api URL> # e.g., https://convex-api--...coder.hahomelabs.com
CONVEX_SITE_ORIGIN=<convex-site URL> # e.g., https://convex-site--...coder.hahomelabs.com
CONVEX_DEPLOYMENT_URL=<convex-api URL> # Same as CONVEX_CLOUD_ORIGIN
—
Frontend Configuration
—
VITE_CONVEX_URL=<convex-api URL> # Same as CONVEX_CLOUD_ORIGIN
—
Admin Key
—
CONVEX_SELF_HOSTED_ADMIN_KEY=<admin-key> # Auto-generated
—
JWT Configuration (for auth)
—
JWT_ISSUER=<convex-site URL> # Same as CONVEX_SITE_ORIGIN
—
JWT_PRIVATE_KEY is loaded from jwt_private_key.pem via entrypoint script
—
Database (if using PostgreSQL)
—
POSTGRES_URL=<postgres-connection-string> # e.g., postgresql://convex:convex@localhost:5432/convex
undefined—
Critical Variable Relationships
—
CONVEX_CLOUD_ORIGIN = CONVEX_DEPLOYMENT_URL = VITE_CONVEX_URL (all point to convex-api, port 3210)
CONVEX_SITE_ORIGIN = JWT_ISSUER (both point to convex-site, port 3211)Why this works:
- All Convex client communication goes through the API (port 3210)
- The is used for auth provider discovery (set via
CONVEX_SITE_ORIGIN)npx convex env set - The site proxy (port 3211) handles HTTP routes and auth endpoint discovery
- JWT tokens are validated against the which must match
JWT_ISSUERCONVEX_SITE_ORIGIN
—
Docker Commands Reference
—
bash
undefined—
Start services
—
[package-manager] run convex:start # Setup and start all services
—
Stop services
—
[package-manager] run convex:stop # Stop all services
—
View logs
—
[package-manager] run convex:logs # View backend logs
—
Check status
—
[package-manager] run convex:status # Check container status
—
Restart services
—
docker compose -f docker-compose.convex.yml restart
—
Execute command in container
—
docker exec -it <container-name> sh
undefined—
Post-Setup: Next Steps
—
After completing the setup:
- Switch to skill for everyday development
coder-convex - Define your schema in (in
convex/schema.ts)applicationTables - Write queries and mutations in files
convex/*.ts - Integrate with React using hooks
convex/react - Deploy functions with
[package-manager] run deploy:functions]
—
Common Setup Patterns
—
Pattern 1: Minimal Setup with Auth
—
typescript
// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
import { authTables } from "@convex-dev/auth/server";
const applicationTables = {
tasks: defineTable({
title: v.string(),
status: v.string(),
userId: v.id("users"),
}).index("by_user", ["userId"]),
};
export default defineSchema({
...authTables,
...applicationTables,
});—
Pattern 2: With AI/RAG
—
Requires:
- in environment
OPENAI_API_KEY ENABLE_RAG=true- Embeddings generation script
—
Quick Setup Command Sequence
—
For a complete fresh setup:
bash
undefined—
1. Install dependencies
—
[package-manager] add convex @convex-dev/auth
[package-manager] add -D @types/node typescript
—
2. Create directories
—
mkdir -p convex lib scripts
—
3. Create schema with auth
—
cat > convex/schema.ts << 'EOF'
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
import { authTables } from "@convex-dev/auth/server";
const applicationTables = {
tasks: defineTable({
title: v.string(),
status: v.string(),
}).index("by_status", ["status"]),
};
export default defineSchema({
...authTables,
...applicationTables,
});
EOF
—
4. Create auth file
—
cat > convex/auth.ts << 'EOF'
import { convexAuth, getAuthUserId } from "@convex-dev/auth/server";
import { Password } from "@convex-dev/auth/providers/Password";
import { Anonymous } from "@convex-dev/auth/providers/Anonymous";
import { query } from "./_generated/server";
export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
providers: [Password, Anonymous],
});
export const currentUser = query({
args: {},
handler: async (ctx) => {
const userId = await getAuthUserId(ctx);
if (!userId) return null;
return await ctx.db.get(userId);
},
});
EOF
—
5. Create HTTP router files
—
cat > convex/router.ts << 'EOF'
import { httpRouter } from "convex/server";
const http = httpRouter();
export default http;
EOF
cat > convex/http.ts << 'EOF'
import { auth } from "./auth";
import router from "./router";
const http = router;
auth.addHttpRoutes(http);
export default http;
EOF
—
6. Create setup script (copy from Step 5 above)
—
...
—
7. Create docker-compose file (copy from Step 7 above)
—
...
—
8. Run setup
—
[package-manager] run convex:start
—
9. Initialize env vars and deploy
—
bash scripts/init-convex-env.sh
[package-manager] run deploy:functions
undefined—
Summary
—
This skill covers the one-time setup of self-hosted Convex in Coder workspaces:
- Install dependencies (including )
@convex-dev/auth - Create directory structure
- Define schema with auth tables
- Configure auth (in
convexAuth(),auth.ts,router.ts)http.ts - Create Coder-specific setup script
- Configure Docker with proper flags
- Generate environment variables
- Initialize deployment environment variables
- Deploy functions
- Verify setup
For everyday Convex development (queries, mutations, React integration, etc.), use the skill instead.
coder-convex—
Working Example Reference
—
For a complete, working implementation of self-hosted Convex in a Coder workspace, you can reference:
This project demonstrates:
- Self-hosted Convex deployment with Docker Compose
- Complete authentication setup using
@convex-dev/auth - Coder workspace environment configuration
- PostgreSQL database integration
- React frontend with Convex integration
- and
start.shscripts that fully sequence the initialization (env files, admin key generation, deployment)stop.sh
Use this reference to:
- See how all the pieces connect in a real project
- Verify your setup against a working implementation
- Copy configuration patterns (docker-compose, environment setup, scripts)
- Reference the script for the complete initialization sequence
start.sh
Note: This is a demonstration project. Follow the setup steps in this skill for your own project rather than cloning the repo directly.
—
Key Differences from Standard Convex
—
| Aspect | Standard Convex | Coder Convex |
|---|---|---|
| Deployment URL | | Custom Coder proxy URL |
| Environment Variables | | |
| Auth Configuration | Uses Convex Cloud | Uses |
| Site Proxy Port | Not applicable | 3211 |
| Dashboard | Web dashboard at convex.dev | Local at |
| Setup Script | Guided in dashboard | Custom |
—