codingstairs
NotesEDULifeContact
⌕Search⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

Get in touch

Send without signing in. Add your email if you'd like a reply.

  • Leave a message anonymously →
  • ✉ warragon112@gmail.com
  • KakaoTalk Open Chat ↗

© 2026 codingstairs

  • Notes
  • EDU
  • Search
  • Life
  • Contact
  • Legal
  • RSS
  • GitHub
Notes›tools

Environment variables and secrets

Published 2026-04-28· Updated 2026-05-18·0 views

Environment variables and secrets

.env files and environment variables show up early for newcomers. This article covers their origins, behavior, and the tools used for secrets management — based on facts.

1. Where env vars and .env come from

An environment variable is a key-value pair a process reads from the operating system. On Unix it's accessed via getenv(3), on Windows via GetEnvironmentVariable. The shell's export FOO=bar (bash) or $env:FOO = "bar" (PowerShell) hands them to child processes.

.env is the convention of writing those key-value pairs into a text file and letting a library inject them into the process environment. There is no formally defined standard. The de facto standards split into two:

  • dotenv (Node.js, motdotla/dotenv, 2013) — Started by Brandon Keepers, taken over by Scott Motte. Reads KEY=value lines and fills process.env. BSD-2.
  • python-dotenv (2014) — Started by Saurabh Kumar. Same behavior for Python's os.environ.

The Twelve-Factor App methodology (Adam Wiggins, Heroku, 2011) cemented the pattern when its III. Config section said "store config in the environment".

Why environment variables won out:

  • Language- and OS-agnostic standard.
  • Low chance of accidentally landing in a code repo.
  • Managed independently per deployment.

2. How it works

How .env ends up in the environment at runtime:

  1. A library (dotenv, python-dotenv, Vite, Next.js, etc.) reads the file at startup.
  2. Parses it line by line (handling quotes, whitespace, comments) and sets keys in process.env or os.environ.
  3. If the same key already exists in the OS environment, it is usually not overwritten (depends on the library and options).

Cases where the runtime auto-loads:

  • Next.js — Auto-loads .env.local, .env.development, .env.production. The NEXT_PUBLIC_ prefix is bundled to the client; everything else stays server-side.
  • Vite — Auto-loads .env, exposes only the VITE_ prefix to the client.
  • Docker Compose — Injects via env_file: or --env-file.

3. Per-environment split conventions

File Intent
.env Common values across environments. Whether to commit it is team policy.
.env.local Per-developer machine. Usually gitignored.
.env.development / .env.production Per-environment defaults.
.env.example Template with key names only. Tells new developers which vars to fill.

4. Secrets management tools

A common opinion: real secrets (external API keys, DB passwords) should be managed differently from build settings (public URLs, feature flags).

  • HashiCorp Vault (2015) — Self-hostable secrets manager. Dynamic password issuing, short-lived tokens.
  • AWS Secrets Manager, GCP Secret Manager, Azure Key Vault — Cloud-managed equivalents.
  • SOPS (Mozilla, 2017 OSS) — Encrypts files with KMS / age / PGP keys and commits them straight to git. The structure remains diff-friendly, which fits GitOps well.
  • 1Password CLI (op) — Pulls secrets from a 1Password vault into shell environment variables.
  • Doppler, Infisical — SaaS secrets managers. CLIs replace .env.
  • GitHub Actions Secrets — CI-only secrets.

Some setups run .env in production directly; others keep .env for development only and switch to a secrets manager in production. Both are common. The right side depends on team size, regulatory needs, and operations capacity.

5. Common shapes

.env.example (committed):

DATABASE_URL=postgres://user:pass@localhost:5432/app
SMTP_HOST=
SMTP_USER=
SMTP_PASS=
JWT_SECRET=replace-me

Node side:

import 'dotenv/config';
console.log(process.env.DATABASE_URL);

Python side:

from dotenv import load_dotenv
import os
load_dotenv()
db_url = os.environ["DATABASE_URL"]

Direct shell:

# macOS · Linux
export FOO=bar
echo $FOO
unset FOO
# Windows PowerShell
$env:FOO = "bar"
echo $env:FOO
Remove-Item Env:FOO

6. Common pitfalls

Accidental .env commit — secrets land in git history. The safest response is key rotation. Tools like git filter-repo rewrite history, but anyone who has already cloned still holds the leak.

Key collisions — when the OS environment already has a variable of the same name, libraries may ignore the .env value. Check options like dotenv's override ahead of time.

Newlines and quotes — putting multi-line PEM keys into .env runs into different quote and \n handling per library. Follow the official docs example.

Client-bundle exposure — frontend build tools claim to expose only certain prefixes (NEXT_PUBLIC_, VITE_), but anything that lands in the bundle reaches users in plaintext. Real secrets do not belong in client variables.

"Should .env be committed?" — Team policy. With plaintext secrets inside, usually no. If the team intentionally restricts the file to plaintext config only, then yes. Either way, write the policy down to reduce mishaps.

Closing thoughts

Environment variables are a simple, strong standard codified by the Twelve-Factor App's III. Config section. Keep real secrets out of git, and bringing in a secrets manager (Vault, SOPS, 1Password) raises operational safety a level. Real secrets must never reach the client bundle (the prefix rules are only the build tool's helping hand).

Next

  • version-managers
  • git-workflow

References include the dotenv (Node.js) GitHub, python-dotenv docs, HashiCorp Vault docs, Mozilla SOPS, The Twelve-Factor App III. Config, OWASP Secrets Management Cheat Sheet, and the GitGuardian State of Secrets Sprawl.

More in tools

All in this category →
  • Git Submodule · Subtree · LFS — repos inside repos
  • Regular expressions — finding strings by pattern
  • A history of Python dependency tools
  • Linting and formatting
  • Editor setup
  • Gradle