.env File Best Practices for Local and Deployed Apps

(Updated: July 16, 2026 ) .env DevOps Security Node.js

Dotenv files are the default way many teams inject local configuration. They are also a leading cause of leaked database passwords and “works on my machine” deploy failures. Good .env hygiene is less about syntax and more about what is secret, what is shared, and which process is allowed to read which file.

This guide is about day-to-day file practice. If secrets already appeared in a screenshot or chat, rotate first — then come back here to stop the next leak.

What belongs in .env (and what does not)

BelongsDoes not belong
Local DB URLs, ports, feature flags for devProduction master keys on a laptop “for convenience”
Non-secret public keys clearly named NEXT_PUBLIC_* / VITE_*Entire JSON service-account files pasted as one line without need
Per-developer overridesTeam-wide secrets that should live in a vault

Rule of thumb: if leaking the value grants access to customer data or money movement, prefer a secret manager or platform env for anything beyond local throwaway databases.

Git hygiene that actually works

.env
.env.*
!.env.example

Commit .env.example with placeholder values and comments describing each key. Never commit .env, .env.local, .env.production with real secrets. Double-check Docker build contexts and COPY . statements — images often bake in files that gitignored on the host.

Scan CI with secret detection. Pre-commit hooks (gitleaks, etc.) catch mistakes before they hit origin.

Naming and formatting conventions

# Good: SCREAMING_SNAKE, no spaces around =
DATABASE_URL=postgresql://app:secret@localhost:5432/app
APP_ENV=development
LOG_LEVEL=debug

# Bad
database-url = "postgres://..."

Quotes are sometimes required for values with spaces; many loaders treat # as comments mid-value unless quoted. Avoid exporting the same key in shell profile and .env with different values — you will debug the wrong layer.

Keep keys stable. Renaming DB_URL to DATABASE_URL without a migration window breaks every teammate overnight.

Per-environment files

Common pattern:

.env.example          # committed template
.env                  # local secrets (gitignored)
.env.test             # test runner defaults

Framework-specific overlays (Next.js .env.local, Vite modes) have load order rules — read your framework docs once and document the team’s chosen file in the README. Do not invent a sixth custom loader unless you enjoy onboarding tickets.

For deploys, prefer platform environment variables (Cloudflare, Vercel, Fly, k8s Secrets) over shipping a .env artifact. If you must use a file on a server, restrict filesystem permissions and rotate via automation.

Validate at process boot

Silent undefined config is how you discover missing Stripe keys after the first checkout.

import { z } from "zod";

const env = z
  .object({
    DATABASE_URL: z.string().url(),
    APP_ENV: z.enum(["development", "test", "production"]),
    LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
  })
  .parse(process.env);

export default env;

Fail fast on boot in production. In development, error messages should name the missing key and point to .env.example.

Sharing config without sharing secrets

  • Put shape and non-secret defaults in .env.example
  • Share real secrets via 1Password / vault / platform — not Slack DMs of file contents
  • For ephemeral preview apps, generate secrets in CI and inject as env vars
  • Document required vs optional keys so new hires do not copy stale production dumps

Docker and Compose pitfalls

env_file: .env is convenient and dangerous if the same file is used for compose on a shared bastion. Prefer Compose environment with values from the host env or a secrets mechanism. Never bake production .env into a public image layer.

Local debugging checklist

  • .env gitignored; example file committed
  • No production secrets in laptop dotenv for day-to-day work
  • Boot-time schema validation
  • Framework load order documented
  • CI secret scan enabled
  • Onboarding doc says “copy example → fill local DB only”

Bottom line

Treat .env as a local convenience, not the system of record for production secrets. Gitignore real files, commit examples, validate keys at startup, and keep high-blast credentials in a vault or platform. Boring dotenv practice prevents both outages and incident channels named #env-leak.

Local developer onboarding

A good .env.example lists every key with a one-line comment and a dummy value. Pair it with a make bootstrap or npm script that copies the example once. New hires should not Slack-ask for “the env file” that contains production secrets.