Use when configuring environment variables for Bknd projects. Covers .env files, secrets management, env injection in config, platform-specific variables, and production security.
npx claudepluginhub cameronapak/bknd-expert --plugin bknd-research-skillsThis skill uses the workspace's default tool permissions.
Configure environment variables for Bknd applications across development and production.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
Configure environment variables for Bknd applications across development and production.
bknd.config.ts exists).env filesbknd.config.tsCreate .env in project root:
# Database
DB_URL=file:data.db
DB_TOKEN=
# Auth
JWT_SECRET=your-secret-here-min-32-chars
# Server
PORT=3000
# Development
LOCAL=true
Access env vars via the env parameter in bknd.config.ts:
import type { CliBkndConfig } from "bknd";
export default {
app: (env) => ({
connection: {
url: env.DB_URL ?? "file:data.db",
authToken: env.DB_TOKEN,
},
auth: {
jwt: {
secret: env.JWT_SECRET ?? "dev-secret-change-in-prod",
},
},
}),
} satisfies CliBkndConfig;
The env parameter receives all environment variables loaded from .env files and system environment.
Bknd loads env files in order (later takes precedence):
.env - Base configuration.dev.vars - Development-specific overrides (Cloudflare style)Create .dev.vars for local dev overrides:
# .dev.vars - Dev-only, overrides .env
DB_URL=:memory:
JWT_SECRET=dev-only-secret
| Variable | Description | Example |
|---|---|---|
DB_URL | Database connection URL | file:data.db, libsql://db.turso.io |
DB_TOKEN | LibSQL/Turso auth token | eyJhbGciOiJFZERTQSIs... |
| Variable | Description | Example |
|---|---|---|
JWT_SECRET | JWT signing secret (min 32 chars) | your-very-long-secret-key-here |
GOOGLE_CLIENT_ID | Google OAuth client ID | 123456.apps.googleusercontent.com |
GOOGLE_CLIENT_SECRET | Google OAuth secret | GOCSPX-xxx |
GITHUB_CLIENT_ID | GitHub OAuth client ID | Iv1.abc123 |
GITHUB_CLIENT_SECRET | GitHub OAuth secret | secret_xxx |
| Variable | Description | Example |
|---|---|---|
S3_ACCESS_KEY | S3/R2 access key | AKIAIOSFODNN7EXAMPLE |
S3_SECRET_KEY | S3/R2 secret key | wJalrXUtnFEMI/K7MDENG/... |
S3_ENDPOINT | S3-compatible endpoint | https://bucket.s3.region.amazonaws.com |
CLOUDINARY_CLOUD_NAME | Cloudinary cloud name | my-cloud |
CLOUDINARY_API_KEY | Cloudinary API key | 123456789012345 |
CLOUDINARY_API_SECRET | Cloudinary API secret | abcdefghijk... |
| Variable | Description | Default |
|---|---|---|
PORT | Server port | 3000 |
LOCAL | Disable telemetry | - |
NODE_ENV / ENVIRONMENT | Environment mode | development |
import type { CliBkndConfig } from "bknd";
import { em, entity, text } from "bknd";
const schema = em({
posts: entity("posts", { title: text().required() }),
});
export default {
app: (env) => ({
// Database
connection: {
url: env.DB_URL ?? "file:data.db",
authToken: env.DB_TOKEN,
},
// Production flag
isProduction: env.NODE_ENV === "production",
// Pass all secrets to app
secrets: env,
}),
config: {
data: schema.toJSON(),
// Auth with env-based secrets
auth: {
enabled: true,
jwt: {
secret: env.JWT_SECRET,
issuer: "my-app",
},
strategies: {
password: { enabled: true },
google: env.GOOGLE_CLIENT_ID ? {
config: {
name: "google",
type: "oidc",
client: {
client_id: env.GOOGLE_CLIENT_ID,
client_secret: env.GOOGLE_CLIENT_SECRET,
},
},
} : undefined,
},
},
// Media with env-based adapter config
media: {
enabled: true,
adapter: {
type: "s3",
config: {
access_key: env.S3_ACCESS_KEY,
secret_access_key: env.S3_SECRET_KEY,
url: env.S3_ENDPOINT,
},
},
},
},
} satisfies CliBkndConfig;
Use wrangler.toml for non-secret vars and dashboard for secrets:
# wrangler.toml
[vars]
ENVIRONMENT = "production"
Set secrets via CLI:
npx wrangler secret put JWT_SECRET
npx wrangler secret put DB_TOKEN
Access in config:
import type { CloudflareBkndConfig } from "bknd/adapter/cloudflare";
export default {
app: (env) => ({
connection: env.DB, // D1 binding
isProduction: env.ENVIRONMENT === "production",
secrets: env,
}),
} satisfies CloudflareBkndConfig;
Use Vercel dashboard or CLI for env vars:
vercel env add JWT_SECRET production
vercel env add DB_URL production
Or .env.local for local development (auto-loaded by Next.js):
# .env.local
DB_URL=file:data.db
JWT_SECRET=dev-secret
Pass via docker-compose or -e flag:
# docker-compose.yml
services:
app:
environment:
- DB_URL=file:/data/app.db
- JWT_SECRET=${JWT_SECRET}
env_file:
- .env.production
Use CLI to generate env template from your config:
# Output required secrets as template
npx bknd secrets --template --format env
# Save to file
npx bknd secrets --template --format env --out .env.example
This creates a template without actual values, safe for version control.
Auto-generate .env.example on config changes:
export default {
syncSecrets: {
enabled: true,
outFile: ".env.example",
format: "env", // or "json"
},
app: (env) => ({ ... }),
} satisfies CliBkndConfig;
Conditionally enable features based on environment:
export default {
app: (env) => ({
connection: { url: env.DB_URL ?? "file:data.db" },
}),
config: {
auth: {
enabled: true,
// Only enable OAuth in production (requires secrets)
strategies: {
password: { enabled: true },
google: env.GOOGLE_CLIENT_ID ? {
config: {
name: "google",
type: "oidc",
client: {
client_id: env.GOOGLE_CLIENT_ID,
client_secret: env.GOOGLE_CLIENT_SECRET,
},
},
} : undefined,
},
},
// Only enable S3 media in production
media: env.S3_ACCESS_KEY ? {
enabled: true,
adapter: {
type: "s3",
config: {
access_key: env.S3_ACCESS_KEY,
secret_access_key: env.S3_SECRET_KEY,
url: env.S3_ENDPOINT,
},
},
} : {
enabled: false,
},
},
} satisfies CliBkndConfig;
Bknd resolves database connection in order:
--db-url CLI argumentconnection.url--memory flag (uses :memory:)DB_URL environment variablefile:data.dbCheck env loading:
# Server logs show connection source
npx bknd run
# Look for: "Using connection from ..."
Test env injection:
// Temporarily log env in config
app: (env) => {
console.log("Loaded env:", Object.keys(env));
return { ... };
},
Verify secrets command:
npx bknd secrets --template
Problem: Env vars undefined in config
Fix: Check file location and format:
# .env must be in project root (same level as bknd.config.ts)
ls -la .env
# No quotes around values
DB_URL=file:data.db # Correct
DB_URL="file:data.db" # May cause issues
Problem: Auth fails or warning about weak secret
Fix: Use minimum 32 characters:
# Generate secure secret
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# or
openssl rand -hex 32
Problem: Committed .env with real secrets
Fix:
# Add to .gitignore
echo ".env" >> .gitignore
echo ".env.local" >> .gitignore
echo ".dev.vars" >> .gitignore
# Remove from git history if committed
git rm --cached .env
Problem: env.VAR is undefined in deployed app
Fix: Platform-specific setup:
vercel env addwrangler secret put or dashboardenvironment: or env_file: in composeProblem: Using dev defaults in production
Fix: Fail fast instead of fallback:
app: (env) => {
if (!env.JWT_SECRET && env.NODE_ENV === "production") {
throw new Error("JWT_SECRET required in production");
}
return {
auth: {
jwt: { secret: env.JWT_SECRET ?? "dev-only" },
},
};
},
DO:
.env.example as template (no real values)syncSecrets to keep .env.example updatedDON'T:
.env with real secrets