Skip to content
Developer Template5 columns · 5 sample rows· Updated

Environment Variables (.env) Matrix Markdown Table Template

An environment variable table is the human-readable companion to .env.example. It tells a new developer what to put in a local .env before the app will boot, tells whoever maintains CI which secrets to create, and tells operations what production needs before the first deploy. Keeping configuration in the environment instead of in code only works if somebody writes down what that environment has to contain.

Edit This Template

Edit cells, add rows, or sort — the Markdown updates live
Row
1
PORT
3000
2
DATABASE_URL
YES
postgresql://user:pass@host:5432/db
3
REDIS_CACHE_URL
redis://127.0.0.1:6379/0
4
JWT_SECRET
YES
<output of openssl rand -hex 32>
5
NODE_ENV
Execution environment (development, test, production)
Output
| Variable Name     |  Type  | Required | Default / Example                     | Description                                                 |
| :---------------- | :----: | :------: | :------------------------------------ | :---------------------------------------------------------- |
| `PORT`            | number |    No    | `3000`                                | Local HTTP listener port                                    |
| `DATABASE_URL`    | string | **YES**  | `postgresql://user:pass@host:5432/db` | Primary database connection pool URI                        |
| `REDIS_CACHE_URL` | string |    No    | `redis://127.0.0.1:6379/0`            | Optional distributed cache cluster host                     |
| `JWT_SECRET`      | secret | **YES**  | `<output of openssl rand -hex 32>`    | HMAC signing key for user session tokens                    |
| `NODE_ENV`        |  enum  |    No    | unset (frameworks set it)             | Execution environment (`development`, `test`, `production`) |
5 rows × 5 columns
Rendered Preview
Variable NameTypeRequiredDefault / ExampleDescription
PORTnumberNo3000Local HTTP listener port
DATABASE_URLstringYESpostgresql://user:pass@host:5432/dbPrimary database connection pool URI
REDIS_CACHE_URLstringNoredis://127.0.0.1:6379/0Optional distributed cache cluster host
JWT_SECRETsecretYES<output of openssl rand -hex 32>HMAC signing key for user session tokens
NODE_ENVenumNounset (frameworks set it)Execution environment (development, test, production)

When to Use This Table

Each reader comes with a different decision. The developer on day one wants to know which keys they can skip locally. The platform engineer wants to know which values are secrets that belong in a vault and must be redacted from logs, and which are harmless configuration. The reviewer of a pull request wants to see that a newly read variable was documented. The Required column answers the most urgent question of all: will the process refuse to start without this key?

The strongest version of this table mirrors a startup validation schema — envalid, a Zod object, pydantic-settings, or similar — so the code and the docs list the same keys with the same rules. The weak version has drifted: keys that were removed a year ago, missing ones the app now reads, and example values that are real credentials. Treat every example as public, because in most repositories it is.

What Goes in Each Column

ColumnAlignmentWhat to put here
Variable Nameleft :---The exact key in backticks, normally UPPER_SNAKE_CASE. Keep framework prefixes visible: keys starting with NEXT_PUBLIC_ or VITE_ are inlined into the browser bundle at build time, so anything carrying them is readable by every visitor. Sort alphabetically or group by subsystem (database, auth, email).
Typecenter :---:Every environment value arrives as a string; this column says how the app parses it — number, boolean, url, enum, duration. The template’s secret is a classification rather than a parse type, and it is worth keeping: it tells operators the value goes in a secret store and must never be printed.
Requiredcenter :---:Whether startup fails without the key. The template bolds YES so required rows stand out in a column of plain No. If a key is enforced only in one stage, such as an error-tracking DSN in production, write Prod only rather than forcing it into yes or no.
Default / Exampleleft :---For optional keys, the default the code falls back to; for required keys, a fake value that shows the shape. Label which is which if it is not obvious. Connection strings should show every part — scheme, host, port, database — and passwords containing @ or : must be percent-encoded inside a URL.
Descriptionleft :---What the value controls and what changing it does: units (ms or s), whether it is read on the server only or shipped to the browser, and side effects. “Rotating this logs every user out” is the kind of sentence that saves an incident.

