coder-convex-setup

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Coder-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
coder-convex
skill instead.
你是在Coder工作区中进行自托管Convex初始设置与配置的专家。该技能仅适用于新Convex工作区的一次性设置。日常Convex开发请使用
coder-convex
技能。

When 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
    coder-convex
    instead)
  • Writing queries, mutations, or actions (use
    coder-convex
    instead)
  • Schema modifications (use
    coder-convex
    instead)
  • React integration issues (use
    coder-convex
    instead)
在以下场景使用本技能:
  • 首次在新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:
  1. 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
  2. Docker is available:
    bash
    docker --version
    docker compose version
  3. Project has package.json with Convex dependency:
    json
    {
      "dependencies": {
        "convex": "^1.31.3"
      }
    }
在Coder工作区中设置Convex前,请确保:
  1. 已安装Node.js与包管理器
    bash
    node --version  # 版本应为v18+
    # 检查包管理器:pnpm、yarn、npm或bun
    pnpm --version  # 或:yarn --version, npm --version, bun --version
  2. Docker可用
    bash
    docker --version
    docker compose version
  3. 项目的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:
SlugDisplay NameInternal URLPortHiddenPurpose
convex-dashboard
Convex Dashboard
localhost:6791
6791NoAdmin dashboard
convex-api
Convex API
localhost:3210
3210YesMain API endpoints
convex-site
Convex Site
localhost:3211
3211YesSite Proxy (Auth)
在Coder工作区中,Convex通过多个服务对外暴露。理解这些服务至关重要:
标识显示名称内部URL端口是否隐藏用途
convex-dashboard
Convex控制台
localhost:6791
6791管理控制台
convex-api
Convex API
localhost:3210
3210主API端点
convex-site
Convex站点代理
localhost:3211
3211站点代理(身份验证用)

Step 1: Install Convex Dependencies

步骤1:安装Convex依赖

bash
undefined
bash
undefined

Install 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
undefined

Step 2: Create Convex Directory Structure

步骤2:创建Convex目录结构

Create the following directory structure:
bash
mkdir -p convex/lib
The 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
    ...authTables
    from
    @convex-dev/auth/server
    for Coder workspaces
  • Never manually add
    _id
    or
    _creationTime
    - they're automatic
  • Index names should be descriptive:
    by_fieldName
  • All indexes automatically include
    _creationTime
    as the last field
  • Don't use
    .index("by_creation_time", ["_creationTime"])
    - it's built-in
