Skip to content
Sniptide

Engineering10 min read

What is a .env file? Format, examples and project setup

Learn the .env file format with a copyable example, quoting rules and .env.example guidance, then choose how to load and share project values.

A .env file is a plain text file of KEY=value lines that holds a project's configuration, such as its port, database address and API keys. Your code never reads it directly: a loader such as dotenv, Node's --env-file flag or Bun copies each line into the process environment when the app starts, and the app reads process.env.PORT or os.environ["PORT"].

TL;DR

  • One variable per line, NAME=value, no spaces around =, and # comments on their own line.
  • Put double quotes around any value with spaces or a #, and don't rely on ${VAR} expansion. Loaders disagree on both.
  • Commit .env.example with empty values. Add .env to .gitignore and never commit it.
  • A variable already set in your shell wins over the file in every loader we tested.
  • Send real values through a channel built for it: a password manager, an encrypted file, a secrets manager or a Sniptide env group.

A .env file example you can copy

Here is a complete file for a small app called Acme notes. Every value is fake.

.env
# Acme notes, local development. Every value here is fake.
APP_NAME="Acme notes"
PORT=3000
DEBUG=true

# Database
DATABASE_URL=postgres://localhost:5432/acme_dev

# Third-party services
PAYMENTS_API_KEY=fake-key-for-local-dev
SENTRY_DSN=

Download the sample .env and its matching .env.example. We loaded this exact file with five loaders, and all five read the same six values:

Output
APP_NAME="Acme notes"
PORT="3000"
DEBUG="true"
DATABASE_URL="postgres://localhost:5432/acme_dev"
PAYMENTS_API_KEY="fake-key-for-local-dev"
SENTRY_DSN=""

Every value arrives as a string. PORT is "3000" and DEBUG is "true", so convert them in your code before you compare or add. An empty value such as SENTRY_DSN= sets the variable to an empty string, which is different from leaving the line out.

How a .env file reaches your app

The operating system knows nothing about .env. Each process has its own environment, a list of names and values it inherits from the shell that started it. A loader reads the file once at startup and adds its lines to that list.

The loader copies the file into the process environment once, at startup. A value already set in the shell wins.

Two consequences follow from this:

  • Edits need a restart. Changing .env while the app runs does nothing until the loader runs again.
  • The shell wins. If PORT=4000 is already exported in your terminal, the app sees 4000. We checked this with dotenv, Node, Bun and python-dotenv, and all four keep the existing value. dotenv and python-dotenv have an override option if you want the file to win.

.env file format and syntax

There's no formal standard for the format. These rules hold in every loader we tested:

Rule
Example
One variable per line, with no spaces around =
PORT=3000
Names use letters, digits and _, uppercase by convention
DATABASE_URL
A comment starts its own line with #
# Database
Double quotes keep spaces, and are removed from the value
APP_NAME="Acme notes"
An empty value sets the variable to an empty string
SENTRY_DSN=
If a name repeats, the later line wins
PORT=3001

The Node, Bun and Python loaders trim spaces around =, but a shell rejects them, so leave them out. Blank lines are ignored everywhere.

Most loaders also accept export NAME=value, single quotes and multi-line values in double quotes, which is how you store something like a PEM key. dotenv, Node, Bun and python-dotenv all read those the same way. The trouble starts with a few specific lines.

Do all loaders read .env the same way?

No. We wrote one test file with the lines people most often get wrong and loaded it on 9 November 2026 with dotenv 18.0.10, Node 22.23.4 with --env-file, Bun 1.4.4, python-dotenv 1.2.3, and Bash with set -a; source .env. Every other line in the file read the same in all five.

Line in .env
dotenv
Node
Bun
python-dotenv
Bash
APP_NAME=Acme notes
Acme notes
Acme notes
Acme notes
Acme notes
Unset, notes: command not found
HASH=abc#123
abc
abc
abc
abc#123
abc#123
PRICE='costs $5'
costs $5
costs $5
costs
costs $5
costs $5
API_URL=${API_BASE}/v1
${API_BASE}/v1
${API_BASE}/v1
Expanded
Expanded
Expanded
SIGNATURE="Line one\nLine two"
Two lines
Two lines
Two lines
Two lines
A literal \n

What each row means for your file:

  • Quote values with spaces. Unquoted Acme notes works in the Node and Python loaders, but a shell treats notes as a command.
  • Quote values with a #. dotenv, Node and Bun cut abc#123 at the hash, so a password containing # silently loses its end. HASH="abc#123" reads the same everywhere.
  • Escape $ for Bun. Bun expands $5 even inside single quotes, so costs $5 becomes costs . Bun's docs say to write \$ instead.
  • Don't build values from other variables. dotenv and Node keep ${API_BASE} as text, while Bun, Python and the shell substitute it. Write the full value on each line.

If a file only uses the rules from the format table and quotes anything unusual, every loader above reads it the same. The sample file at the top is written that way.

