bun --env-file: choose which .env file Bun loads
- tool
- Bun
- command
- bun
- flag
- --env-file=FILE
- default
- off; Bun loads .env, .env.$NODE_ENV and .env.local automatically
Bun reads .env files automatically: without any flag, bun index.ts loads .env, .env.$NODE_ENV (.env.development by default) and .env.local from the current directory into process.env and Bun.env. bun --env-file=.env.staging index.ts loads the named file instead, which is how you pick the configuration for one environment. Variables already set in the shell win over every file.
Automatic loading and --env-file
cat > .env <<'EOF'
PORT=8080
NAME=default
EOF
cat > .env.staging <<'EOF'
PORT=7000
EOF
cat > main.ts <<'EOF'
console.log("PORT =", process.env.PORT, "| NAME =", process.env.NAME);
EOF
echo "--- bun main.ts (automatic .env)"
npx --yes bun@1 main.ts
echo "--- bun --env-file=.env.staging main.ts"
npx --yes bun@1 --env-file=.env.staging main.ts
echo "--- shell variable wins"
PORT=9000 npx --yes bun@1 --env-file=.env.staging main.tsOutput
--- bun main.ts (automatic .env) PORT = 8080 | NAME = default --- bun --env-file=.env.staging main.ts PORT = 7000 | NAME = undefined --- shell variable wins PORT = 9000 | NAME = undefined
NAME exists only in .env. It is set in the first run and undefined with --env-file=.env.staging: the named file replaces the automatic list instead of adding to it. The shell value of PORT beats the file.
Load order without the flag
| File | When it is read |
|---|---|
| .env | Always |
| .env.production / .env.development / .env.test | Depending on NODE_ENV (development when unset; bun test uses test) |
| .env.local | Always, except when NODE_ENV=test |
| --env-file=FILE | Replaces the automatic list with the given file(s); repeatable |
| Shell environment | Always wins over any file |
Later files override earlier ones, so .env.local can hold developer-specific values on top of the committed .env.
Syntax extras
Bun's parser supports variable expansion, which Node.js does not:
# .env
HOST=db.local
DATABASE_URL=postgres://${HOST}/app # becomes postgres://db.local/app in Bun
LITERAL=\$NOT_EXPANDED # a backslash keeps the dollar signQuotes around values are removed, and # starts a comment on unquoted lines. Every value is a string.
Pitfalls
- Because loading is automatic, a forgotten
.envin the working directory changes behaviour without any flag on the command line. Checkprocess.envwhen a value looks wrong. - The flag goes before the script or file:
bun main.ts --env-file=xpasses the text to your program. bun builddoes not inline these variables unless you configureenvin the build options; runtime and bundling are separate.- Keep credentials in
.env.local(git-ignored) and commit only placeholder values.
Node.js and Deno have explicit flags with similar rules: node --env-file and deno run --env-file.
FAQ
Does Bun load .env automatically?
Yes. Without any flag, Bun reads .env, .env.$NODE_ENV and .env.local from the current directory. That is the main difference from Node.js, which needs --env-file.
Can I use --env-file with bun run scripts?
Yes. bun --env-file=.env.staging run start loads the file before the script runs, and the variables are visible to the script and to the processes it starts.
Do variables set in the shell override the .env file?
Yes. A variable that is already in the environment keeps its value; files only add variables that are missing.