GenerativeModels.ai
Environment Variables Guidelines Overview

Environment Variables Guidelines


🎯 Why Environment Variables Matter

Environment variables (“env vars”) allow us to configure applications cleanly and securely without modifying the codebase.

They are critical for:

  • Security (secrets never in code)
  • Reproducibility (different environments with same code)
  • Reliability (fail fast if critical config is missing)
  • Scalability (infrastructure can modify settings without redeploy)

🗂️ Where Environment Variables Come From

EnvironmentSource
Local Dev.env loaded by Docker Compose or container
Docker / KubernetesPassed by orchestrator (docker-compose.yml, k8s ConfigMap/Secret)
Cloud (Prod)Passed via Secret Manager, Env injection, or Platform (e.g., GCP Run, AWS ECS)

Applications never load .env files themselves.

Environment must set variables before the app starts.


📄 How to Handle Environment Variables

1. Pass Vars via Infrastructure

Local Dev:

  • Store .env file in project root (make sure it’s added to .gitignore)

  • Store a .envrc file in project root (and check it in), with the following content

    dotenv
  • Load automatically through:

    • Using env_file if using docker-compose.yml:

      services:
        backend:
          build: .
          env_file:
            - .env
    • Using direnv for shell auto-loading, which automatically loads the content of .env file when you cd into the project folder, and unloads them when you cd out of it.

Production:

  • Inject secrets at deploy time (CI/CD pipeline, Secret Manager, GCP/AWS environment config)

2. Do NOT Load .env Files in Code

No dotenv.load_dotenv() calls in the application.

Instead, read environment variables directly:

import os

DATABASE_URL = os.environ["DATABASE_URL"]

3. Type Safety

Always cast env vars explicitly:

DEBUG_MODE = os.environ.get("DEBUG_MODE", "false").lower() == "true"
MAX_CONNECTIONS = int(os.environ.get("MAX_CONNECTIONS", "10"))

📦 Local Development Flow

Every project must include:

  • .env.exampleCommitted (template only, no real secrets)
  • .envrcCommitted with a single line: dotenv. This instruct direnv to load environment variables from .env in the same director
  • .envIgnored via .gitignore (created manually from example)
  • Always wrap values inside double quotes (even numbers and boolean). This avoids surprises, and provide compatibility across different environments.

Typical .env:

# Database
DATABASE_URL="postgresql://user:password@localhost:5432/db"
MAX_CONNECTIONS="20"

# Redis
REDIS_URL="redis://localhost:6379"

# API Keys
OPENAI_API_KEY="sk-..."

# App Settings
DEBUG_MODE="true"
ENVIRONMENT="local"

🔐 Secrets Management Rules

  • Secrets must never be hardcoded
  • .env must be in .gitignore
  • .env.example must be kept up-to-date
  • Cloud secrets (prod) must be stored in Secret Manager or similar
  • Rotate secrets regularly

⚙️ Good Practices for Using Environment Variables

  • Validate critical env vars at app startup (fail fast)
  • Group related vars logically (DB vars, Redis vars, LLM vars)
  • Document important variables in project README or Notion
  • Avoid leaking secrets in logs or errors

🧵 Environment Variables and Typer CLI

[TODO: Review]

CLI tools that require env vars should expect them to be preloaded (via Docker, local .env, or deployment config).

You may optionally expose fallback flags via CLI, but env vars are the default:

import os
import typer

app = typer.Typer()

@app.command()
def evaluate(model: str = typer.Option(os.environ.get("DEFAULT_MODEL", "gpt-4"))):
    ...

📋 Checklist for Handling Env Vars

  • .env is ignored in .gitignore
  • .env.example is committed and updated
  • App does not load .env manually
  • Critical vars validated at startup
  • Type-safe parsing of env vars
  • No secrets printed in logs
  • Safe secrets management in cloud environments

🧠 Final Note: Treat Environment Like a Contract

The environment is a first-class citizen, just like code and data.

A correct environment setup guarantees that the same code works reliably across local, staging, and production—without hacks, without surprises.