Skip to main content
TF
By Rohit V.9 min readArticle

What Is a .env File? Environment Variables Explained

TF
ToolsFuel Team
Web development tools & tips
Terminal window on a laptop showing command-line configuration

Photo by Unsplash on Unsplash

The Plain-English Version

> Quick answer: A `.env` file is a plain text file that holds configuration values — API keys, database URLs, secret tokens, feature flags — as simple `KEY=value` pairs, one per line. Your app reads them at startup as *environment variables* instead of having them hard-coded in the source. The point is to keep secrets and per-environment settings out of your code, so the same codebase can run on your laptop, a teammate's machine, and production, each pulling different values. The single most important rule: you never commit a `.env` file to Git, because it usually contains secrets. You commit a `.env.example` with the keys but no real values, and add `.env` to your `.gitignore`.

The first real bug I caused as a junior dev was hard-coding a database password directly in a config file and pushing it to a shared repo. It worked on my machine and leaked a credential at the same time. That's the exact mistake `.env` files exist to prevent. Config that changes between environments, and anything secret, does not belong in your code — it belongs in the environment your code runs in.


Here's what environment variables actually are, how a `.env` file plugs into them, and the handful of rules that keep you from leaking a secret the way I did.

What Environment Variables Actually Are

An environment variable is a named value that lives in the operating system's environment, outside your program, and your program can read it when it runs. They predate all of this config-file talk by decades — `PATH`, the variable that tells your shell where to find commands, is an environment variable you've used a thousand times without thinking about it.

The reason they matter for apps is separation. Your code says "give me the value of `DATABASE_URL`" without caring what that value is or where it came from. On your laptop it points at a local database; in production it points at the real one. Same code, different environment, no edits. That's the whole idea behind the
Twelve-Factor App methodology, which says config that varies between deploys should live in the environment, not in the code — a principle that quietly became the industry default.

You can set environment variables a bunch of ways: exported in your shell (`export API_KEY=abc123`), configured in your hosting dashboard, injected by a container orchestrator, or — for local development — loaded from a `.env` file. In production, the hosting platform (Vercel, a Docker setup, a cloud provider) is usually where the real values live, entered through their UI or secrets manager. The `.env` file is mostly a local-development convenience so you're not typing `export` statements every time you open a terminal.


Environment variables are also where a lot of secret material ends up — signing keys, tokens, the kind of credential that sits inside a
JWT token. Keeping that out of source control is exactly why the pattern exists, and it's why the rules later in this post are worth taking seriously.

How a .env File Works

Code editor and terminal showing configuration key-value pairs

Photo by Unsplash on Unsplash

The file itself couldn't be simpler. It's `KEY=value`, one per line:

``` DATABASE_URL=postgres://localhost:5432/myapp API_KEY=sk_test_abc123 PORT=3000 DEBUG=true ```


No quotes needed for simple values, `#` starts a comment, and by convention keys are UPPER_SNAKE_CASE. That's basically the entire format.


What reads it depends on your stack. For years the standard move in Node was the `dotenv` package: `npm install dotenv`, then `require('dotenv').config()` at the very top of your entry file, and every key in `.env` shows up on `process.env`. Python has `python-dotenv`, Ruby has `dotenv`, and most frameworks bundle something equivalent.


The newer twist is that Node.js now reads `.env` files natively, no package required. Since Node 20.6 you can run `node --env-file=.env app.js` and it loads the file for you; that flag graduated from experimental to stable in the Node 22.21 and 24.10 releases in late 2025, per the
Node.js CLI docs. One catch worth knowing: the built-in loader doesn't do variable expansion (referencing one variable inside another), so if your `.env` relies on that, you still want `dotenv` with `dotenv-expand`.

One thing that trips people up: a `.env` file is not read automatically just by existing. Something has to load it — the package, the `--env-file` flag, or your framework's built-in support. If your variables come back `undefined`, that missing load step is the first thing to check. And these values are always strings when they arrive, so `PORT=3000` gives you the string `"3000"`, not the number — convert it yourself before doing math on it. If you manage dependencies across a few tools, the
npm vs Yarn vs pnpm rundown covers how each handles installing something like dotenv.

