Zum Inhalt springen

dotenv Cheat Sheet

Overview

dotenv is a convention and set of libraries for loading environment variables from .env files into application processes. Instead of setting environment variables system-wide or in shell profiles, dotenv reads key-value pairs from a .env file in the project root and makes them available via the standard environment variable APIs of each language.

The dotenv pattern is implemented in virtually every programming language: python-dotenv for Python, dotenv for Node.js, godotenv for Go, dotenv gem for Ruby, and many more. This approach keeps sensitive configuration like API keys and database credentials out of source code while making local development configuration simple.

Installation

Node.js

npm install dotenv
# or
yarn add dotenv

Python

pip install python-dotenv

Ruby

gem install dotenv
# or add to Gemfile
# gem 'dotenv-rails', groups: [:development, :test]

Go

go get github.com/joho/godotenv

CLI Tool (direnv alternative)

# dotenvx - cross-platform CLI
curl -sfS https://dotenvx.sh | sh

# Or via npm
npm install -g @dotenvx/dotenvx

.env File Format

# .env file — key=value pairs
DATABASE_URL=postgres://user:pass@localhost:5432/mydb
REDIS_URL=redis://localhost:6379
API_KEY=sk-abc123def456
SECRET_KEY=mysupersecretkey

# Quoted values (preserves whitespace)
APP_NAME="My Application"
GREETING='Hello World'

# Multiline values
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA...
-----END RSA PRIVATE KEY-----"

# Variable expansion (supported by some implementations)
BASE_URL=https://api.example.com
AUTH_URL=${BASE_URL}/auth
USERS_URL=${BASE_URL}/users

# Comments
# This is a comment
DATABASE_HOST=localhost  # Inline comments (some implementations)

# Empty values
EMPTY_VAR=
ALSO_EMPTY=''

# Export prefix (works with shell sourcing)
export SHELL_COMPAT=true

Usage by Language

Node.js

// Load at application entry point
require('dotenv').config();

// Access variables
const dbUrl = process.env.DATABASE_URL;
const port = process.env.PORT || 3000;

// Custom path
require('dotenv').config({ path: '.env.local' });

// Multiple files (dotenvx or dotenv-expand)
require('dotenv').config({ path: '.env' });
require('dotenv').config({ path: '.env.local', override: true });

// ES modules
import 'dotenv/config';
// or
import dotenv from 'dotenv';
dotenv.config();

Python

from dotenv import load_dotenv
import os

# Load .env file
load_dotenv()

# Access variables
db_url = os.getenv('DATABASE_URL')
debug = os.getenv('DEBUG', 'false').lower() == 'true'
port = int(os.getenv('PORT', '8000'))

# Custom path
load_dotenv('.env.production')

# Override existing env vars
load_dotenv(override=True)

# Find .env automatically
from dotenv import find_dotenv
load_dotenv(find_dotenv())

Go

package main

import (
    "fmt"
    "os"
    "github.com/joho/godotenv"
)

func main() {
    err := godotenv.Load()  // Loads .env
    if err != nil {
        log.Println("No .env file found")
    }

    dbURL := os.Getenv("DATABASE_URL")
    fmt.Println("DB:", dbURL)
}

// Load specific file
godotenv.Load(".env.production")

// Load multiple files (first takes precedence)
godotenv.Load(".env.local", ".env")

Ruby

# Gemfile
gem 'dotenv', groups: [:development, :test]

# config/application.rb (Rails)
require 'dotenv/load'

# Manual loading
require 'dotenv'
Dotenv.load('.env.local', '.env')

# Access
db_url = ENV['DATABASE_URL']

Configuration

File Hierarchy (Convention)

# Common .env file hierarchy (most specific wins)
.env                  # Default values, committed to repo
.env.local            # Local overrides, gitignored
.env.development      # Development-specific
.env.test             # Test-specific
.env.production       # Production-specific
.env.development.local  # Local dev overrides

.gitignore Setup

# .gitignore
.env.local
.env.*.local
.env.production
.env.staging

# Keep template committed
!.env.example
!.env.template

Template File

# .env.example (committed to repo)
DATABASE_URL=postgres://user:password@localhost:5432/mydb
REDIS_URL=redis://localhost:6379
API_KEY=your-api-key-here
SECRET_KEY=generate-a-secret-key
DEBUG=true
PORT=3000

Advanced Usage

dotenvx CLI

# Run any command with .env loaded
dotenvx run -- node server.js
dotenvx run -- python app.py

# Use specific env file
dotenvx run -f .env.production -- node server.js

# Multiple env files
dotenvx run -f .env -f .env.local -- node server.js

# Encrypt .env file
dotenvx encrypt

# Decrypt .env file
dotenvx decrypt

Variable Expansion

# .env with expansion (requires dotenv-expand or dotenvx)
HOST=localhost
PORT=5432
DATABASE_URL=postgres://user:pass@${HOST}:${PORT}/mydb

# Default values
CACHE_TTL=${CACHE_TTL:-3600}
// Node.js with expansion
const dotenv = require('dotenv');
const dotenvExpand = require('dotenv-expand');

const config = dotenv.config();
dotenvExpand.expand(config);

Shell Integration

# Source .env in shell (basic approach)
export $(cat .env | grep -v '^#' | xargs)

# Safer approach with set
set -a
source .env
set +a

# Using env command
env $(cat .env | grep -v '^#' | xargs) node server.js

# Docker Compose reads .env automatically
docker compose up  # Reads .env in same directory

Framework-Specific Patterns

Next.js

# .env.local (gitignored, all environments)
DATABASE_URL=postgres://localhost/mydb

# .env.development (dev only)
NEXT_PUBLIC_API_URL=http://localhost:3000/api

# Prefix with NEXT_PUBLIC_ for browser access
NEXT_PUBLIC_ANALYTICS_ID=UA-12345
# Server-only (no prefix)
SECRET_KEY=mysecret

Django

# settings.py
from dotenv import load_dotenv
load_dotenv()

SECRET_KEY = os.getenv('SECRET_KEY')
DEBUG = os.getenv('DEBUG', 'False') == 'True'
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.getenv('DB_NAME'),
        'HOST': os.getenv('DB_HOST', 'localhost'),
    }
}

Troubleshooting

IssueSolution
Variables not loadingEnsure dotenv.config() is called before accessing vars
.env changes not reflectedRestart the application; dotenv reads at startup
Variable expansion not workingInstall dotenv-expand or use dotenvx
Quotes included in valueCheck if your implementation strips quotes
Production values in .envUse .env.production and proper deployment secrets
.env committed to GitAdd to .gitignore; rotate any exposed secrets
# Verify .env is readable
cat .env

# Check if variable is set
echo $DATABASE_URL
node -e "require('dotenv').config(); console.log(process.env.DATABASE_URL)"
python -c "from dotenv import load_dotenv; load_dotenv(); import os; print(os.getenv('DATABASE_URL'))"

# Debug dotenv loading (Node.js)
require('dotenv').config({ debug: true })