🔧Developer

Environment Variables: The Right Way to Handle Secrets in Your App

How to manage environment variables for local development, CI/CD, and production without leaking secrets, hardcoding values, or debugging .env file issues for hours.

6 min readFebruary 8, 2026By FreeToolKit TeamFree to read

A surprising number of production incidents trace back to environment variable mishaps — a key hardcoded in source, a .env committed to a public repo, a missing variable that caused a silent failure. Here's the pattern that avoids all of them.

The File Hierarchy (Stop Guessing)

  • .env.example — committed, shows required variables with placeholder values and comments. The documentation.
  • .env — not committed, local defaults. Sometimes committed for truly non-secret defaults.
  • .env.local — not committed, overrides everything for local development.
  • .env.production / .env.staging — not committed locally, values set in hosting platform.

Validate at Startup

The worst outcome: a missing environment variable causes a silent failure or a cryptic error deep in runtime. Validate required variables when the application starts and fail loudly if any are missing:

config.ts

const required = [
  'DATABASE_URL',
  'JWT_SECRET',
  'STRIPE_SECRET_KEY',
];

for (const key of required) {
  if (!process.env[key]) {
    throw new Error(`Missing required environment variable: ${key}`);
  }
}

export const config = {
  databaseUrl: process.env.DATABASE_URL!,
  jwtSecret: process.env.JWT_SECRET!,
  stripeKey: process.env.STRIPE_SECRET_KEY!,
};

Client-Side vs Server-Side Variables

In Next.js: only variables prefixed with NEXT_PUBLIC_ are available in the browser. Everything else is server-only. This is critical for secrets — a STRIPE_SECRET_KEY without the NEXT_PUBLIC_ prefix never reaches the client. In Vite: the prefix is VITE_. In Create React App: REACT_APP_. The pattern prevents accidentally exposing secrets by including them in client-side bundles.

The Secret That Got Committed: What To Do

  1. 1Rotate the compromised secret immediately — assume it's already been harvested.
  2. 2Remove from git history: git filter-branch or BFG Repo Cleaner (better tool).
  3. 3Force-push the rewritten history.
  4. 4Add to .gitignore.
  5. 5If the repo is public: the secret was exposed. Even after removal from history, assume scrapers captured it.

For Teams: Use a Secrets Manager

Doppler and Infisical are the accessible options for most teams. You define secrets once per environment in a web UI, and they inject values into your local development (via a CLI), CI/CD, and production deployments automatically. No more 'ask Alice for the .env file' for new developers. The cost (Doppler starts free) is worth eliminating the coordination overhead and accidental leakage.

Frequently Asked Questions

Should I commit my .env file to git?+
Never. .env files contain secrets — API keys, database passwords, credentials — that must not be in version control. Add .env to .gitignore immediately. If you've already committed a .env file: remove it with git rm --cached .env, add to .gitignore, commit the removal, and then rotate every secret that was exposed (assume they're compromised). Check with git log -- .env to see if the file ever appeared in history. GitHub's secret scanning will alert you if you push keys from known services.
What's the difference between .env and .env.local?+
In frameworks like Next.js and Vite, there's a hierarchy. .env contains defaults for all environments. .env.local overrides for local development (not committed, in .gitignore). .env.production is loaded in production builds. .env.development is loaded in dev mode. .env.local always wins over .env for the same key. The pattern: commit .env with non-secret defaults and documentation comments, keep secrets in .env.local and on your hosting platform.
How should I share .env values with my team?+
Password manager (1Password, Bitwarden) team vaults with a secrets section. A dedicated secrets manager (AWS Secrets Manager, HashiCorp Vault, Doppler, Infisical). An .env.example file committed to the repo with placeholder values — developers fill in real values from a shared password manager. Never via Slack, email, or any plain-text channel. The .env.example approach with a password manager is the pragmatic choice for most small teams.
How do environment variables work in CI/CD (GitHub Actions, etc.)?+
CI platforms provide secret storage for sensitive values. GitHub Actions uses repository secrets (Settings → Secrets and variables → Actions) — these appear as environment variables during workflow runs. In your workflow YAML: env: DATABASE_URL: ${{ secrets.DATABASE_URL }}. For non-secret env vars that differ between environments, environment-specific variables work. Never hardcode production credentials in workflow files.

🔧 Free Tools Used in This Guide

FT

FreeToolKit Team

FreeToolKit Team

We build free browser-based tools and write practical guides without the fluff.

Tags:

developersecuritydevopsenvironment-variables