Keep It Out of Git — and Other Rules

This is the part that actually protects you, so I'll be blunt about it.

Never commit your `.env` file. It holds secrets, and once a secret lands in Git history it's effectively public — even if you delete it in a later commit, it's still sitting in the history for anyone with repo access to find. Add `.env` to your `.gitignore` before you write a single real value into it. If you ever do push one by accident, treat those credentials as compromised and rotate them immediately; scrubbing Git history is painful and unreliable.

Commit a `.env.example` instead. This is a template with all the keys your app needs but placeholder or empty values:

``` DATABASE_URL= API_KEY= PORT=3000 ```


A new teammate copies it to `.env`, fills in real values, and is running in minutes without you having to DM them a list of keys. It also documents, in the repo, exactly what config the app expects.


A few more habits that have saved me:


- Generate real secrets, don't invent them. When I need a token or a strong value for a `.env`, I use a proper
password generator rather than mashing the keyboard — anything predictable is a liability. - Keep separate files per environment (`.env.development`, `.env.production`) if your setup needs it, and load the right one per context. - In production, prefer your platform's secrets manager or dashboard over a file on disk. A `.env` file is a local-dev convenience; a hosted secrets store is the grown-up version. - Don't log `process.env`. It's an easy way to accidentally dump every secret into a log file that a dozen services can read.

One more thing that catches people out as a project grows: `.env` files don't merge or cascade on their own. If you're used to a framework that layers `.env`, `.env.local`, and `.env.production` automatically, know that plain Node with `--env-file` won't do that for you — it loads exactly the file you point it at and nothing else. Frameworks like Next.js and Vite add their own layering rules on top, so check your tool's docs rather than assuming. And the load order matters: a real environment variable set in your shell or hosting dashboard usually wins over one in a `.env` file, which is exactly what you want, since production should override local defaults.


Get these right and `.env` files do exactly what they're meant to: keep your code portable, keep your secrets out of source control, and let the same app run anywhere by swapping the environment around it. For the other small utilities I keep handy while wiring up a project — encoders, formatters, generators — the
ToolsFuel tools directory has them together.

Frequently Asked Questions

Should I commit my .env file to Git?

No, never — a .env file usually holds secrets like API keys and database passwords, and anything committed to Git lives in the history permanently, even after you delete it. Add .env to your .gitignore before adding real values, and commit a .env.example template with empty values instead. If a secret ever does get pushed, rotate those credentials right away rather than trusting a history rewrite. You can generate replacement secrets with the [ToolsFuel password generator](/tools/password-generator).

Does Node.js read .env files automatically?

Not just by having the file present — something has to load it. Since Node 20.6 you can pass the --env-file=.env flag to load it natively, and that flag became stable in the Node 22.21 and 24.10 releases in late 2025. Before that, and still commonly today, people use the dotenv package by calling require('dotenv').config() at the top of their entry file. If your variables come back undefined, a missing load step is almost always why.

What's the difference between .env and .env.example?

The .env file holds your real, secret values and stays out of Git, while .env.example is a committed template that lists the same keys with placeholder or empty values. The example file documents what config the app needs so a new teammate can copy it, fill in their own values, and get running. Keeping them split is what lets you share the structure of your config without leaking the secrets.

Are environment variables secure?

They're safer than hard-coding secrets in your source, but they're not encrypted or magic — anything with access to the running process can read them. The security comes from keeping the .env file out of Git and using a proper secrets manager in production rather than a plain file on disk. Avoid logging process.env, since that's an easy way to leak everything at once into a log.

Why are my .env values coming through as strings?

Because environment variables are always strings by design — there's no type system in a .env file. So PORT=3000 arrives as the string "3000", and a value like DEBUG=true arrives as "true", not a real boolean. You have to convert them yourself in code, using Number() or a comparison like value === 'true', before you rely on them as numbers or booleans.

Try ToolsFuel

23+ free online tools for developers, designers, and everyone. No signup required.

Browse All Tools