| name | bknd-env-config |
| description | Use when configuring environment variables for Bknd projects. Covers .env files, secrets management, env injection in config, platform-specific variables, and production security. |
Environment Variables Configuration
Configure environment variables for Bknd applications across development and production.
Prerequisites
- Bknd project initialized (
bknd.config.ts exists)
- Understanding of your deployment target (local, Cloudflare, Vercel, etc.)
When to Use UI Mode
- Viewing current config via admin panel
- N/A for environment variables - all done via code/files
When to Use Code Mode
- Creating
.env files
- Configuring secrets in
bknd.config.ts
- Setting up platform-specific env vars
- All environment configuration tasks
Code Approach
Step 1: Create .env File
Create .env in project root:
DB_URL=file:data.db
DB_TOKEN=
JWT_SECRET=your-secret-here-min-32-chars
PORT=3000
LOCAL=true
Step 2: Inject Env in Config
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.
Step 3: Use .dev.vars for Dev Overrides (Optional)
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:
DB_URL=:memory:
JWT_SECRET=dev-only-secret
Common Environment Variables
Database
| Variable | Description | Example |
|---|
DB_URL | Database connection URL | file:data.db, libsql://db.turso.io |
DB_TOKEN | LibSQL/Turso auth token | eyJhbGciOiJFZERTQSIs... |
Authentication
| 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 |
Media/Storage
| 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... |
Server
| Variable | Description | Default |
|---|
PORT | Server port | 3000 |
LOCAL | Disable telemetry | - |
NODE_ENV / ENVIRONMENT | Environment mode | development |
Complete Configuration Example
import type { CliBkndConfig } from "bknd";
import { em, entity, text } from "bknd";
const schema = em({
posts: entity("posts", { title: text().required() }),
});
export default {
app: (env) => ({
connection: {
url: env.DB_URL ?? "file:data.db",
authToken: env.DB_TOKEN,
},
isProduction: env.NODE_ENV === "production",
secrets: env,
}),
config: {
data: schema.toJSON(),
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: {
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;
Platform-Specific Configuration
Cloudflare Workers/Pages
Use wrangler.toml for non-secret vars and dashboard for secrets:
[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,
isProduction: env.ENVIRONMENT === "production",
secrets: env,
}),
} satisfies CloudflareBkndConfig;
Vercel
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):
DB_URL=file:data.db
JWT_SECRET=dev-secret
Docker
Pass via docker-compose or -e flag:
services:
app:
environment:
- DB_URL=file:/data/app.db
- JWT_SECRET=${JWT_SECRET}
env_file:
- .env.production
Generate .env Template
Use CLI to generate env template from your config:
npx bknd secrets --template --format env
npx bknd secrets --template --format env --out .env.example
This creates a template without actual values, safe for version control.
SyncSecrets Option
Auto-generate .env.example on config changes:
export default {
syncSecrets: {
enabled: true,
outFile: ".env.example",
format: "env",
},
app: (env) => ({ ... }),
} satisfies CliBkndConfig;
Environment-Based Feature Flags
Conditionally enable features based on environment:
export default {
app: (env) => ({
connection: { url: env.DB_URL ?? "file:data.db" },
}),
config: {
auth: {
enabled: true,
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: 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;
Database Connection Priority
Bknd resolves database connection in order:
--db-url CLI argument
- Config file
connection.url
--memory flag (uses :memory:)
DB_URL environment variable
- Fallback:
file:data.db
Verification
Check env loading:
npx bknd run
Test env injection:
app: (env) => {
console.log("Loaded env:", Object.keys(env));
return { ... };
},
Verify secrets command:
npx bknd secrets --template
Common Pitfalls
.env Not Loading
Problem: Env vars undefined in config
Fix: Check file location and format:
ls -la .env
DB_URL=file:data.db
DB_URL="file:data.db"
JWT_SECRET Too Short
Problem: Auth fails or warning about weak secret
Fix: Use minimum 32 characters:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
openssl rand -hex 32
Secrets in Version Control
Problem: Committed .env with real secrets
Fix:
echo ".env" >> .gitignore
echo ".env.local" >> .gitignore
echo ".dev.vars" >> .gitignore
git rm --cached .env
Platform Env Not Available
Problem: env.VAR is undefined in deployed app
Fix: Platform-specific setup:
- Vercel: Add via dashboard or
vercel env add
- Cloudflare: Add via
wrangler secret put or dashboard
- Docker: Check
environment: or env_file: in compose
Wrong Fallback in Production
Problem: 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" },
},
};
},
DOs and DON'Ts
DO:
- Use
.env.example as template (no real values)
- Generate JWT_SECRET with crypto-safe randomness
- Use platform-specific secret management in production
- Validate required secrets on app start
- Use
syncSecrets to keep .env.example updated
DON'T:
- Commit
.env with real secrets
- Use weak or short JWT secrets
- Hardcode secrets in config files
- Use same secrets across environments
- Log env vars containing secrets
Related Skills
- bknd-local-setup - Initial project setup
- bknd-setup-auth - Configure authentication
- bknd-oauth-setup - OAuth provider configuration
- bknd-storage-config - Storage adapter configuration
- bknd-production-config - Production configuration
- bknd-deploy-hosting - Deployment options