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 | 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
bash
1npm install -D wrangler
Step 2: Create wrangler.toml
toml
1name = "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
typescript
1// 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
bash
1# Create database
2wrangler d1 create my-database
3
4# Copy the database_id to wrangler.toml
Step 5: Set Secrets
bash
1wrangler secret put JWT_SECRET
2# Enter your secret (min 32 chars)
Step 6: Deploy
Cloudflare Pages (with Functions)
Step 1: Create functions/api/[[bknd]].ts
typescript
1import { 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
typescript
1// 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
bash
1export 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
bash
1# 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
dockerfile
1FROM 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
yaml
1version: "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
bash
1# Build and run
2docker compose up -d
3
4# View logs
5docker compose logs -f bknd
Vercel (Next.js)
Step 1: Create API Route
typescript
1// app/api/bknd/[[...bknd]]/route.ts
2export { GET, POST, PUT, DELETE, PATCH } from "bknd/adapter/nextjs";
Step 2: Create bknd.config.ts
typescript
1import 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:
bash
1vercel env add DB_URL
2vercel env add DB_TOKEN
3vercel env add JWT_SECRET
Step 4: Deploy
bash
1vercel deploy --prod
AWS Lambda
Step 1: Install Dependencies
bash
1npm install -D serverless serverless-esbuild
Step 2: Create handler.ts
typescript
1import { 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
yaml
1service: 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
bash
1serverless deploy --stage prod
Pre-Deployment Checklist
bash
1# 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
| 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:
typescript
1connection: {
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:
bash
1# 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:
toml
1[[d1_databases]]
2binding = "DB" # Must match env.DB in code
3database_name = "my-database"
4database_id = "actual-id-from-wrangler-d1-create"
Problem: Local storage doesn't work in serverless
Fix: Use cloud storage adapter:
typescript
1config: {
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:
typescript
1// Most adapters handle this automatically
2// For custom needs, check platform docs
Deployment Commands Reference
bash
1# 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: true in 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
.env files with real secrets
- 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)