| name | coder-convex-setup |
| description | Initial Convex workspace setup in Coder workspaces with self-hosted Convex deployment, authentication configuration, Docker setup, and environment variable generation |
| updated | "2026-01-16T00:00:00.000Z" |
Coder-Convex-Setup: Initial Convex Workspace Setup in Coder
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.
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)
Prerequisites
Before setting up Convex in a Coder workspace, ensure:
-
Node.js and a package manager are installed:
node --version
pnpm --version
-
Docker is available:
docker --version
docker compose version
-
Project has package.json with Convex dependency:
{
"dependencies": {
"convex": "^1.31.3"
}
}
Coder Workspace Services Overview
In a Coder workspace, Convex is exposed through multiple services. Understanding these is critical:
| Slug | Display Name | Internal URL | Port | Hidden | Purpose |
|---|
convex-dashboard | Convex Dashboard | localhost:6791 | 6791 | No | Admin dashboard |
convex-api | Convex API | localhost:3210 | 3210 | Yes | Main API endpoints |
convex-site | Convex Site | localhost:3211 | 3211 | Yes | Site Proxy (Auth) |
Step 1: Install Convex Dependencies
[package-manager] add convex
[package-manager] add @convex-dev/auth
[package-manager] add -D @types/node typescript
Step 2: Create Convex Directory Structure
Create the following directory structure:
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)
Step 3: Create Initial Schema
Create 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(),
}).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
Step 4: Create Auth Configuration
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:
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);
},
});
Create convex/router.ts:
import { httpRouter } from "convex/server";
const http = httpRouter();
export default http;
Create convex/http.ts:
import { auth } from "./auth";
import router from "./router";
const 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.
Step 5: Create Coder Setup Script
Create scripts/setup-convex.sh:
#!/bin/bash
if [ -n "$CODER" ] && [ -n "$CODER_WORKSPACE_NAME" ]; then
CODER_PROTOCOL="${CODER_URL%%://*}"
CODER_DOMAIN="${CODER_URL#*//}"
WORKSPACE_NAME="${CODER_WORKSPACE_NAME}"
USERNAME="${CODER_WORKSPACE_OWNER_NAME:-$USER}"
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
_RAW_POSTGRES_URL="${DATABASE_URL:-${POSTGRES_URI:-${POSTGRES_URL:-}}}"
if [ -n "$_RAW_POSTGRES_URL" ]; then
_STRIPPED_URL="${_RAW_POSTGRES_URL%/[^/]*}"
if [[ "$_STRIPPED_URL" == *"?"* ]]; then
POSTGRES_URL="${_STRIPPED_URL}&sslmode=disable"
else
POSTGRES_URL="${_STRIPPED_URL}?sslmode=disable"
fi
else
POSTGRES_URL="postgresql://convex:convex@localhost:5432/convex?sslmode=disable"
fi
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
CONVEX_ADMIN_KEY="${CONVEX_ADMIN_KEY:-}"
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
ENV_FILE=".env.convex.local"
ENV_FILE_MISSING=0
if [ ! -f "$ENV_FILE" ]; then
echo "📝 Creating $ENV_FILE..."
ENV_FILE_MISSING=1
else
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
cat > "$ENV_FILE" << ENVEOF
# Self-hosted Convex configuration
# Auto-generated by setup-convex.sh
# PostgreSQL connection URL
POSTGRES_URL=$POSTGRES_URL
# Convex Cloud Origin - External URL for Convex API access
CONVEX_CLOUD_ORIGIN=$CONVEX_CLOUD_ORIGIN
# Convex Site Origin - HTTP actions endpoint (for auth)
CONVEX_SITE_ORIGIN=$CONVEX_SITE_ORIGIN
# Convex Site URL - Used by @convex-dev/auth for provider domain
CONVEX_SITE_URL=$CONVEX_API_URL
# Convex Deployment URL
CONVEX_DEPLOYMENT_URL=$CONVEX_DEPLOYMENT_URL
# Frontend Configuration
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
# JWT Configuration (for auth)
# JWT_ISSUER should match CONVEX_SITE_ORIGIN for proper auth validation
JWT_ISSUER=$CONVEX_SITE_ORIGIN
ENVEOF
echo "✅ Created $ENV_FILE"
fi
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:
chmod +x scripts/setup-convex.sh
./scripts/setup-convex.sh
Step 6: Create Custom Entrypoint Script
Create convex-backend-entrypoint.sh:
#!/bin/bash
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"}
POSTGRES_DB_FLAGS=(--db postgres-v5)
MYSQL_DB_FLAGS=(--db mysql-v5)
mkdir -p "$TMPDIR" "$STORAGE_DIR"
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 [ -n "$JWT_PRIVATE_KEY" ]; then
echo "export JWT_PRIVATE_KEY=\"$JWT_PRIVATE_KEY\"" > /convex/jwt_env.sh
echo "JWT environment written to /convex/jwt_env.sh"
. /convex/jwt_env.sh
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 "Warning: DATABASE_URL is deprecated."
DB_SPEC="$DATABASE_URL"
DB_FLAGS=("${POSTGRES_DB_FLAGS[@]}")
else
DB_SPEC="$SQLITE_DB"
DB_FLAGS=()
fi
STORAGE_FLAGS=(--local-storage "$STORAGE_DIR")
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:
chmod +x convex-backend-entrypoint.sh
Step 7: Create Docker Compose Configuration
Create docker-compose.convex.yml:
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"
- "3211:3211"
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_CLOUD_ORIGIN:-http://localhost:3210}
- CONVEX_SITE_ORIGIN=${CONVEX_SITE_ORIGIN:-http://localhost:3211}
- POSTGRES_URL=${POSTGRES_URL}
- INSTANCE_NAME=app
- CONVEX_ADMIN_KEY=${CONVEX_ADMIN_KEY:-}
- RUST_LOG=info,convex=debug
- DOCUMENT_RETENTION_DELAY=172800
- DO_NOT_REQUIRE_SSL=true
- 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:
- 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
Step 8: Create Startup Script
Create start-convex-backend.sh:
#!/bin/bash
if [ -f .env.convex.local ]; then
set -a
source .env.convex.local
set +a
fi
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:
- Start Docker services (backend becomes healthy)
- Initialize deployment environment variables (
npx convex env set) - These MUST be set before deployment!
- 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.
Step 9: Add NPM Scripts
Add these scripts to your package.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
Step 10: Initialize Convex Deployment
[package-manager] run convex:start
[package-manager] run dev:backend
This will:
- Generate Coder-specific environment variables
- Start Docker services with correct configuration
- Create the database schema
- Generate type definitions in
convex/_generated/
Step 11: Initialize Deployment Environment Variables
IMPORTANT: Run this step BEFORE deploying functions. Auth environment variables must be set first.
Create scripts/init-convex-env.sh:
#!/bin/bash
set -e
DEPLOYMENT_ENV_FILE=".env.convex.deployment"
CONTAINER_ENV_FILE=".env.convex.local"
echo "🔐 Initializing Convex deployment environment variables..."
if [ ! -f "$DEPLOYMENT_ENV_FILE" ]; then
echo "📝 Creating $DEPLOYMENT_ENV_FILE..."
cat > "$DEPLOYMENT_ENV_FILE" << 'EOF'
JWT_PRIVATE_KEY_BASE64=""
JWT_ISSUER=""
JWKS=""
EOF
fi
set -a
source "$CONTAINER_ENV_FILE"
set +a
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 "📂 Using existing JWT key from $JWT_KEY_FILE"
else
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"
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
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 "📝 Updating auto-generated variables in $DEPLOYMENT_ENV_FILE..."
TEMP_FILE=$(mktemp)
JWT_PRIVATE_KEY_BASE64=$(echo "$JWT_PRIVATE_KEY" | base64 -w 0)
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"
echo "📤 Setting deployment environment variables..."
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 " 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 " Setting JWT_ISSUER..."
if ! npx convex env set JWT_ISSUER "$CONVEX_SITE_ORIGIN"; then
echo "❌ Failed to set JWT_ISSUER"
exit 1
fi
echo " Setting JWKS..."
if ! echo "$JWKS" | npx convex env set JWKS; then
echo "❌ Failed to set JWKS"
exit 1
fi
USER_SECTION=false
while IFS= read -r line || [ -n "$line" ]; do
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 " 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:
chmod +x scripts/init-convex-env.sh
Run the script to initialize deployment environment variables:
bash scripts/init-convex-env.sh
What this script does:
- Creates
.env.convex.deployment file for tracking deployment variables
- 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, JWT_ISSUER, and JWKS via npx convex env set
- 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.
Step 12: Deploy Functions
Now that environment variables are initialized, deploy your Convex functions:
[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:
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:
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:
Troubleshooting Setup Issues
Issue: Authentication fails
Solution: Verify your environment variables:
grep "CONVEX_SITE" .env.convex.local
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:
docker ps | grep 3211
curl http://localhost:3211/
Issue: Docker container not starting
Solution:
[package-manager] run convex:logs
lsof -i :3210
lsof -i :3211
lsof -i :6791
[package-manager] run convex:stop
[package-manager] run convex:start
Issue: Type definitions not generating
Solution:
rm -rf convex/_generated
[package-manager] run dev:backend
[package-manager] run deploy:functions
Issue: Cannot connect to Convex deployment
Solution:
docker ps
grep CONVEX .env.convex.local
curl $CONVEX_CLOUD_ORIGIN/version
curl $CONVEX_SITE_ORIGIN/
Coder Workspace URL Patterns
Internal (Localhost)
| Service | URL |
|---|
| Convex API | http://localhost:3210 |
| Site Proxy (Auth) | http://localhost:3211 |
| Dashboard | http://localhost:6791 |
External (Coder Proxy)
| Service | URL Pattern | Example |
|---|
| 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
CONVEX_CLOUD_ORIGIN=<convex-api URL>
CONVEX_SITE_ORIGIN=<convex-site URL>
CONVEX_DEPLOYMENT_URL=<convex-api URL>
VITE_CONVEX_URL=<convex-api URL>
CONVEX_SELF_HOSTED_ADMIN_KEY=<admin-key>
JWT_ISSUER=<convex-site URL>
POSTGRES_URL=<postgres-connection-string>
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
[package-manager] run convex:start
[package-manager] run convex:stop
[package-manager] run convex:logs
[package-manager] run convex:status
docker compose -f docker-compose.convex.yml restart
docker exec -it <container-name> sh
Post-Setup: Next Steps
After completing the setup:
- Switch to
coder-convex skill for everyday development
- Define your schema in
convex/schema.ts (in applicationTables)
- Write queries and mutations in
convex/*.ts files
- Integrate with React using
convex/react hooks
- Deploy functions with
[package-manager] run deploy:functions]
Common Setup Patterns
Pattern 1: Minimal Setup with Auth
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:
[package-manager] add convex @convex-dev/auth
[package-manager] add -D @types/node typescript
mkdir -p convex lib scripts
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
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
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
[package-manager] run convex:start
bash scripts/init-convex-env.sh
[package-manager] run deploy:functions
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 (
convexAuth() in 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 coder-convex skill instead.
Working Example Reference
For a complete, working implementation of self-hosted Convex in a Coder workspace, you can reference:
jovermier/convex-ai-chat
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
| Aspect | Standard Convex | Coder Convex |
|---|
| Deployment URL | *.convex.cloud | Custom Coder proxy URL |
| Environment Variables | CONVEX_DEPLOYMENT | CONVEX_CLOUD_ORIGIN, CONVEX_SITE_ORIGIN |
| Auth Configuration | Uses Convex Cloud | Uses convexAuth() with providers, CONVEX_SITE_ORIGIN (site proxy, port 3211) |
| Site Proxy Port | Not applicable | 3211 |
| Dashboard | Web dashboard at convex.dev | Local at localhost:6791 |
| Setup Script | Guided in dashboard | Custom setup-convex.sh script |