.env vs .env.example vs the environment

Three things share the name, and they have different jobs:

What it is
Commit it?
.env
Your real values for this machine
No. Add it to .gitignore
.env.example
Every variable name the app needs, with empty or safe defaults
Yes. It documents the configuration
Process environment
What the running app actually sees, from the shell, the host or a loader
Not a file. Your host or CI sets it in production

.env.example is the file a new teammate copies after cloning. Keep it in step with .env whenever you add a variable, and leave real secrets out of it, since it lives in Git history forever.

In production, you usually skip the file entirely. Hosting platforms, containers and CI systems all have their own way to set environment variables, and the app reads them the same way.

How to create a .env file

  1. Copy the example

    From the project root, copy the template and keep .env out of Git:

    Terminal
    cp .env.example .env
    echo ".env" >> .gitignore

    If the repository has no .env.example, check its README or the code for process.env and os.environ to find the names it expects.

  2. Fill in your values

    Open .env and set each value. Use the portable rules above: no spaces around =, and double quotes around anything with spaces or #.

  3. Load it

    Pick the loader for your runtime.

    Node 20.6 and later read the file with a flag. It's stable since 22.21 and 24.10.

    Terminal
    node --env-file=.env server.js

    Use --env-file-if-exists=.env when the file is optional, for example in CI.

  4. Check what the app sees

    Print one value to confirm the file loaded:

    Terminal
    node --env-file=.env -p "process.env.APP_NAME"
    Output
    Acme notes

How to share .env values with your team

The file stays on your machine, so the hard part is getting the real values to the next person without pasting them into a chat. These are the common options, from least to most setup:

Option
Fits when
Watch out for
Password manager or one-time link
One person needs the values once
Nothing updates when a value changes
Encrypted file in Git, with dotenvx or SOPS
The team already shares a key and wants values versioned with the code
Everyone needs the key, and rotating it touches every file
Secrets manager, such as Doppler or Infisical
You need audit logs, rotation and per-environment access rules
More setup than a small project needs
Sniptide env group
A solo developer or small team wants the values in the same place as their shared code
Encrypted at rest with a key the server holds, so it doesn't replace a dedicated secrets manager

Sniptide is a good fit when a solo developer or small team wants a project's .env values next to the code they already share, encrypted at rest and pulled into any checkout with one command, on Sniptide's servers or their own. If you need audit logs, automatic rotation or fine-grained access policies, a dedicated secrets manager is the better pick.

One env group per project. Push from one machine, pull or run on another.

Here is the handoff with the Sniptide CLI, using the Acme notes file:

  1. Install the CLI and log in

    Terminal
    npm install --global @sniptide/cli
    printf '%s' "$SNIPTIDE_TOKEN" | sniptide login --token-stdin

    Create the token under Settings, API tokens in the app, with write access so the CLI can push. Add --base-url to login if you run your own instance.

  2. Push your .env once

    Terminal
    sniptide env push acme-notes

    This creates the env group acme-notes from ./.env. A push replaces the whole group, so a variable you deleted from the file is deleted from the group too.

  3. Pull it on another machine

    Terminal
    sniptide env pull acme-notes

    The CLI writes .env readable only by you, with a first line such as # sniptide env acme-notes revision 1. That line lets the next push refuse to overwrite changes someone else made in the meantime. To skip the file entirely, run the app with the values set:

    Terminal
    sniptide env run acme-notes -- npm run dev

You can also open the group on the Secrets page to edit values, reveal one at a time, or share it through a password-protected link that expires after an hour, a day or a week. The encrypted secrets docs explain how values are encrypted and what the server can read.

If you'd rather keep the values on your own server, Sniptide self-hosts with one command on a Linux server with Docker and the Compose plugin:

Terminal
curl -fsSL https://sniptide.com/install.sh | sh

When the .env file isn't loading

  • The app reads undefined. Nothing loaded the file. Check that you passed --env-file, called config() before the code that reads the value, or that the runtime loads it on its own.
  • The file is in the wrong folder. Loaders look in the current working directory, which isn't always the folder of the script. Run the command from the project root, or pass the full path.
  • An old value sticks. The shell already exports that name. Run echo $PORT, then unset PORT or open a new terminal.
  • A value is cut short. It contains # or $. Put it in double quotes, and escape $ as \$ for Bun.
  • The file is named .env.txt. Some editors add an extension. Show file extensions and rename it to exactly .env.

If you keep reusable code next to these values, the Claude Code skills tutorial shows how the same CLI turns a collection into an agent skill, and the Pastebin alternatives comparison covers sharing the code itself.

Sources

Checked on 9 November 2026.

Pick where your library lives.

Use sniptide.com

Save your first pattern in the app, then connect Claude Code, Cursor or Codex with one sign-in in the browser.

Run it on your own server

One image and a database give you the app, the API and the MCP endpoint. Sniptide is open source under AGPL-3.0.

$docker compose up -d