Environment Configuration
Proper environment configuration is the foundation of a reliable deployment pipeline. This guide walks you through setting up local, staging, and production environments with Cloudflare D1 and Drizzle ORM.
Understanding Environments
Most applications need at least three environments:
flowchart LR A["Local Development"] --> B["Staging"] B --> C["Production"] A -.-> |"Test changes"| A B -.-> |"Validate before release"| B C -.-> |"Serve users"| C
| Environment | Purpose | Database Type |
|---|---|---|
| Local | Development and testing on your machine | Local SQLite file |
| Staging | Pre-production testing, mirrors production | Remote D1 database |
| Production | Live application serving real users | Remote D1 database |
The wrangler.toml File
The wrangler.toml file is the central configuration for Cloudflare Workers and D1. It defines your environments, database bindings, and deployment settings.
Basic Structure
A complete wrangler.toml for multi-environment deployment:
# =============================================================================# Project Configuration# =============================================================================name = "my-app"main = "src/index.ts"compatibility_date = "2024-01-01"compatibility_flags = ["nodejs_compat"]
# =============================================================================# Production Environment (Default)# =============================================================================# The default configuration applies to production deployments# Triggered by: wrangler deploy (no --env flag)
[[d1_databases]]binding = "DB"database_name = "my-app-prod"database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"migrations_dir = "migrations"
# =============================================================================# Staging Environment# =============================================================================# Triggered by: wrangler deploy --env staging
[env.staging]name = "my-app-staging"
[[env.staging.d1_databases]]binding = "DB"database_name = "my-app-staging"database_id = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"migrations_dir = "migrations"
# =============================================================================# Preview Environment (Optional)# =============================================================================# For pull request previews or feature branches# Triggered by: wrangler deploy --env preview
[env.preview]name = "my-app-preview"
[[env.preview.d1_databases]]binding = "DB"database_name = "my-app-preview"database_id = "zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz"migrations_dir = "migrations"Configuration Breakdown
Let’s understand each section:
Project Configuration:
name = "my-app" # Worker name for deploymentmain = "src/index.ts" # Entry point for your applicationcompatibility_date = "2024-01-01" # Cloudflare runtime versioncompatibility_flags = ["nodejs_compat"] # Enable Node.js compatibilityDatabase Binding:
[[d1_databases]]binding = "DB" # Variable name in your code (env.DB)database_name = "my-app-prod" # Human-readable database namedatabase_id = "xxx-xxx-xxx" # Unique identifier from Cloudflaremigrations_dir = "migrations" # Path to migration filesThe binding name is what you use in your code to access the database:
// The binding name "DB" becomes available as env.DBapp.get('/users', async (c) => { const db = drizzle(c.env.DB); const users = await db.select().from(usersTable); return c.json(users);});Setting Up Local Development
Local development uses a SQLite database file stored on your machine. No remote database is needed.
How Local D1 Works
When you run wrangler dev or apply migrations with --local:
- Wrangler creates a local SQLite database
- The database is stored in
.wrangler/state/v3/d1/ - Your code uses this local database seamlessly
.wrangler/└── state/ └── v3/ └── d1/ └── miniflare-D1DatabaseObject/ └── <database-id>.sqliteLocal Development Commands
# Start development server (uses local database)pnpm wrangler dev
# Apply migrations locallypnpm wrangler d1 migrations apply DB --local
# Check local migration statuspnpm wrangler d1 migrations list DB --local
# Execute SQL against local databasepnpm wrangler d1 execute DB --local --command "SELECT * FROM users"Resetting Your Local Database
During development, you may want to start fresh:
# Remove the local databaserm -rf .wrangler/state/v3/d1
# Reapply all migrationspnpm wrangler d1 migrations apply DB --localSetting Up Remote Environments
Creating D1 Databases
Before configuring your environments, create the D1 databases:
# Create staging databasepnpm wrangler d1 create my-app-staging
# Create production databasepnpm wrangler d1 create my-app-prodExpected output:
✅ Successfully created DB 'my-app-staging' in region WNAMCreated your new D1 database.
[[d1_databases]]binding = "DB"database_name = "my-app-staging"database_id = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"Copy the database_id from the output into your wrangler.toml.
Verifying Your Databases
List all D1 databases in your account:
pnpm wrangler d1 listExpected output:
┌──────────────────┬──────────────────────────────────────┬─────────────────────┐│ Name │ ID │ Region │├──────────────────┼──────────────────────────────────────┼─────────────────────┤│ my-app-prod │ xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx │ WNAM │├──────────────────┼──────────────────────────────────────┼─────────────────────┤│ my-app-staging │ yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy │ WNAM │└──────────────────┴──────────────────────────────────────┴─────────────────────┘Environment-Specific D1 Bindings
Each environment can have its own database configuration. Here’s the pattern:
Production (Default)
The root-level [[d1_databases]] configuration applies to production:
# Production - used when no --env flag is specified[[d1_databases]]binding = "DB"database_name = "my-app-prod"database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"migrations_dir = "migrations"Commands for production:
# Deploy to productionpnpm wrangler deploy
# Apply migrations to productionpnpm wrangler d1 migrations apply DB --remote
# Check production migration statuspnpm wrangler d1 migrations list DB --remoteStaging
Environment-specific configuration uses the [env.NAME] pattern:
[env.staging]name = "my-app-staging" # Optional: different worker name
[[env.staging.d1_databases]]binding = "DB"database_name = "my-app-staging"database_id = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"migrations_dir = "migrations"Commands for staging:
# Deploy to stagingpnpm wrangler deploy --env staging
# Apply migrations to stagingpnpm wrangler d1 migrations apply DB --remote --env staging
# Check staging migration statuspnpm wrangler d1 migrations list DB --remote --env stagingMultiple Bindings Per Environment
If your application uses multiple databases:
# Production with multiple databases[[d1_databases]]binding = "DB"database_name = "my-app-main"database_id = "xxx-xxx-xxx"migrations_dir = "migrations/main"
[[d1_databases]]binding = "ANALYTICS_DB"database_name = "my-app-analytics"database_id = "aaa-aaa-aaa"migrations_dir = "migrations/analytics"
# Staging with multiple databases[env.staging][[env.staging.d1_databases]]binding = "DB"database_name = "my-app-main-staging"database_id = "yyy-yyy-yyy"migrations_dir = "migrations/main"
[[env.staging.d1_databases]]binding = "ANALYTICS_DB"database_name = "my-app-analytics-staging"database_id = "bbb-bbb-bbb"migrations_dir = "migrations/analytics"Secrets Management
Sensitive values like API keys, tokens, and credentials should never be stored in wrangler.toml. Use Cloudflare secrets instead.
What Are Secrets?
Secrets are encrypted environment variables that:
- Are stored securely by Cloudflare
- Are only decrypted at runtime
- Are never exposed in logs or the dashboard
- Must be set per environment
Setting Secrets
Use the wrangler secret put command:
# Set a secret for productionpnpm wrangler secret put API_KEYYou’ll be prompted to enter the value:
? Enter a secret value: › ********
✨ Success! Uploaded secret API_KEYFor specific environments:
# Set a secret for stagingpnpm wrangler secret put API_KEY --env staging
# Set a secret for production explicitlypnpm wrangler secret put API_KEYListing Secrets
View all secrets for an environment (values are hidden):
# List production secretspnpm wrangler secret list
# List staging secretspnpm wrangler secret list --env stagingExpected output:
┌──────────────────┬─────────────────────────────┐│ Name │ Created │├──────────────────┼─────────────────────────────┤│ API_KEY │ 2024-01-15T10:30:00.000Z │├──────────────────┼─────────────────────────────┤│ WEBHOOK_SECRET │ 2024-01-15T10:31:00.000Z │└──────────────────┴─────────────────────────────┘Deleting Secrets
Remove a secret when it’s no longer needed:
pnpm wrangler secret delete API_KEYpnpm wrangler secret delete API_KEY --env stagingAccessing Secrets in Code
Secrets are available on the env object, just like bindings:
app.get('/external-api', async (c) => { const apiKey = c.env.API_KEY;
const response = await fetch('https://api.example.com/data', { headers: { 'Authorization': `Bearer ${apiKey}`, }, });
return c.json(await response.json());});Type Safety for Secrets
Add secrets to your environment type definition:
export interface Env { // D1 Database binding DB: D1Database;
// Secrets API_KEY: string; WEBHOOK_SECRET: string;
// Optional secrets (may not be set in all environments) DEBUG_TOKEN?: string;}Local Development with Secrets
For local development, create a .dev.vars file:
API_KEY=dev-api-key-for-testingWEBHOOK_SECRET=dev-webhook-secretImportant: Add .dev.vars to your .gitignore:
# Local development secrets.dev.varsComplete wrangler.toml Example
Here’s a production-ready configuration with all the concepts covered:
# =============================================================================# D1 Migration Mastery - Wrangler Configuration# =============================================================================
name = "d1-migration-mastery"main = "src/index.ts"compatibility_date = "2024-01-01"compatibility_flags = ["nodejs_compat"]
# =============================================================================# Production Environment (Default)# =============================================================================# Deploy: pnpm wrangler deploy# Migrations: pnpm wrangler d1 migrations apply DB --remote
[[d1_databases]]binding = "DB"database_name = "d1-migration-mastery-prod"database_id = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"migrations_dir = "migrations"
# Optional: KV namespaces, R2 buckets, etc.# [[kv_namespaces]]# binding = "CACHE"# id = "..."
# =============================================================================# Staging Environment# =============================================================================# Deploy: pnpm wrangler deploy --env staging# Migrations: pnpm wrangler d1 migrations apply DB --remote --env staging
[env.staging]name = "d1-migration-mastery-staging"
[[env.staging.d1_databases]]binding = "DB"database_name = "d1-migration-mastery-staging"database_id = "f1e2d3c4-b5a6-0987-fedc-ba0987654321"migrations_dir = "migrations"
# =============================================================================# Preview Environment (for PR previews)# =============================================================================# Deploy: pnpm wrangler deploy --env preview# Migrations: pnpm wrangler d1 migrations apply DB --remote --env preview
[env.preview]name = "d1-migration-mastery-preview"
[[env.preview.d1_databases]]binding = "DB"database_name = "d1-migration-mastery-preview"database_id = "00112233-4455-6677-8899-aabbccddeeff"migrations_dir = "migrations"
# =============================================================================# Build Configuration (if needed)# =============================================================================# [build]# command = "pnpm build"
# =============================================================================# Development Settings (local only)# =============================================================================# These settings only apply to `wrangler dev`# [dev]# port = 8787# local_protocol = "http"Environment Configuration Flow
flowchart TD
subgraph Local["Local Development"]
L1["wrangler dev"]
L2["Local SQLite"]
L3[".dev.vars secrets"]
L1 --> L2
L1 --> L3
end
subgraph Staging["Staging Environment"]
S1["wrangler deploy --env staging"]
S2["D1: my-app-staging"]
S3["Staging secrets"]
S1 --> S2
S1 --> S3
end
subgraph Production["Production Environment"]
P1["wrangler deploy"]
P2["D1: my-app-prod"]
P3["Production secrets"]
P1 --> P2
P1 --> P3
end
Local --> |"Test & Validate"| Staging
Staging --> |"Approved"| Production
Quick Reference
| Task | Command |
|---|---|
| Start local dev server | pnpm wrangler dev |
| Deploy to staging | pnpm wrangler deploy --env staging |
| Deploy to production | pnpm wrangler deploy |
| Create D1 database | pnpm wrangler d1 create <name> |
| List D1 databases | pnpm wrangler d1 list |
| Set secret (production) | pnpm wrangler secret put <NAME> |
| Set secret (staging) | pnpm wrangler secret put <NAME> --env staging |
| List secrets | pnpm wrangler secret list |
| Delete secret | pnpm wrangler secret delete <NAME> |
Summary
Environment configuration is about keeping your development, staging, and production setups properly isolated:
- wrangler.toml defines your environments and database bindings
- Local development uses a SQLite file in
.wrangler/ - Staging and production each get their own D1 database
- Secrets keep sensitive values out of your codebase
- .dev.vars provides local secrets for development
With proper environment configuration, you can confidently develop locally and deploy to production knowing each environment is correctly isolated.