创建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
@convex-dev/auth
(v0.0.90+) uses the
convexAuth()
function directly. A separate
auth.config.ts
file is no longer required.
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
auth.addHttpRoutes(http)
call is required for auth endpoints (
/auth/*
) to be accessible. Without this, authentication will not work.
注意:新版
@convex-dev/auth
(v0.0.90+)直接使用
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/bash

Detect 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.sh
echo "📦 从$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.sh

Step 6: Create Custom Entrypoint Script

步骤6:创建自定义入口脚本

Create convex-backend-entrypoint.sh:
bash
#!/bin/bash
创建convex-backend-entrypoint.sh
bash
#!/bin/bash

Wrapper 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"

Make it executable:
```bash
chmod +x convex-backend-entrypoint.sh
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"

设置可执行权限:
```bash
chmod +x convex-backend-entrypoint.sh

Step 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: local
Critical Configuration Explained:
  • Custom Entrypoint: Loads
    JWT_PRIVATE_KEY
    from mounted file before starting backend
  • Volume Mount:
    jwt_private_key.pem
    is mounted at
    /jwt_private_key.pem:ro
  • Ports: 3210 (API), 3211 (site proxy for auth), 6791 (dashboard)
  • CONVEX_CLOUD_ORIGIN
    : External URL for the API (for internal Convex communication)
  • CONVEX_SITE_ORIGIN
    : External URL for the site proxy (for auth provider discovery, set via
    npx convex env set
    )
  • JWT_ISSUER
    : Points to site proxy URL
  • 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(控制台)
  • CONVEX_CLOUD_ORIGIN
    :API用外部URL(Convex内部通信用)
  • CONVEX_SITE_ORIGIN
    :站点代理用外部URL(身份验证提供程序发现用,通过
    npx convex env set
    设置)
  • JWT_ISSUER
    :指向站点代理URL
  • 健康检查:确保后端就绪后再启动控制台

Step 8: Create Startup Script

步骤8:创建启动脚本

Create start-convex-backend.sh:
bash
#!/bin/bash
创建start-convex-backend.sh
bash
#!/bin/bash

Load 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:
  • dev
    - Starts both frontend and Convex backend in parallel
  • dev:frontend
    - Runs the frontend development server (Vite, Next.js, etc.)
  • dev:backend
    - Runs Convex in development mode against local backend, then exits
  • convex:start
    - Sets up environment and starts Docker services
  • convex:stop
    - Stops Docker services
  • convex:logs
    - Shows Convex backend logs
  • convex:status
    - Shows status of Docker containers
  • deploy:functions
    - Deploys Convex functions to the self-hosted backend
将以下脚本添加到你的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"
  }
}
脚本说明
  • dev
    - 并行启动前端和Convex后端
  • dev:frontend
    - 运行前端开发服务器(Vite、Next.js等)
  • dev:backend
    - 针对本地后端以开发模式运行Convex,然后退出
  • convex:start
    - 设置环境并启动Docker服务
  • convex:stop
    - 停止Docker服务
  • convex:logs
    - 查看Convex后端日志
  • convex:status
    - 查看Docker容器状态
  • deploy:functions
    - 将Convex函数部署到自托管后端

Step 10: Initialize Convex Deployment

步骤10:初始化Convex部署

bash
undefined
bash
undefined

Setup 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/bash

Initialize 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 = \
$JWT_PRIVATE_KEY\\
; const publicKey = crypto.createPublicKey(privateKey); const jwk = publicKey.export({ format: 'jwk' }); // JWKS格式需要{\"keys\": [...]}包装 const jwks = { keys: [{ use: 'sig', ...jwk }] }; console.log(JSON.stringify(jwks)); ")

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.sh
Run the script to initialize deployment environment variables:
bash
bash scripts/init-convex-env.sh
What this script does:
  1. Creates
    .env.convex.deployment
    file for tracking deployment variables
  2. Generates or reads existing JWT private key from
    jwt_private_key.pem
  3. Generates JWKS from the private key using Node.js crypto API
  4. Sets
    JWT_PRIVATE_KEY
    ,
    CONVEX_SITE_ORIGIN
    ,
    JWT_ISSUER
    , and
    JWKS
    via
    npx convex env set
  5. Sets any user-defined variables from the deployment env file
Note: The
.env.convex.deployment
file uses
JWT_PRIVATE_KEY_BASE64
for safe storage of the multi-line key as a single-line value. The script decodes it before setting in Convex.
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:functions
This deploys your Convex functions to the self-hosted backend.
Why this order matters: Auth-related environment variables (
CONVEX_SITE_ORIGIN
,
JWT_ISSUER
,
JWKS
) must be set before deploying functions. If you deploy first, the deployment may fail or authentication may not work properly.

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:
  • .env.convex.local
    exists with correct Coder URLs
  • convex/_generated/
    directory exists with type definitions
  • convex/schema.ts
    includes
    ...authTables
  • convex/auth.ts
    uses
    convexAuth()
    with providers
  • convex/http.ts
    calls
    auth.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
    [package-manager] run dev:backend
    without errors
  • Can run
    [package-manager] run deploy:functions
    successfully
  • 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

Solution: Run
./scripts/setup-convex.sh
to regenerate environment.

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)

ServiceURL
Convex API
http://localhost:3210
Site Proxy (Auth)
http://localhost:3211
Dashboard
http://localhost:6791

External (Coder Proxy)

ServiceURL PatternExample
Convex API
https://convex-api--<workspace>--<user>.<domain>
https://convex-api--myproject--johndoe.coder.hahomelabs.com
Convex Site
https://convex-site--<workspace>--<user>.<domain>
https://convex-site--myproject--johndoe.coder.hahomelabs.com
Convex Dashboard
https://convex--<workspace>--<user>.<domain>
https://convex--myproject--johndoe.coder.hahomelabs.com

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
    CONVEX_SITE_ORIGIN
    is used for auth provider discovery (set via
    npx convex env set
    )
  • The site proxy (port 3211) handles HTTP routes and auth endpoint discovery
  • JWT tokens are validated against the
    JWT_ISSUER
    which must match
    CONVEX_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:
  1. Switch to
    coder-convex
    skill
    for everyday development
  2. Define your schema in
    convex/schema.ts
    (in
    applicationTables
    )
  3. Write queries and mutations in
    convex/*.ts
    files
  4. Integrate with React using
    convex/react
    hooks
  5. 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:
  • OPENAI_API_KEY
    in environment
  • 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:
  1. Install dependencies (including
    @convex-dev/auth
    )
  2. Create directory structure
  3. Define schema with auth tables
  4. Configure auth (
    convexAuth()
    in
    auth.ts
    ,
    router.ts
    ,
    http.ts
    )
  5. Create Coder-specific setup script
  6. Configure Docker with proper flags
  7. Generate environment variables
  8. Initialize deployment environment variables
  9. Deploy functions
  10. Verify setup
For everyday Convex development (queries, mutations, React integration, etc.), use the
coder-convex
skill instead.

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
  • start.sh
    and
    stop.sh
    scripts
    that fully sequence the initialization (env files, admin key generation, deployment)
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
    start.sh
    script for the complete initialization sequence
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

AspectStandard ConvexCoder Convex
Deployment URL
*.convex.cloud
Custom Coder proxy URL
Environment Variables
CONVEX_DEPLOYMENT
CONVEX_CLOUD_ORIGIN
,
CONVEX_SITE_ORIGIN
Auth ConfigurationUses Convex CloudUses
convexAuth()
with providers,
CONVEX_SITE_ORIGIN
(site proxy, port 3211)
Site Proxy PortNot applicable3211
DashboardWeb dashboard at convex.devLocal at
localhost:6791
Setup ScriptGuided in dashboardCustom
setup-convex.sh
script