The .env file: format, examples and safety

A .env file is a plain text file with one KEY=value variable per line. A library or the runtime loads those pairs into the process environment when the program starts.

Updated 4 min read

Learning objectives

After reading this article you will be able to:

  • Write a .env file with the correct syntax
  • Load a .env file in Node.js and Python
  • Keep the file out of Git and configure values safely in production
On this page

Every project has values that do not belong in the code: a bot token, a database password, an API key. Typing them into the terminal on every run gets old, and hard-coding them is dangerous. The .env file is the common answer: a text file next to your project, read at startup.

In practice, it is only a way to fill in environment variables during development. The operating system does not read the file. The runtime (Node.js) or a library (python-dotenv) does, and copies the pairs into the process.

.envTOKEN=DEBUG=DATABASE_URL=at startupProcess environmentTOKENDEBUGDATABASE_URLAlready setTOKEN=wins over the filereadsprocess.envos.environ
Read at startup, the .env fills the process environment; a variable already set in the environment wins over the file.

What is the syntax of a .env file?#

One variable per line, as NAME=value. There is no single official standard, but these rules hold in both Node.js and python-dotenv:

bash
DEBUG=false # comment
DISCORD_TOKEN=paste_the_token_here
PORT=3000
DATABASE_URL="postgresql://user:password@localhost:5432/mydb" # trailing comment
PHRASE='a # inside quotes does not start a comment'
PRIVATE_KEY="line 1
line 2"
export NODE_ENV=development
  • Names use letters, digits and underscores, must not start with a digit, and are usually UPPERCASE. In Node.js, my-var and 1_VAR are invalid.
  • Quotes are optional, but needed when the value contains # or spans several lines. Quoted values can run over multiple lines.
  • Everything is a string. PORT=3000 arrives as "3000" and DEBUG=false arrives as "false", which is truthy in JavaScript and Python. Convert in your code.
  • export at the start of a line is accepted and ignored, so the same file can be loaded with source .env in a shell.

Watch out for $ in values too. Next.js expands $OTHER_VAR inside .env files and needs \$ for a literal dollar sign. python-dotenv only expands the ${VAR} form.

How do you load a .env file in Node.js?#

Recent Node.js versions have built-in support, no package needed, and the --env-file flag is no longer experimental:

bash
node --env-file=.env index.js
js
// index.js
const token = process.env.DISCORD_TOKEN;
if (!token) throw new Error("DISCORD_TOKEN is not set");

const port = Number(process.env.PORT ?? 3000);
console.log(`Starting on port ${port}`);

If the file does not exist, --env-file throws an error. Use --env-file-if-exists=.env, also available in recent Node.js versions, when the file is optional, for example in production. You can also load it from code with process.loadEnvFile('.env'), and util.parseEnv() only parses text without touching process.env.

You can pass several files. Later files override earlier ones, but a variable already set in the environment always beats the file:

bash
node --env-file=.env --env-file=.env.local index.js

How do you load a .env file in Python?#

Use the python-dotenv package:

bash
pip install python-dotenv
python
import os
from dotenv import load_dotenv

load_dotenv()  # looks for .env in the script's folder or above

token = os.environ["DISCORD_TOKEN"]  # KeyError if missing
port = int(os.getenv("PORT", "3000"))
print(f"Starting on port {port}")

By default load_dotenv() does not replace variables that already exist in the environment; load_dotenv(override=True) does the opposite. To read values without changing os.environ, use dotenv_values(".env"), which returns a dictionary.

Other frameworks may read the file on their own. Next.js loads .env, .env.local and per-NODE_ENV variants, and only exposes variables prefixed with NEXT_PUBLIC_ to the browser, baked into the JavaScript at build time. In Docker Compose, a service's env_file key loads the file's variables into the container's environment.

How do you keep .env secrets from leaking?#

The real risk of a .env file is ending up in the repository. Do this before your first commit:

  1. Create a .gitignore with the line .env (and .env.* if you use variants, allowing only .env.example).
  2. Commit a .env.example with names and fake values, so people who clone the repo know what to fill in:
bash
DISCORD_TOKEN=
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
PORT=3000
  1. Check with git status that .env does not show up as a new file.
  2. Never print values in logs or paste them into error screenshots.

If a secret was already committed, deleting the file does not help, because history keeps the content. Generate a new key and revoke the old one. The steps are in API key exposed on GitHub, and for bots see the Discord bot token.

What are the common .env mistakes?#

The variable is undefined. The file was not loaded (missing --env-file or load_dotenv()), it sits in another folder (the --env-file path is relative to the current directory, not the script), or the name has a typo.

I changed the value and nothing changed. The process reads the file only at startup: restart it. If the variable also exists in the environment, it beats the file. Run echo $DISCORD_TOKEN (or $env:DISCORD_TOKEN in PowerShell) to check.

The value was cut off at #. Wrap the value in quotes.

PORT does not work as a number. It is a string. Convert with Number() or int().

A front-end variable shows up empty. In frameworks like Next.js, only variables prefixed with NEXT_PUBLIC_ reach the browser, and the value is fixed at build time. Never use that prefix for secrets.

How to do this on Square Cloud#

On Square Cloud, environment variables are configured per application, in the dashboard, through the API, the CLI or the VS Code extension. Your code keeps reading process.env or os.environ as usual.

If you already have a .env ready, the dashboard imports the file and fills in the variables. You can also export everything back as KEY=VALUE. A change takes effect on the application's next restart, with no rebuild. Inside the application, the SQUARECLOUD_APP_ID variable gives you its ID at runtime.

That way the .env stays on your computer, listed in .gitignore, and production values are configured on the application itself. See the details in environment variables on Square Cloud.

Deploy

Push from GitHub, the CLI or a ZIP and go live in seconds

Learn more

FAQs

Does the file have to be named exactly .env?

No. The name .env is a convention. Node.js accepts any path in --env-file=file and python-dotenv accepts any path in load_dotenv('file'). Frameworks such as Next.js look for specific names, like .env.local.

Can I commit my .env file to Git?

Not if it holds secrets. Add .env to .gitignore and commit only a .env.example with variable names and fake values. If a secret was already committed, rotate the key: deleting the file later does not remove it from history.

Does .env override a variable that already exists in the system?

By default, no. With Node.js --env-file and python-dotenv load_dotenv(), the value already in the environment wins over the file. In python-dotenv, override=True flips that.

Do I need a .env file in production?

Not necessarily. In production the platform usually has its own environment variables setting, and your code reads the same process.env or os.environ. The .env file is mostly a convenience for local development.

Next articleVPS vs cloud platform: what fits your projectA VPS gives you full control of a server and makes you run it. A hosting platform automates the server. See how they differ and when each one fits your project.

More in Deploy

See all 33 articles