Deploy to Hosting
Deploy your Bknd application to various hosting platforms.
Prerequisites
- Working Bknd application locally
- Schema defined and tested
- Database provisioned (see
bknd-database-provision) - Environment variables prepared (see
bknd-env-config)
When to Use UI Mode
- Cloudflare/Vercel dashboards for environment variables
- Platform-specific deployment settings
- Viewing deployment logs
When to Use Code Mode
- All deployment configuration and commands
- Adapter setup for target platform
- CI/CD pipeline configuration
Platform Selection Guide
| Platform | Best For | Database Options | Cold Start |
|---|---|---|---|
| Cloudflare Workers | Edge, global low-latency | D1, Turso | ~0ms |
| Cloudflare Pages | Static + API | D1, Turso | ~0ms |
| Vercel | Next.js apps | Turso, Neon | ~200ms |
| Node.js/Bun VPS | Full control, dedicated | Any | N/A |
| Docker | Containerized, portable | Any | N/A |
| AWS Lambda | Serverless, pay-per-use | Turso, RDS | ~500ms |
Code Approach
Cloudflare Workers
Step 1: Install Wrangler
bash1npm install -D wrangler
Step 2: Create wrangler.toml
toml1name = "my-bknd-app" 2main = "src/index.ts" 3compatibility_date = "2024-01-01" 4 5[[d1_databases]] 6binding = "DB" 7database_name = "my-database" 8database_id = "your-d1-database-id" 9 10# Optional: R2 for media storage 11[[r2_buckets]] 12binding = "R2_BUCKET" 13bucket_name = "my-bucket" 14 15[vars] 16ENVIRONMENT = "production"
Step 3: Configure Adapter
typescript1// src/index.ts 2import { hybrid, type CloudflareBkndConfig } from "bknd/adapter/cloudflare"; 3import { d1Sqlite } from "bknd/adapter/cloudflare"; 4import { em, entity, text } from "bknd"; 5 6const schema = em({ 7 posts: entity("posts", { 8 title: text().required(), 9 }), 10}); 11 12export default hybrid<CloudflareBkndConfig>({ 13 app: (env) => ({ 14 connection: d1Sqlite({ binding: env.DB }), 15 schema, 16 isProduction: true, 17 auth: { 18 jwt: { 19 secret: env.JWT_SECRET, 20 }, 21 }, 22 config: { 23 media: { 24 enabled: true, 25 adapter: { 26 type: "r2", 27 config: { bucket: env.R2_BUCKET }, 28 }, 29 }, 30 }, 31 }), 32});
Step 4: Create D1 Database
bash1# Create database 2wrangler d1 create my-database 3 4# Copy the database_id to wrangler.toml
Step 5: Set Secrets
bash1wrangler secret put JWT_SECRET 2# Enter your secret (min 32 chars)
Step 6: Deploy
bash1wrangler deploy
Cloudflare Pages (with Functions)
Step 1: Create functions/api/[[bknd]].ts
typescript1import { hybrid, type CloudflareBkndConfig } from "bknd/adapter/cloudflare"; 2import { d1Sqlite } from "bknd/adapter/cloudflare"; 3import schema from "../../bknd.config"; 4 5export const onRequest = hybrid<CloudflareBkndConfig>({ 6 app: (env) => ({ 7 connection: d1Sqlite({ binding: env.DB }), 8 schema, 9 isProduction: true, 10 auth: { 11 jwt: { secret: env.JWT_SECRET }, 12 }, 13 }), 14});
Step 2: Configure Pages
In Cloudflare dashboard:
- Connect your git repository
- Set build command (if any)
- Add D1 binding under Settings > Functions > D1 Database Bindings
- Add environment variables under Settings > Environment Variables
Node.js / Bun (VPS)
Step 1: Create Production Entry
typescript1// index.ts 2import { serve, type BunBkndConfig } from "bknd/adapter/bun"; 3// or for Node.js: 4// import { serve } from "bknd/adapter/node"; 5 6const config: BunBkndConfig = { 7 connection: { 8 url: process.env.DB_URL!, 9 authToken: process.env.DB_TOKEN, 10 }, 11 isProduction: true, 12 auth: { 13 jwt: { 14 secret: process.env.JWT_SECRET!, 15 expires: "7d", 16 }, 17 }, 18 config: { 19 media: { 20 enabled: true, 21 adapter: { 22 type: "s3", 23 config: { 24 bucket: process.env.S3_BUCKET!, 25 region: process.env.S3_REGION!, 26 accessKeyId: process.env.S3_ACCESS_KEY!, 27 secretAccessKey: process.env.S3_SECRET_KEY!, 28 }, 29 }, 30 }, 31 guard: { 32 enabled: true, 33 }, 34 }, 35}; 36 37serve(config);
Step 2: Set Environment Variables
bash1export DB_URL="libsql://your-db.turso.io" 2export DB_TOKEN="your-turso-token" 3export JWT_SECRET="your-32-char-minimum-secret" 4export PORT=3000
Step 3: Run with Process Manager
bash1# Using PM2 2npm install -g pm2 3pm2 start "bun run index.ts" --name bknd-app 4 5# Or systemd (create /etc/systemd/system/bknd.service)
Docker
Step 1: Create Dockerfile
dockerfile1FROM oven/bun:1.0-alpine 2 3WORKDIR /app 4 5COPY package.json bun.lockb ./ 6RUN bun install --frozen-lockfile --production 7 8COPY . . 9 10# Create data directory for SQLite (if using file-based) 11RUN mkdir -p /app/data 12 13ENV PORT=3000 14 15EXPOSE 3000 16 17CMD ["bun", "run", "index.ts"]
Step 2: Create docker-compose.yml
yaml1version: "3.8" 2services: 3 bknd: 4 build: . 5 ports: 6 - "3000:3000" 7 volumes: 8 - bknd-data:/app/data 9 environment: 10 - DB_URL=file:/app/data/bknd.db 11 - JWT_SECRET=${JWT_SECRET} 12 - NODE_ENV=production 13 restart: unless-stopped 14 15volumes: 16 bknd-data:
Step 3: Deploy
bash1# Build and run 2docker compose up -d 3 4# View logs 5docker compose logs -f bknd
Vercel (Next.js)
Step 1: Create API Route
typescript1// app/api/bknd/[[...bknd]]/route.ts 2export { GET, POST, PUT, DELETE, PATCH } from "bknd/adapter/nextjs";
Step 2: Create bknd.config.ts
typescript1import type { NextjsBkndConfig } from "bknd/adapter/nextjs"; 2import { em, entity, text } from "bknd"; 3 4const schema = em({ 5 posts: entity("posts", { 6 title: text().required(), 7 }), 8}); 9 10type Database = (typeof schema)["DB"]; 11declare module "bknd" { 12 interface DB extends Database {} 13} 14 15export default { 16 app: (env) => ({ 17 connection: { 18 url: env.DB_URL, 19 authToken: env.DB_TOKEN, 20 }, 21 schema, 22 isProduction: env.NODE_ENV === "production", 23 auth: { 24 jwt: { secret: env.JWT_SECRET }, 25 }, 26 }), 27} satisfies NextjsBkndConfig;
Step 3: Set Vercel Environment Variables
In Vercel dashboard or CLI:
bash1vercel env add DB_URL 2vercel env add DB_TOKEN 3vercel env add JWT_SECRET
Step 4: Deploy
bash1vercel deploy --prod
AWS Lambda
Step 1: Install Dependencies
bash1npm install -D serverless serverless-esbuild
Step 2: Create handler.ts
typescript1import { createHandler } from "bknd/adapter/aws"; 2 3export const handler = createHandler({ 4 connection: { 5 url: process.env.DB_URL!, 6 authToken: process.env.DB_TOKEN, 7 }, 8 isProduction: true, 9 auth: { 10 jwt: { secret: process.env.JWT_SECRET! }, 11 }, 12});
Step 3: Create serverless.yml
yaml1service: bknd-api 2 3provider: 4 name: aws 5 runtime: nodejs20.x 6 region: us-east-1 7 environment: 8 DB_URL: ${env:DB_URL} 9 DB_TOKEN: ${env:DB_TOKEN} 10 JWT_SECRET: ${env:JWT_SECRET} 11 12plugins: 13 - serverless-esbuild 14 15functions: 16 api: 17 handler: handler.handler 18 events: 19 - http: 20 path: /{proxy+} 21 method: ANY 22 - http: 23 path: / 24 method: ANY
Step 4: Deploy
bash1serverless deploy --stage prod
Pre-Deployment Checklist
bash1# 1. Generate types 2npx bknd types 3 4# 2. Test locally with production-like config 5DB_URL="your-prod-db" JWT_SECRET="your-secret" npx bknd run 6 7# 3. Verify schema sync 8# Schema auto-syncs on first request in production
Environment Variables (All Platforms)
| Variable | Required | Description |
|---|---|---|
DB_URL | Yes | Database connection URL |
DB_TOKEN | Depends | Auth token (Turso/LibSQL) |
JWT_SECRET | Yes | Min 32 chars for security |
PORT | No | Server port (default: 3000) |
Common Pitfalls
"Module not found" for Native SQLite
Problem: better-sqlite3 not available in serverless
Fix: Use LibSQL/Turso instead of file-based SQLite:
typescript1connection: { 2 url: "libsql://your-db.turso.io", 3 authToken: process.env.DB_TOKEN, 4}
"JWT_SECRET required" Error
Problem: Auth fails in production
Fix: Set JWT_SECRET environment variable:
bash1# Cloudflare 2wrangler secret put JWT_SECRET 3 4# Vercel 5vercel env add JWT_SECRET 6 7# Docker 8docker run -e JWT_SECRET="your-secret" ...
Cold Start Timeouts (Lambda)
Problem: First request times out
Fix:
- Use lighter database (Turso over RDS)
- Reduce bundle size
- Enable provisioned concurrency for critical functions
D1 Binding Not Found
Problem: env.DB is undefined
Fix: Check wrangler.toml D1 binding:
toml1[[d1_databases]] 2binding = "DB" # Must match env.DB in code 3database_name = "my-database" 4database_id = "actual-id-from-wrangler-d1-create"
Media Uploads Fail in Serverless
Problem: Local storage doesn't work in serverless
Fix: Use cloud storage adapter:
typescript1config: { 2 media: { 3 adapter: { 4 type: "s3", // or "r2", "cloudinary" 5 config: { /* credentials */ }, 6 }, 7 }, 8}
CORS Errors
Problem: Frontend can't access API
Fix: Configure CORS in your adapter:
typescript1// Most adapters handle this automatically 2// For custom needs, check platform docs
Deployment Commands Reference
bash1# Cloudflare Workers 2wrangler deploy 3wrangler tail # View logs 4 5# Vercel 6vercel deploy --prod 7vercel logs 8 9# Docker 10docker compose up -d 11docker compose logs -f 12 13# AWS Lambda 14serverless deploy --stage prod 15serverless logs -f api
DOs and DON'Ts
DO:
- Set
isProduction: truein production config - Use cloud storage (S3/R2/Cloudinary) for media
- Set strong JWT_SECRET (min 32 chars)
- Enable Guard for authorization
- Test with production database before deploying
- Use environment variables for all secrets
DON'T:
- Use file-based SQLite in serverless
- Hardcode secrets in code
- Deploy without testing schema sync
- Use local storage adapter in production
- Skip JWT_SECRET configuration
- Commit
.envfiles with real secrets
Related Skills
- bknd-database-provision - Set up production database
- bknd-production-config - Production security settings
- bknd-storage-config - Configure media storage
- bknd-env-config - Environment variable setup
- bknd-local-setup - Local development (pre-deploy testing)