Back to list
cameronapak

bknd-production-config

by cameronapak

A no-build, un-bloated stack built upon Web Standards that feels freeing to use and can be deployed anywhere.

23🍴 2📅 Jan 21, 2026

SKILL.md


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:

// bknd.config.ts
export default {
  app: (env) => ({
    connection: { url: env.DB_URL },
    isProduction: true,  // or env.NODE_ENV === "production"
  }),
};

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,  // 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 httpOnly: true set
  • Cookie secure: true in production (HTTPS)
  • Cookie sameSite: "strict" or "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 implicit_allow unless intended
  • Sensitive entities have explicit permissions
  • Row-level security filters user-owned data

Data

  • isProduction: true set
  • 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:

  1. Admin panel is read-only (no schema changes)
  2. API errors don't expose stack traces
  3. Auth requires valid JWT
  4. 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 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

  • 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

Score

Total Score

75/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

+10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon