DEV Community

Binary Journal
Binary Journal

Posted on

Environment Variables the Safe Way

Why Environment Variables Matter

Every app needs configuration: database URLs, API keys, feature flags. Hardcoding them is a recipe for disaster. Environment variables let you keep secrets out of your codebase and change behavior without redeploying. But using them carelessly can still leak secrets or break your app in production.

Here's how I handle env vars safely, from local development to deployment.

The Basics: Reading Env Vars

In Node.js, you read env vars from process.env. In Python, it's os.environ. But the safest way is to use a library that validates and typecasts them.

Node.js with dotenv and envalid

// .env
DATABASE_URL=postgres://user:pass@localhost:5432/mydb
PORT=3000

// config.js
import dotenv from 'dotenv';
dotenv.config();

import { cleanEnv, str, num } from 'envalid';

const env = cleanEnv(process.env, {
  DATABASE_URL: str(),
  PORT: num({ default: 3000 }),
});

export default env;
Enter fullscreen mode Exit fullscreen mode

envalid throws an error if a required variable is missing, and it converts types. No more parseInt(process.env.PORT) || 3000 scattered around.

Python with pydantic

# config.py
from pydantic import BaseSettings

class Settings(BaseSettings):
    database_url: str
    port: int = 3000

    class Config:
        env_file = ".env"

settings = Settings()
Enter fullscreen mode Exit fullscreen mode

Pydantic reads from .env and validates types. If DATABASE_URL is missing, it raises a clear error at startup.

Never Commit .env Files

Your .env file with real secrets should never hit version control. Add it to .gitignore immediately.

# .gitignore
.env
Enter fullscreen mode Exit fullscreen mode

But you still need to share what variables are required. Commit a .env.example with dummy values.

# .env.example
DATABASE_URL=postgres://user:pass@localhost:5432/mydb
PORT=3000
Enter fullscreen mode Exit fullscreen mode

New team members copy it to .env and fill in real values.

Use a Secret Manager in Production

In production, don't rely on a .env file on the server. Use your cloud provider's secret manager (AWS Secrets Manager, GCP Secret Manager, or a tool like Vault). Inject secrets as environment variables at runtime.

For example, in a Docker container:

docker run -e DATABASE_URL=$(aws secretsmanager get-secret-value --secret-id mydb --query SecretString --output text) myapp
Enter fullscreen mode Exit fullscreen mode

Or in Kubernetes, use a Secret resource:

apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
stringData:
  DATABASE_URL: "postgres://user:pass@prod-db:5432/mydb"
Enter fullscreen mode Exit fullscreen mode

Then reference it in your pod spec:

env:
  - name: DATABASE_URL
    valueFrom:
      secretKeyRef:
        name: app-secrets
        key: DATABASE_URL
Enter fullscreen mode Exit fullscreen mode

This keeps secrets out of your image and out of your deployment YAML.

Validate Early and Fail Fast

Your app should crash on startup if a required env var is missing. That's better than running with undefined and failing later in a confusing way.

With the validation libraries above, you get that for free. But if you're using plain process.env, add a check at the top of your entry file.

const required = ['DATABASE_URL', 'JWT_SECRET'];
for (const key of required) {
  if (!process.env[key]) {
    throw new Error(`Missing required env var: ${key}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Watch Out for Defaults

Defaults are useful, but they can hide problems. If you default to localhost, you might accidentally connect to your local database in production. I prefer to have no default for critical vars, and only default for non-essential ones like LOG_LEVEL.

Never Log Secrets

Logging env vars is an easy way to leak them. Make sure your logger doesn't dump the entire process.env or os.environ. If you need to debug, log the variable names but not the values.

console.log('DATABASE_URL is set:', !!process.env.DATABASE_URL);
Enter fullscreen mode Exit fullscreen mode

Local Development Tips

  • Use a .env.local for personal overrides and keep it gitignored.
  • Don't share .env files over chat or email. Use a secure vault or a tool like doppler or dotenv-vault if you need to share.
  • If you use Docker Compose, put env vars in a env_file directive, not inline in the YAML.
services:
  app:
    env_file:
      - .env
Enter fullscreen mode Exit fullscreen mode

Conclusion

Handling environment variables safely is about discipline:

  1. Centralize reading and validation.
  2. Keep secrets out of version control.
  3. Use a secret manager in production.
  4. Fail fast if something's missing.
  5. Never log secrets.

It's a small investment that pays off every time you onboard a developer, rotate a key, or debug a production issue. Your future self will thank you.

Top comments (0)