How to Fill It In

  1. 1Find every read in the code: search for process.env, import.meta.env, os.environ, os.Getenv, or std::env::var, and also check environment: blocks in Compose files and CI workflow definitions.
  2. 2For each key, read the fallback in code (the value after ?? or the second argument to .get()) and check whether startup validation rejects a missing value. That gives you Default and Required.
  3. 3Classify each key: anything that grants access — API keys, signing keys, passwords, DSNs containing credentials — is a secret. Everything else is plain configuration.
  4. 4Write example values by hand. Never copy them from a working .env. Generate placeholder shapes instead, for example openssl rand -hex 32 output for a signing key, and run a secret scanner over the file before committing.
  5. 5Prove the Required column: start the app in a clean CI job or a fresh container with only the required keys set. If it fails, a key is missing from the table; if it starts with fewer, a row is wrongly marked required.

Ready-to-Paste Variations

Per-environment matrix

When values differ by stage, a column per environment is clearer than a single default. Point to the secret store path instead of writing secret values.

| Variable | Development | Staging | Production |
| :--- | :--- | :--- | :--- |
| `NODE_ENV` | `development` | `production` | `production` |
| `LOG_LEVEL` | `debug` | `info` | `warn` |
| `DATABASE_URL` | Local Docker Postgres | Secrets Manager: `staging/db-url` | Secrets Manager: `prod/db-url` |
| `FEATURE_NEW_CHECKOUT` | `true` | `true` | `false` |

Compact .env.example companion

For a README where the full matrix is too much. The example column matches the placeholder in .env.example line for line.

| Variable | Required | Example |
| :--- | :---: | :--- |
| `DATABASE_URL` | **Yes** | `postgresql://app:app@localhost:5432/app_dev` |
| `SESSION_SECRET` | **Yes** | `<output of openssl rand -hex 32>` |
| `SMTP_URL` | No | `smtp://localhost:1025` |
| `PORT` | No | `3000` |

Secrets inventory

A separate table for the security-sensitive keys: where each lives, who owns it, and what rotation breaks.

| Secret | Stored in | Owner | Rotation |
| :--- | :--- | :--- | :--- |
| `JWT_SECRET` | AWS Secrets Manager | Platform team | Every 90 days; signs out all sessions |
| `STRIPE_SECRET_KEY` | AWS Secrets Manager | Payments team | When anyone with access leaves |
| `SMTP_PASSWORD` | GitHub Actions secret | Platform team | Yearly |

Common Mistakes

  • Using a real or realistic credential as the example value.

    Use an obvious placeholder such as sk_test_... or <generate with openssl rand -hex 32>. If a real one was committed, rotate it immediately; rewriting Git history does not remove copies already fetched or forked.

  • Putting a secret in a NEXT_PUBLIC_ or VITE_ variable.

    Those prefixes exist to expose values to client code, and they end up in the JavaScript sent to every visitor. Keep secrets unprefixed and read them only on the server; note “exposed to browser” in Description for prefixed keys.

  • Listing a default the runtime does not provide, such as NODE_ENV defaulting to development.

    Node.js itself sets no default; process.env.NODE_ENV is undefined unless something sets it. Frameworks such as Next.js set it for you during next dev and next build. Write “unset” and describe what your framework does.

  • Leaving boolean parsing unspecified.

    A naive truthiness check treats the string "false" as true because it is non-empty. State the accepted spellings (true/false only, or also 1/0) and parse strictly.

  • Letting the table, .env.example, and code drift apart.

    Generate one from the other, or add a CI check that compares keys in .env.example against the startup schema and fails on any difference.

Browse all 50 templates
FAQ

Environment Variables (.env) Matrix: Frequently Asked Questions

Should I commit my .env file?

No. Add .env to .gitignore and commit .env.example with placeholder values instead. The example file is what developers copy; this table is where you explain each key, its format, and its consequences.

What is the difference between a default and an example?

A default is what the code uses when the key is missing, so leaving it unset is a valid choice. An example only shows the expected format for a key that has no fallback. Mixing the two up leads people to ship placeholder values to production.

Do environment variable names have to be uppercase?

No, but uppercase is the near-universal convention. Be aware that Windows treats names case-insensitively, so Port and PORT refer to the same variable there, while Linux and macOS treat them as two different variables.

Are environment variables a safe place for secrets?

They are acceptable but not private: child processes inherit them, crash reporters may capture them, and on Linux the same user or root can read /proc/<pid>/environ. Many official Docker images also accept a _FILE variant, such as POSTGRES_PASSWORD_FILE, to read the secret from a mounted file instead.