| name | bknd-production-config |
| description | Use when preparing a Bknd application for production deployment. Covers security hardening, environment configuration, isProduction flag, JWT settings, Guard enablement, CORS, media storage, and production checklist. |
Configure for Production
Prepare and secure your Bknd application for production deployment.
Prerequisites
- Working Bknd application tested locally
- Database provisioned (see
bknd-database-provision)
- Hosting platform selected (see
bknd-deploy-hosting)
When to Use UI Mode
- Viewing current configuration in admin panel
- Verifying Guard settings are active
- Checking auth configuration
When to Use Code Mode
- All production configuration changes
- Setting environment variables
- Configuring security settings
- Setting up adapters
Code Approach
Step 1: Enable Production Mode
Set isProduction: true to disable development features:
export default {
app: (env) => ({
connection: { url: env.DB_URL },
isProduction: true,
}),
};
What isProduction: true does:
- Disables schema auto-sync (prevents accidental migrations)
- Hides detailed error messages from API responses
- Disables admin panel modifications (read-only)
- Enables stricter security defaults
Step 2: Configure JWT Authentication
Critical: Never use default or weak JWT secrets in production.
export default {
app: (env) => ({
connection: { url: env.DB_URL },
isProduction: true,
auth: {
jwt: {
secret: env.JWT_SECRET,
alg: "HS256",
expires: "7d",
issuer: "my-app",
fields: ["id", "email", "role"],
},
cookie: {
httpOnly: true,
secure: true,
sameSite: "strict",
expires: 604800,
},
},
}),
};
Generate secure secret:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
openssl rand -hex 32
Step 3: Enable Guard (Authorization)
export default {
app: (env) => ({
connection: { url: env.DB_URL },
isProduction: true,
config: {
guard: {
enabled: true,
},
},
}),
};
Without Guard enabled, all authenticated users have full access.
Step 4: Configure CORS
export default {
app: (env) => ({
config: {
server: {
cors: {
origin: env.ALLOWED_ORIGINS?.split(",") ?? ["https://myapp.com"],
credentials: true,
methods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
},
},
},
}),
};
Step 5: Configure Media Storage
Never use local storage in production serverless. Use cloud providers:
export default {
app: (env) => ({
config: {
media: {
enabled: true,
body_max_size: 10 * 1024 * 1024,
adapter: {
type: "s3",
config: {
bucket: env.S3_BUCKET,
region: env.S3_REGION,
accessKeyId: env.S3_ACCESS_KEY,
secretAccessKey: env.S3_SECRET_KEY,
},
},
},
},
}),
};
config: {
media: {
adapter: {
type: "r2",
config: { bucket: env.R2_BUCKET },
},
},
}
config: {
media: {
adapter: {
type: "cloudinary",
config: {
cloudName: env.CLOUDINARY_CLOUD,
apiKey: env.CLOUDINARY_KEY,
apiSecret: env.CLOUDINARY_SECRET,
},
},
},
}
Complete Production Configuration
import type { CliBkndConfig } from "bknd";
import { em, entity, text, relation, enumm } from "bknd";
const schema = em(
{
users: entity("users", {
email: text().required().unique(),
name: text(),
role: enumm(["admin", "user"]).default("user"),
}),
posts: entity("posts", {
title: text().required(),
content: text(),
published: enumm(["draft", "published"]).default("draft"),
}),
},
({ users, posts }) => ({
post_author: relation(posts, users),
})
);
type Database = (typeof schema)["DB"];
declare module "bknd" {
interface DB extends Database {}
}
export default {
app: (env) => ({
connection: {
url: env.DB_URL,
authToken: env.DB_TOKEN,
},
schema,
isProduction: env.NODE_ENV === "production",
auth: {
enabled: true,
jwt: {
secret: env.JWT_SECRET,
alg: "HS256",
expires: "7d",
fields: ["id", "email", "role"],
},
cookie: {
httpOnly: true,
secure: env.NODE_ENV === "production",
sameSite: "strict",
expires: 604800,
},
strategies: {
password: {
enabled: true,
hashing: "bcrypt",
rounds: 12,
minLength: 8,
},
},
allow_register: true,
default_role_register: "user",
},
config: {
guard: {
enabled: true,
},
roles: {
admin: {
implicit_allow: true,
},
user: {
implicit_allow: false,
permissions: [
"data.posts.read",
{
permission: "data.posts.create",
effect: "allow",
},
{
permission: "data.posts.update",
effect: "filter",
condition: { author_id: "@user.id" },
},
{
permission: "data.posts.delete",
effect: "filter",
condition: { author_id: "@user.id" },
},
],
},
anonymous: {
implicit_allow: false,
is_default: true,
permissions: [
{
permission: "data.posts.read",
effect: "filter",
condition: { published: "published" },
},
],
},
},
media: {
enabled: true,
body_max_size: 10 * 1024 * 1024,
adapter: {
type: "s3",
config: {
bucket: env.S3_BUCKET,
region: env.S3_REGION,
accessKeyId: env.S3_ACCESS_KEY,
secretAccessKey: env.S3_SECRET_KEY,
},
},
},
server: {
cors: {
origin: env.ALLOWED_ORIGINS?.split(",") ?? [],
credentials: true,
},
},
},
}),
} satisfies CliBkndConfig;
Environment Variables Template
Create .env.production or set in your platform:
NODE_ENV=production
DB_URL=libsql://your-db.turso.io
DB_TOKEN=your-turso-token
JWT_SECRET=your-64-char-random-secret-here-generate-with-openssl
ALLOWED_ORIGINS=https://myapp.com,https://www.myapp.com
S3_BUCKET=my-bucket
S3_REGION=us-east-1
S3_ACCESS_KEY=AKIA...
S3_SECRET_KEY=secret...
CLOUDINARY_CLOUD=my-cloud
CLOUDINARY_KEY=123456
CLOUDINARY_SECRET=secret
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...
Security Checklist
Authentication
Authorization
Data
Media
Network
Platform-Specific Security
Cloudflare Workers
export default hybrid<CloudflareBkndConfig>({
app: (env) => ({
connection: d1Sqlite({ binding: env.DB }),
isProduction: true,
auth: {
jwt: { secret: env.JWT_SECRET },
cookie: {
httpOnly: true,
secure: true,
sameSite: "strict",
},
},
}),
});
Vercel
vercel env add JWT_SECRET production
vercel env add DB_URL production
vercel env add DB_TOKEN production
Docker
services:
bknd:
environment:
- NODE_ENV=production
- JWT_SECRET=${JWT_SECRET}
Testing Production Config Locally
Test with production-like settings before deploying:
NODE_ENV=production
DB_URL=libsql://test-db.turso.io
DB_TOKEN=test-token
JWT_SECRET=test-secret-min-32-characters-here
NODE_ENV=production bun run index.ts
source .env.production.local && bun run index.ts
Verify:
- Admin panel is read-only (no schema changes)
- API errors don't expose stack traces
- Auth requires valid JWT
- Guard enforces permissions
Common Pitfalls
"JWT_SECRET required" Error
Problem: Auth fails at startup
Fix: Ensure JWT_SECRET is set and accessible:
echo $JWT_SECRET
wrangler secret put JWT_SECRET
docker run -e JWT_SECRET="your-secret" ...
Guard Not Enforcing Permissions
Problem: Users can access everything
Fix: Ensure Guard is enabled:
config: {
guard: {
enabled: true,
},
}
Cookies Not Set (CORS Issues)
Problem: Auth works in Postman but not browser
Fix:
auth: {
cookie: {
sameSite: "lax",
secure: true,
},
},
config: {
server: {
cors: {
origin: ["https://your-frontend.com"],
credentials: true,
},
},
}
Admin Panel Allows Changes
Problem: Schema can be modified in production
Fix: Set isProduction: true:
isProduction: true,
Detailed Errors Exposed
Problem: API returns stack traces
Fix: isProduction: true hides internal errors. Also check for custom error handlers exposing details.
DOs and DON'Ts
DO:
- Set
isProduction: true in production
- Generate cryptographically secure JWT secrets (32+ chars)
- Enable Guard for authorization
- Use cloud storage for media
- Set explicit CORS origins
- Use environment variables for all secrets
- Test production config locally first
- Enable HTTPS (via platform/proxy)
- Set cookie
secure: true and httpOnly: true
DON'T:
- Use default or weak JWT secrets
- Commit secrets to version control
- Use wildcard (
*) CORS origins
- Leave Guard disabled in production
- Use local filesystem storage in serverless
- Expose detailed error messages
- Skip the security checklist
- Use
sha256 password hashing (use bcrypt)
- Set
implicit_allow: true on non-admin roles
Related Skills
- bknd-deploy-hosting - Deploy to hosting platforms
- bknd-database-provision - Set up production database
- bknd-env-config - Environment variable setup
- bknd-setup-auth - Authentication configuration
- bknd-create-role - Define authorization roles
- bknd-storage-config - Media storage setup