Environment Variables and .env Files: Use Secrets Without Committing Them

A practical guide to environment variables and .env files for student web developers: how config stays out of code, loading variables in Python and Node.js, production config on hosting platforms, the six mistakes that leak secrets, and a pre-deploy checklist.

Written by Projectech7 min readPublished
For Students building web apps with API keys, database passwords or cloud credentials who have wondered where secrets are supposed to live. Topics: Node.js, Python, dotenv, Deployment
Blueprint-style technical illustration of a document sealed with a padlock beside a terminal window of redacted configuration lines, with a key floating above, in white line art on deep blue.
Illustration generated for this guide.
In this guide

Your app needs a database password. Your first instinct is to type it directly into the code, right where the connection string is built. It works — until you push to GitHub and the password is now public, or you deploy to a server and the code still points at your laptop's database.

Environment variables are the standard answer, and .env files are how you feed them to your app during development. The concept is small but the mistakes around it are the most common way student projects leak secrets. This guide covers how environment variables actually work, how to load them in Python and Node, the six mistakes that leak keys, and how config changes between your laptop and a production server.

The problem environment variables solve

Code should be identical in development, testing, and production. Secrets and settings cannot be: the database password on your laptop is not the password on the server, the API key for testing is not the production key, and the debug flag must never be on in production. If settings live inside the code, every environment needs a different code copy — which is how people end up editing live code at 1 AM.

Environment variables solve this by keeping settings outside the code. The code reads a named value from the operating system's environment at runtime; each machine provides its own values. Nothing secret is written in the source files, so the code can be committed, shared, and deployed safely.

What a .env file is (and isn't)

A .env file is a plain text file, kept in the project root during development, that lists key-value pairs:

DATABASE_URL=postgres://devuser@localhost:5432/myapp
API_KEY=REPLACE_WITH_YOUR_OWN_KEY
DEBUG=true

Two rules govern it:

  1. It is never committed to git. The moment a .env file enters version control, every clone of the repository contains your secrets. Add .env to .gitignore before you create the file, not after.
  2. It is development-only convenience. On a real server you set environment variables in the hosting platform's dashboard or shell, not by uploading your .env file. The .env file is a local stand-in for the real environment.

Ship a .env.example file instead — same keys, placeholder values — so teammates know what to configure without ever seeing your secrets.

Loading them in Python

The python-dotenv library reads .env into the process environment:

from dotenv import load_dotenv
import os

load_dotenv()  # reads .env into os.environ

db_url = os.environ.get("DATABASE_URL")
if not db_url:
    raise RuntimeError("DATABASE_URL is not set")

Call load_dotenv() at the very top of your entry point, before any module that reads config. Failing fast with a clear error when a variable is missing is far better than the app booting with half its config empty and failing mysteriously later.

Loading them in Node.js

The dotenv package does the equivalent:

import "dotenv/config";

const dbUrl = process.env.DATABASE_URL;
if (!dbUrl) {
  throw new Error("DATABASE_URL is not set");
}

The import must come before anything that reads process.env. Note: dotenv does not override variables that are already set in the real environment — the real environment always wins, which is exactly the behaviour you want in production.

How production config actually works

On a deployment platform (a VPS, Render, Railway, Vercel, a college server — anywhere), you do not upload .env. You enter the same keys in the platform's environment-variable settings, and the platform injects them into the running process. Your code reads os.environ / process.env exactly as it did locally, and cannot tell the difference. This is why the code never references a file path: it reads the environment, and .env was only ever a way to fake that environment on your laptop.

The six mistakes that leak secrets

  1. Committing .env to git. The classic. Check .gitignore before the first commit, and if it ever happens, assume the key is compromised — rotate it, don't just delete the file (git history remembers).
  2. Hardcoding a fallback key. Writing API_KEY = os.environ.get("API_KEY") or "hardcoded-real-key" defeats the whole mechanism. Fall back to nothing; fail loudly instead.
  3. Printing config in logs or error pages. Debug output that dumps all environment variables will eventually be screenshared, screenshotted, or logged to a file someone reads. Never log secrets.
  4. Sending the key to the browser. Anything in frontend JavaScript is public. Keys the browser needs must be safe-to-expose public keys; anything else stays on the server.
  5. Sharing .env over chat or email. Send a .env.example with blanks; let each person fill in their own credentials from the service's dashboard.
  6. Using one key everywhere. Development, staging, and production should have separate keys so that a leak in one environment doesn't compromise the others — and so you can rotate one without breaking everything.

What to check before you deploy

  • .env is in .gitignore and no secret is in the repo (search the codebase for the actual key values to be sure).
  • Every required variable is set on the production platform, and the app fails fast with a clear message if one is missing.
  • DEBUG / verbose error pages are off in production — detailed stack traces handed to strangers are a vulnerability.
  • The .env.example is up to date, so the next person can set the project up without asking you for values.

FAQ

Is a .env file encrypted or secure by itself?
No. It's a plain text file — its entire security model is "it stays on your machine and out of git." Anyone with your laptop can read it. The protection comes from never publishing it, not from the format.

My teammate needs the same keys. How do I share them safely?
The safe pattern is that each person generates their own credentials from the service's dashboard (most APIs let you create multiple keys). Sharing one key over chat means it now lives in a chat history, a notification preview, and possibly a cloud backup.

Why does my app work locally but fail on the server?
In nine cases out of ten, a variable is missing or misspelled in the production environment, or the code reads process.env before the platform injects values (wrong startup order). Add a startup check that lists which required variables are missing — never their values — and the answer appears in the first log line.

Do I need different variables for frontend and backend?
Yes, and this is a common source of leaks: build tools embed frontend variables into the JavaScript bundle at build time, making them public. Keep secrets in backend-only variables and expose to the frontend only what the browser genuinely needs.

Limitations

  • Environment variables are a configuration mechanism, not a secrets vault: on shared or compromised machines they are readable by anyone with process access. High-sensitivity systems use dedicated secret managers instead.
  • Values are strings — numbers, booleans and lists need parsing in code, which is a small but real source of bugs ("false" the string is truthy in most languages).
  • This guide covers the standard pattern for student web apps; regulated environments (payments, health data) impose stricter secret-handling rules that go beyond .env discipline.

More project guides

More in Web Development