Claude-skill-registry bknd-production-config
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.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/bknd-production-config" ~/.claude/skills/majiayu000-claude-skill-registry-bknd-production-config && rm -rf "$T"
skills/data/bknd-production-config/SKILL.mdConfigure 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:
// bknd.config.ts export default { app: (env) => ({ connection: { url: env.DB_URL }, isProduction: true, // or env.NODE_ENV === "production" }), };
What
does:isProduction: true
- 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, // Required, min 32 chars alg: "HS256", // Or "HS384", "HS512" expires: "7d", // Token lifetime issuer: "my-app", // Optional, identifies token source fields: ["id", "email", "role"], // Claims in token }, cookie: { httpOnly: true, // Prevent XSS access secure: true, // HTTPS only sameSite: "strict", // CSRF protection expires: 604800, // 7 days in seconds }, }, }), };
Generate secure secret:
# Node.js node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" # OpenSSL openssl rand -hex 32
Step 3: Enable Guard (Authorization)
export default { app: (env) => ({ connection: { url: env.DB_URL }, isProduction: true, config: { guard: { enabled: true, // Enforce all permissions }, }, }), };
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, // Allow cookies methods: ["GET", "POST", "PUT", "PATCH", "DELETE"], }, }, }, }), };
Step 5: Configure Media Storage
Never use local storage in production serverless. Use cloud providers:
// AWS S3 export default { app: (env) => ({ // ... config: { media: { enabled: true, body_max_size: 10 * 1024 * 1024, // 10MB max upload adapter: { type: "s3", config: { bucket: env.S3_BUCKET, region: env.S3_REGION, accessKeyId: env.S3_ACCESS_KEY, secretAccessKey: env.S3_SECRET_KEY, }, }, }, }, }), }; // Cloudflare R2 config: { media: { adapter: { type: "r2", config: { bucket: env.R2_BUCKET }, }, }, } // Cloudinary config: { media: { adapter: { type: "cloudinary", config: { cloudName: env.CLOUDINARY_CLOUD, apiKey: env.CLOUDINARY_KEY, apiSecret: env.CLOUDINARY_SECRET, }, }, }, }
Complete Production Configuration
// bknd.config.ts 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), // posts.author_id -> users }) ); type Database = (typeof schema)["DB"]; declare module "bknd" { interface DB extends Database {} } export default { app: (env) => ({ // Database connection: { url: env.DB_URL, authToken: env.DB_TOKEN, }, // Schema schema, // Production mode isProduction: env.NODE_ENV === "production", // Authentication 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", }, // Authorization config: { guard: { enabled: true, }, roles: { admin: { implicit_allow: true, // Full access }, 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, // Unauthenticated users permissions: [ { permission: "data.posts.read", effect: "filter", condition: { published: "published" }, }, ], }, }, // Media storage 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, }, }, }, // CORS server: { cors: { origin: env.ALLOWED_ORIGINS?.split(",") ?? [], credentials: true, }, }, }, }), } satisfies CliBkndConfig;
Environment Variables Template
Create
.env.production or set in your platform:
# Required 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 # CORS ALLOWED_ORIGINS=https://myapp.com,https://www.myapp.com # Media Storage (S3) S3_BUCKET=my-bucket S3_REGION=us-east-1 S3_ACCESS_KEY=AKIA... S3_SECRET_KEY=secret... # Or Cloudinary CLOUDINARY_CLOUD=my-cloud CLOUDINARY_KEY=123456 CLOUDINARY_SECRET=secret # OAuth (if used) GOOGLE_CLIENT_ID=... GOOGLE_CLIENT_SECRET=... GITHUB_CLIENT_ID=... GITHUB_CLIENT_SECRET=...
Security Checklist
Authentication
- JWT secret is 32+ characters, randomly generated
- JWT secret stored in environment variable, not code
- Cookie
sethttpOnly: true - Cookie
in production (HTTPS)secure: true - Cookie
orsameSite: "strict""lax" - Password hashing uses bcrypt with rounds >= 10
- Minimum password length enforced (8+ chars)
Authorization
- Guard enabled (
)guard.enabled: true - Default role defined for anonymous users
- Admin role does NOT use
unless intendedimplicit_allow - Sensitive entities have explicit permissions
- Row-level security filters user-owned data
Data
-
setisProduction: true - Database credentials in environment variables
- No test/seed data in production
- Backups configured for database
Media
- Cloud storage configured (not local filesystem)
- Storage credentials in environment variables
- CORS configured on storage bucket
- Max upload size limited (
)body_max_size
Network
- CORS origins explicitly listed (no wildcard
)* - HTTPS enforced (via platform/proxy)
- API rate limiting configured (if needed)
Platform-Specific Security
Cloudflare Workers
// Secrets set via wrangler // wrangler secret put JWT_SECRET // wrangler secret put DB_TOKEN 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
# Set via Vercel CLI or dashboard vercel env add JWT_SECRET production vercel env add DB_URL production vercel env add DB_TOKEN production
Docker
# docker-compose.yml services: bknd: environment: - NODE_ENV=production - JWT_SECRET=${JWT_SECRET} # From .env or host # Never put secrets directly in docker-compose.yml
Testing Production Config Locally
Test with production-like settings before deploying:
# Create .env.production.local (gitignored) NODE_ENV=production DB_URL=libsql://test-db.turso.io DB_TOKEN=test-token JWT_SECRET=test-secret-min-32-characters-here # Run with production env NODE_ENV=production bun run index.ts # Or source the file 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:
# Check env is loaded echo $JWT_SECRET # Cloudflare: set secret wrangler secret put JWT_SECRET # Docker: pass env 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, // Must be true! }, }
Cookies Not Set (CORS Issues)
Problem: Auth works in Postman but not browser
Fix:
auth: { cookie: { sameSite: "lax", // "strict" may block OAuth redirects secure: true, }, }, config: { server: { cors: { origin: ["https://your-frontend.com"], // Explicit, not "*" credentials: true, }, }, }
Admin Panel Allows Changes
Problem: Schema can be modified in production
Fix: Set
isProduction: true:
isProduction: true, // Locks admin to read-only
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
in productionisProduction: true - 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
andsecure: truehttpOnly: 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
password hashing (usesha256
)bcrypt - Set
on non-admin rolesimplicit_allow: true
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