A self-hosted Convex Backend-as-a-Service boilerplate running in Docker. Unlike traditional databases where you write SQL queries or use ORMs, Convex combines your database and API layer into one unified system where your API endpoints are defined as functions inside the database itself.
Perfect for developers familiar with SQL/NoSQL databases who want to modernize their stack with real-time capabilities, type safety, and serverless-style functions.
Traditional Stack:
Next.js App β API Routes β ORM/Query Builder β Database
Convex Stack:
Next.js App β Generated Client β Convex Functions (Your API + Database)
- No separate API layer: Your database functions ARE your API endpoints
- Real-time by default: All queries automatically subscribe to changes
- Type-safe: Full TypeScript support from database to frontend
- Serverless functions: Write backend logic as simple TypeScript functions
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Docker Convex Backend-as-a-Service β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Next.js Container β Convex Container β
β βββββββββββββββββββββββ β βββββββββββββββββββββββββββ β
β β Your Frontend ββββββΆβ β convex/example.ts β β
β β - useQuery() β β β βββββββββββββββββββββββ β β
β β - useMutation() β β β β export const β β β
β β - Real-time updates β β β β listItems = query() β β β
β β - Type safety β β β β addItem = mutation()β β β
β βββββββββββββββββββββββ β β β deleteItem = ... β β β
β β β β βββββββββββββββββββββββ β β
β βΌ β βββββββββββββββββββββββββββ β
β βββββββββββββββββββββββ β β β
β β Generated Client βββββββΌββββββββββββ β
β β - api.example.* β β βββββββββββββββββββββββββββ β
β β - Type definitions β β β Docker Backend β β
β β - Real-time hooks β β β Port 3210 (API) β β
β βββββββββββββββββββββββ β β Port 6791 (Dashboard) β β
β β βββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
pnpm run self-hosted:setup
β
βΌ
βββββββββββββββββββββββ
β docker:up β βββΆ Start containers (backend + dashboard)
βββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββ
β Wait 10 seconds β βββΆ Let backend initialize
βββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββ
β generate-admin-key β βββΆ Create admin credentials
βββββββββββββββββββββββ
pnpm run deploy-functions
β
βΌ
βββββββββββββββββββββββ
β convex dev --once β βββΆ Deploy functions to self-hosted instance
βββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββ
β Analyze convex/ β βββΆ Scan *.ts files (example.ts, etc.)
βββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββ
β Generate _generated β βββΆ Create API bindings & types
βββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββ
β Push to backend β βββΆ Upload functions to Docker instance
βββββββββββββββββββββββ
- Prerequisites
- Setup Guide
- Database Management Commands
- Database Access
- Troubleshooting
- Generated Files Explained
- Docker for database containerization
- Node.js (v18+) for running management tools
- pnpm (v8+) for package management
Get your Convex database running in one command:
pnpm run self-hosted:setup
This will:
- Start the Docker containers
- Generate admin credentials
- Display the deployment URL, dashboard URL, and admin key
- Save the admin key to a timestamped file in
./admin-key/
Then deploy your functions:
pnpm run deploy-functions
Create a docker-compose.yml
in your main project:
version: '3.8'
services:
# Your Next.js frontend
frontend:
build: ./frontend
ports:
- "3000:3000"
environment:
- NEXT_PUBLIC_CONVEX_URL=http://convex-backend:3210
depends_on:
- convex-backend
networks:
- app-network
# This Convex backend
convex-backend:
build: ./docker-convex # Path to this boilerplate
ports:
- "3210:3210" # API
- "6791:6791" # Dashboard
volumes:
- convex-data:/app/convex-data
networks:
- app-network
volumes:
convex-data:
networks:
app-network:
driver: bridge
In your turbo.json
:
{
"pipeline": {
"dev": {
"dependsOn": ["^build"],
"cache": false,
"persistent": true
},
"convex:dev": {
"cache": false,
"persistent": true
}
}
}
-
Install Convex in your Next.js project:
npm install convex
-
Configure environment variables:
# .env.local NEXT_PUBLIC_CONVEX_URL=http://localhost:3210 # For production: NEXT_PUBLIC_CONVEX_URL=http://convex-backend:3210
-
Setup Convex provider in your app:
// app/layout.tsx or pages/_app.tsx import { ConvexProvider, ConvexReactClient } from "convex/react"; const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!); export default function RootLayout({ children }) { return ( <ConvexProvider client={convex}> {children} </ConvexProvider> ); }
-
Use in your components:
// components/ItemList.tsx import { useQuery, useMutation } from "convex/react"; import { api } from "../convex/_generated/api"; export function ItemList() { const items = useQuery(api.example.listItems); const addItem = useMutation(api.example.addItem); const deleteItem = useMutation(api.example.deleteItem); return ( <div> {items?.map(item => ( <div key={item._id}> {item.name} <button onClick={() => deleteItem({ id: item._id })}> Delete </button> </div> ))} <button onClick={() => addItem({ name: "New Item" })}> Add Item </button> </div> ); }
This project runs a Convex database instance in Docker containers, providing a complete database environment with persistence and administration capabilities.
For custom configuration, set up the environment files:
cp .env.docker.example .env.docker
Configure Database Settings in .env.docker
:
- Set a secure
INSTANCE_SECRET
for database encryption - Configure database ports if needed (defaults: 3210, 3211, 6791)
- Optionally configure storage paths and memory limits
Start everything with a single command:
pnpm run self-hosted:setup
This command will:
- Start Docker containers
- Wait for the backend to be ready
- Generate and display admin credentials
- Save credentials to
./admin-key/admin_key_[timestamp].md
Start the database server:
pnpm run docker:up
Stop the database server:
pnpm run docker:down
Deploy schema changes:
pnpm run deploy-functions
For manual control over the database setup:
-
Start database and create admin credentials:
pnpm run self-hosted:setup-manual
-
Configure admin access in
.devcontainer/.env.local
:CONVEX_SELF_HOSTED_ADMIN_KEY=<generated-admin-key>
-
Initialize database schema:
pnpm run deploy-functions
pnpm run self-hosted:setup
- One-command setup: Start containers and generate admin keypnpm run deploy-functions
- Deploy schema changes
pnpm run docker:up
- Start the database serverpnpm run docker:down
- Stop the database serverpnpm run docker:logs
- View database logs
pnpm run docker:generate-admin-key
- Generate new admin credentialspnpm run self-hosted:setup-manual
- Manual database initializationpnpm run self-hosted:reset
- Stop services and perform full Docker reset
pnpm run docker:cleanup-admin-keys
- Remove saved admin key filespnpm run docker:reset-images
- Stop Docker and prune system volumespnpm run docker:reset-full
- Combine key cleanup and image reset
All commands use
pnpm
. Ensure it's installed vianpm install -g pnpm
if needed.
- Keep admin credentials secure and rotate them regularly
- Use environment variables for sensitive configuration
- Never commit
.env
files containing credentials - Restrict database ports to localhost when possible
- Configure firewall rules to restrict database access
- Use TLS/SSL for external connections
- Monitor access logs for suspicious activity
- Keep Docker and database software updated
- Implement regular backup procedures
- Encrypt sensitive data at rest
- Follow the principle of least privilege
- Document all security configurations
- Database API: http://localhost:3210
- Admin Dashboard: http://localhost:6791
The Convex Admin Dashboard at http://localhost:6791 provides a web interface for:
- Monitoring database health
- Managing data and schemas
- Viewing logs and metrics
- Running queries
To access the admin dashboard:
-
Quick Setup (Recommended):
pnpm run self-hosted:setup
This will display the deployment URL, dashboard URL, and admin key.
-
Or generate credentials separately:
pnpm run docker:generate-admin-key
-
Use the generated key for dashboard login - Format:
instance-name|admin-key-hash
Example:
convex-tutorial-local|01f7a735340227d2769630a37069656211287635ea7cc4737e98d517cbda803723deccea7b4b848d1d797975102c2c7328
-
Credential Storage: The admin key is automatically saved in
.devcontainer/.env.local
asCONVEX_SELF_HOSTED_ADMIN_KEY
Security Note: The admin key grants full database access. Store it securely and never share it.
If unable to connect to the database:
- Verify Docker container status:
docker ps
- Check database logs:
pnpm run docker:logs
- Ensure ports 3210 and 6791 are available
- Validate connection settings in
.devcontainer/.env.local
If dashboard authentication fails:
- Generate new credentials:
pnpm run docker:generate-admin-key
- Verify admin key in
.devcontainer/.env.local
- Use complete key format:
instance-name|admin-key-hash
If schema changes aren't reflecting:
- Execute
pnpm run deploy-functions
- Check deployment logs for errors
- Verify schema file syntax
If data isn't persisting between restarts:
- Check Docker volume configuration
- Verify database shutdown was clean
- Review backup settings if configured
# Monitor database status
docker ps --filter "name=convex"
# View detailed database logs
docker compose logs convex-backend
# Inspect database volumes
docker volume ls --filter "name=convex"
# Create full database backup
docker run --rm --volumes-from convex-backend -v $(pwd):/backup alpine tar cvf /backup/convex-data.tar /data
# Export database schema
pnpm run deploy-functions -- --dry-run > schema-backup.json
# Restore from backup
docker run --rm --volumes-from convex-backend -v $(pwd):/backup alpine tar xvf /backup/convex-data.tar
# Reset database (β οΈ Warning: Deletes all data!)
docker compose down -v && docker volume prune -f
# Rebuild database container
docker compose build convex-backend
# Check database health
curl http://localhost:3210/_system/health
# View database metrics
curl http://localhost:3210/_system/metrics
- Always change the
INSTANCE_SECRET
in.env.docker
before deploying to any non-local environment! - Keep your admin key secure - it provides full access to your Convex backend
- Never commit environment files containing secrets to version control
The following ports are automatically forwarded:
- 5173: Vite development server (your React app)
- 3210: Convex backend server
- 6791: Convex dashboard
Once the development server is running:
- Click on the "Ports" tab in VS Code
- Click the globe icon next to port 5173 to open your app
- Access the Convex dashboard on port 6791 (use the admin key as password)
The files in convex/_generated/
are automatically created by Convex and are essential for your application:
- Purpose: Provide typed API references for your frontend
- Contains:
api.example.listItems
,api.example.addItem
,api.example.deleteItem
references - Used by: Your frontend app imports these to call your backend functions
- Auto-regenerated: Every time you run
convex dev
ordeploy-functions
- Purpose: Provide server-side utilities (
mutation
,query
,action
) - Contains: TypeScript definitions for Convex function builders
- Used by:
convex/example.ts
importsmutation
andquery
from here - Auto-regenerated: When Convex analyzes your schema and functions
- Purpose: TypeScript definitions for your database schema
- Contains: Table definitions, document types, and ID types
- Note: Currently permissive (
Doc = any
) because no schema.ts exists - Auto-regenerated: When you add a
convex/schema.ts
file
convex/example.ts
- Example functions (customize for your needs)ADMIN_KEY_WORKFLOW.md
- Admin key management documentationdocker-build/
- Docker build scripts and utilitiesdocker-compose.yml
- Container orchestration- All files in
convex/_generated/
- Auto-generated API bindings
Replace the example functions in convex/example.ts
with your own:
// convex/yourFunctions.ts
import { mutation, query } from "./_generated/server";
import { v } from "convex/values";
export const yourQuery = query({
args: { /* your args */ },
handler: async (ctx, args) => {
// Your query logic
},
});
export const yourMutation = mutation({
args: { /* your args */ },
handler: async (ctx, args) => {
// Your mutation logic
},
});
After adding functions, run pnpm run deploy-functions
to update the generated API.
π Rapid Prototyping
- Skip API development entirely - write database functions directly
- Real-time features out of the box (live updates, collaborative editing)
- Type-safe from database to frontend without manual API contracts
π± Modern Web Applications
- Chat applications: Real-time messaging with automatic subscriptions
- Collaborative tools: Live document editing, shared whiteboards
- Dashboards: Real-time analytics and monitoring interfaces
- Social features: Live comments, reactions, notifications
π’ Enterprise Applications
- Admin panels: CRUD operations with real-time updates
- Workflow management: Task tracking with live status updates
- Team collaboration: Project management with real-time sync
- Customer support: Live chat and ticket management
π§ Developer Experience
- No API boilerplate: Functions are your API endpoints
- Automatic optimizations: Query batching, caching, subscriptions
- Built-in auth: User management and permissions
- File storage: Handle uploads and file management
- Full-text search: Built-in search capabilities
Traditional Stack | Convex BaaS |
---|---|
Write API routes manually | Functions auto-generate API |
Set up WebSockets for real-time | Real-time by default |
Manage database migrations | Schema evolution built-in |
Handle caching manually | Automatic query optimization |
Write separate validation | Type-safe end-to-end |
Set up auth from scratch | Built-in authentication |
Configure file uploads | Integrated file storage |
- Heavy computational workloads: Use dedicated compute services
- Complex SQL requirements: Stick with PostgreSQL/MySQL
- Existing large codebases: Migration might be complex
- Specific database features: If you need Redis, Elasticsearch, etc.