Skip to main content

Command Palette

Search for a command to run...

The .env Surprise: Why Your dotenv Configuration Has No Effect in Bun

Updated
3 min read
The .env Surprise: Why Your dotenv Configuration Has No Effect in Bun

For Node.js developers, the workflow is muscle memory: need environment variables? Install dotenv, call config(), and the variables from your .env file are ready to go. However, when you bring this habit to Bun, the newer, blazing-fast JavaScript runtime, you'll encounter a surprise—a bit of "magic" that renders your dotenv setup seemingly useless.

It's an anomaly that has stumped many developers: You have both a .env and a .env.local file. You explicitly call dotenv in your code, which should load .env. But when you run it, Bun loads the variables from .env.local instead.

This isn't a bug. It's a fundamental feature that showcases how Bun thinks differently.


The Surprising Scenario

Imagine the following simple project structure:

.env

DATABASE_URL="postgresql://default_user@db/common_database"

.env.local

DATABASE_URL="postgresql://local_user@db/my_special_database"

server.js

JavaScript

// The old habit from the Node.js world
import 'dotenv/config';

console.log(`Connecting to database using: ${process.env.DATABASE_URL}`);

Based on pure dotenv logic, the output you'd expect would come from the .env file, as dotenv.config() targets that file by default.

However, when you run bun run server.js, the output that appears in your console is:

Connecting to database using: postgresql://local_user@db/my_special_database

The value from .env.local wins. The dotenv package you imported appears to have been completely ignored. What's really going on?


Unraveling the "Magic": The Bun Runtime Acts First

The answer lies in the order of execution. Unlike Node.js, which is a "blank canvas," Bun is a "batteries-included" runtime. One of those "batteries" is a highly efficient, native environment variable loader.

Here is the actual sequence of events when you run bun run server.js:

  1. Bun Takes Control (Before Your Code Runs): When the bun run command is executed, before a single line of your server.js is touched, the Bun runtime itself scans your project directory.

  2. Native Loading by Bun: Bun automatically discovers all relevant .env files. It loads them in a predetermined order of precedence, where more specific files override more general ones. The rule is: .env.local always overrides .env.

  3. process.env is Pre-populated: After this step, the global process.env object has already been populated by Bun. In our case, process.env.DATABASE_URL is already set to "postgresql://local_user@db/my_special_database".

  4. Your Code Execution Begins: Only after all this preparation is complete does Bun start executing the code inside server.js.

  5. The Futile dotenv Call: Your code reaches the import 'dotenv/config' line. The dotenv package runs and reads the .env file. However, when it's about to inject DATABASE_URL into process.env, it sees that the variable already exists. The default behavior of dotenv is not to overwrite existing variables. As a result, its call has no effect.

In short, Bun has already set the stage. The dotenv package you called arrived at a party that was already over.


What This Means for You

Understanding this behavior is critical for working efficiently with Bun.

  1. Ditch dotenv from Your Bun Projects: You simply don't need it. Relying on Bun's native loader is cleaner, faster, and removes one dependency from your project.

  2. Trust Bun's Conventions: Leverage Bun's built-in .env precedence system. Use .env for team-wide defaults, .env.development or .env.production for environment-specific settings, and .env.local for your local overrides that should never be committed to Git.

  3. A New Mindset: This is a perfect example of how Bun isn't just a "faster Node.js." It's an ecosystem with its own design philosophy that aims to simplify the modern development workflow.

So, the next time you see "weird" behavior in Bun, remember that it's often not a bug, but a clever feature designed to make your life easier—even if it means unlearning a few old habits.

More from this blog

F

Finlup ID | Sharing dunia teknologi dan coding

206 posts

Membedah Tren dan Teknologi yang Mengubah Dunia.