Skip to content

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

EnvironmentPurposeDatabase Type
LocalDevelopment and testing on your machineLocal SQLite file
StagingPre-production testing, mirrors productionRemote D1 database
ProductionLive application serving real usersRemote 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:

wrangler.toml
# =============================================================================
# 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:

wrangler.toml
name = "my-app" # Worker name for deployment
main = "src/index.ts" # Entry point for your application
compatibility_date = "2024-01-01" # Cloudflare runtime version
compatibility_flags = ["nodejs_compat"] # Enable Node.js compatibility

Database Binding:

wrangler.toml
[[d1_databases]]
binding = "DB" # Variable name in your code (env.DB)
database_name = "my-app-prod" # Human-readable database name
database_id = "xxx-xxx-xxx" # Unique identifier from Cloudflare
migrations_dir = "migrations" # Path to migration files

The binding name is what you use in your code to access the database:

src/index.ts
// The binding name "DB" becomes available as env.DB
app.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:

  1. Wrangler creates a local SQLite database
  2. The database is stored in .wrangler/state/v3/d1/
  3. Your code uses this local database seamlessly
.wrangler/
└── state/
└── v3/
└── d1/
└── miniflare-D1DatabaseObject/
└── <database-id>.sqlite

Local Development Commands

Terminal
# Start development server (uses local database)
pnpm wrangler dev
# Apply migrations locally
pnpm wrangler d1 migrations apply DB --local
# Check local migration status
pnpm wrangler d1 migrations list DB --local
# Execute SQL against local database
pnpm wrangler d1 execute DB --local --command "SELECT * FROM users"

Resetting Your Local Database

During development, you may want to start fresh:

Terminal
# Remove the local database
rm -rf .wrangler/state/v3/d1
# Reapply all migrations
pnpm wrangler d1 migrations apply DB --local

Setting Up Remote Environments

Creating D1 Databases

Before configuring your environments, create the D1 databases:

Terminal
# Create staging database
pnpm wrangler d1 create my-app-staging
# Create production database
pnpm wrangler d1 create my-app-prod

Expected output:

✅ Successfully created DB 'my-app-staging' in region WNAM
Created 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:

Terminal
pnpm wrangler d1 list

Expected 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:

wrangler.toml
# 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:

Terminal
# Deploy to production
pnpm wrangler deploy
# Apply migrations to production
pnpm wrangler d1 migrations apply DB --remote
# Check production migration status
pnpm wrangler d1 migrations list DB --remote

Staging

Environment-specific configuration uses the [env.NAME] pattern:

wrangler.toml
[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:

Terminal
# Deploy to staging
pnpm wrangler deploy --env staging
# Apply migrations to staging
pnpm wrangler d1 migrations apply DB --remote --env staging
# Check staging migration status
pnpm wrangler d1 migrations list DB --remote --env staging

Multiple Bindings Per Environment

If your application uses multiple databases:

wrangler.toml
# 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:

Terminal
# Set a secret for production
pnpm wrangler secret put API_KEY

You’ll be prompted to enter the value:

? Enter a secret value: › ********
✨ Success! Uploaded secret API_KEY

For specific environments:

Terminal
# Set a secret for staging
pnpm wrangler secret put API_KEY --env staging
# Set a secret for production explicitly
pnpm wrangler secret put API_KEY

Listing Secrets

View all secrets for an environment (values are hidden):

Terminal
# List production secrets
pnpm wrangler secret list
# List staging secrets
pnpm wrangler secret list --env staging

Expected 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:

Terminal
pnpm wrangler secret delete API_KEY
pnpm wrangler secret delete API_KEY --env staging

Accessing Secrets in Code

Secrets are available on the env object, just like bindings:

src/index.ts
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:

src/types.ts
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:

.dev.vars
API_KEY=dev-api-key-for-testing
WEBHOOK_SECRET=dev-webhook-secret

Important: Add .dev.vars to your .gitignore:

.gitignore
# Local development secrets
.dev.vars

Complete wrangler.toml Example

Here’s a production-ready configuration with all the concepts covered:

wrangler.toml
# =============================================================================
# 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

TaskCommand
Start local dev serverpnpm wrangler dev
Deploy to stagingpnpm wrangler deploy --env staging
Deploy to productionpnpm wrangler deploy
Create D1 databasepnpm wrangler d1 create <name>
List D1 databasespnpm wrangler d1 list
Set secret (production)pnpm wrangler secret put <NAME>
Set secret (staging)pnpm wrangler secret put <NAME> --env staging
List secretspnpm wrangler secret list
Delete secretpnpm 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.