DevToolBoxฟรี
บล็อก

คู่มือ .env: แนวทางปฏิบัติที่ดีที่สุดสำหรับ Environment Variables

10 นาทีในการอ่านโดย DevToolBox

Environment variables keep secrets out of source code and let you change configuration without redeploying. The .env file has become the standard way to manage them locally. This guide covers syntax rules, framework setup, security, environment-specific files, Docker integration, common errors, and production alternatives.

.env Syntax Rules

A .env file is a plain-text file with one variable per line. Here are the rules every developer should know:

Basic Syntax

Each line follows the KEY=VALUE pattern. No spaces around the equals sign.

# .env
DATABASE_URL=postgres://localhost:5432/mydb
API_KEY=sk-1234567890abcdef
PORT=3000
DEBUG=true

Quoting Rules

Values can be unquoted, single-quoted, or double-quoted. The behavior differs:

# Unquoted — trailing whitespace trimmed
APP_NAME=My Application

# Single quotes — literal, no interpolation
PASSWORD='p@$$w0rd#123'

# Double quotes — escape sequences + interpolation
GREETING="Hello\nWorld"
FULL_URL="${BASE_URL}/api/v1"
  • Unquoted: trailing whitespace is trimmed, no escape sequences.
  • Single-quoted: value is taken literally, no interpolation, no escapes.
  • Double-quoted: supports escape sequences (\n, \t) and variable interpolation.

Multiline Values

Use double quotes and \n for newlines, or use actual newlines inside double quotes:

# Method 1: \n in double quotes
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIB...\n-----END RSA PRIVATE KEY-----"

# Method 2: actual newlines in double quotes
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
-----END RSA PRIVATE KEY-----"

Comments

Lines starting with # are comments. Inline comments work only with unquoted values in some parsers:

# This is a full-line comment
DATABASE_URL=postgres://localhost/mydb  # inline comment (some parsers)

# Empty lines are ignored

API_KEY=abc123  # This may or may not work depending on the parser

Variable Interpolation

Reference other variables using ${VAR} syntax (supported by most parsers in double-quoted values):

BASE_URL=https://api.example.com
API_V1=${BASE_URL}/v1
API_V2=${BASE_URL}/v2

DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp
DATABASE_URL=postgres://${DB_HOST}:${DB_PORT}/${DB_NAME}

Framework Setup

Node.js (dotenv)

The most popular .env loader for Node.js. Install and configure:

# Install
npm install dotenv

# --- app.js (CommonJS) ---
require('dotenv').config();
console.log(process.env.DATABASE_URL);

# --- app.ts (ES Modules) ---
import 'dotenv/config';
console.log(process.env.DATABASE_URL);

# --- Or load from CLI ---
node -r dotenv/config app.js
node -r dotenv/config app.js dotenv_config_path=.env.local

Next.js, Vite, and Create React App load .env files automatically -- no package needed.

Python (python-dotenv)

Load .env in Python projects:

# Install
pip install python-dotenv

# --- app.py ---
from dotenv import load_dotenv
import os

load_dotenv()  # loads .env from current directory
# load_dotenv('.env.local')  # or specify a path

database_url = os.getenv('DATABASE_URL')
print(database_url)

# --- Django: manage.py or settings.py ---
from dotenv import load_dotenv
from pathlib import Path

env_path = Path('.') / '.env'
load_dotenv(dotenv_path=env_path)

Go (godotenv)

Load .env in Go projects:

// Install
// go get github.com/joho/godotenv

package main

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

func main() {
    err := godotenv.Load()  // loads .env
    if err != nil {
        log.Fatal("Error loading .env file")
    }
    dbURL := os.Getenv("DATABASE_URL")
    fmt.Println(dbURL)
}

PHP (vlucas/phpdotenv)

Used by Laravel and most PHP frameworks:

// Install
// composer require vlucas/phpdotenv

<?php
require 'vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();

// Access variables
$dbUrl = $_ENV['DATABASE_URL'];
// or
$dbUrl = getenv('DATABASE_URL');

// Required variables (throws exception if missing)
$dotenv->required(['DATABASE_URL', 'API_KEY']);

Ruby (dotenv gem)

Used by Rails and other Ruby projects:

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

# Or for non-Rails Ruby:
gem 'dotenv'

# --- app.rb ---
require 'dotenv/load'
puts ENV['DATABASE_URL']

# --- Rails: .env is loaded automatically ---
# Access via ENV['DATABASE_URL'] anywhere in your app

Security: Never Commit .env Files

Your .env file contains secrets (API keys, database passwords, tokens). Committing it to version control is the #1 security mistake.

.gitignore Patterns

Add these patterns to your .gitignore file immediately:

# .gitignore

# Ignore all .env files
.env
.env.local
.env.*.local
.env.development
.env.production

# More aggressive pattern — ignore all .env variants
.env*

# But DO commit the example file
!.env.example

Use .env.example

Commit a .env.example file with empty values so teammates know which variables are required:

# .env.example — commit this file
# Copy to .env and fill in your values

DATABASE_URL=
API_KEY=
SMTP_HOST=
SMTP_PORT=587
SMTP_USER=
SMTP_PASS=
REDIS_URL=
JWT_SECRET=

# Optional
DEBUG=false
LOG_LEVEL=info

Already Committed .env?

If you accidentally committed a .env file, remove it from tracking and rotate all secrets:

# Step 1: Remove .env from Git tracking (keeps local file)
git rm --cached .env

# Step 2: Add to .gitignore
echo ".env" >> .gitignore

# Step 3: Commit the removal
git add .gitignore
git commit -m "Remove .env from tracking, add to .gitignore"

# Step 4: CRITICAL — Rotate ALL secrets in the .env file
# Every API key, password, and token that was exposed
# must be regenerated immediately

Environment-Specific Files

Most frameworks support multiple .env files for different environments. Understanding the loading order is critical.

Loading Order (Next.js / Vite / CRA)

Files are loaded in this priority (later files override earlier ones):

# Loading priority (highest to lowest):

# 1. Shell environment variables (always win)
# 2. .env.{NODE_ENV}.local   (e.g. .env.production.local)
# 3. .env.local               (NOT loaded in test)
# 4. .env.{NODE_ENV}          (e.g. .env.production)
# 5. .env                     (default fallback)

# Example for NODE_ENV=production:
# .env                     → loaded first (base defaults)
# .env.production          → overrides .env
# .env.local               → overrides .env.production
# .env.production.local    → overrides everything above

.env.local is always ignored by git (add it to .gitignore). It is NOT loaded during testing to keep tests deterministic.

Which File for What?

FilePurposeGit
.envDefault values, non-secret configCommit
.env.exampleTemplate with empty valuesCommit
.env.localLocal secrets & overridesIgnore
.env.developmentDev-specific (shared)Commit
.env.productionProduction-specific (non-secret)Commit
.env.testTest environment configCommit
.env.production.localProduction secrets (local only)Ignore

Docker & Docker Compose Integration

env_file Directive

Docker Compose can load .env files directly into containers:

# docker-compose.yml (or compose.yml)
services:
  web:
    image: node:20-alpine
    env_file:
      - .env                  # base defaults
      - .env.production       # production overrides
    ports:
      - "3000:3000"

  db:
    image: postgres:16
    env_file:
      - .env.db               # separate file for DB secrets
    volumes:
      - pgdata:/var/lib/postgresql/data

environment vs env_file

You can also inline environment variables. Here is the difference:

# Using env_file (loads from file)
services:
  web:
    env_file:
      - .env

# Using environment (inline)
services:
  web:
    environment:
      - NODE_ENV=production
      - PORT=3000
      - DATABASE_URL=${DATABASE_URL}   # from shell or .env

# Using environment (mapping syntax)
services:
  web:
    environment:
      NODE_ENV: production
      PORT: 3000
  • env_file: loads from file, keeps compose.yml clean, easy to swap per environment.
  • environment: visible in compose.yml, good for non-secret values, supports variable substitution.

.env for Docker Compose Variables

Docker Compose automatically reads a .env file in the project root for variable substitution in compose.yml:

# .env (in same directory as compose.yml)
POSTGRES_VERSION=16
NODE_VERSION=20
APP_PORT=3000

# compose.yml — uses .env for variable substitution
services:
  web:
    image: node:${NODE_VERSION}-alpine
    ports:
      - "${APP_PORT}:3000"

  db:
    image: postgres:${POSTGRES_VERSION}
    environment:
      POSTGRES_DB: myapp

10 Common .env Errors and Fixes

Here are the most frequent .env problems developers encounter:

#ErrorCauseFix
1process.env.VAR is undefineddotenv not loaded or loaded after usageCall dotenv.config() at the very top of your entry file
2Variables empty in production.env file not deployed; relying on file instead of platform env varsSet env vars in your hosting platform (Vercel, AWS, etc.)
3.env values have quotes in themParser treats quotes as literal charactersCheck your parser docs; most strip double quotes but not all
4Wrong .env file loadedWorking directory differs from expected pathUse path option: dotenv.config({ path: ".env.local" })
5Multiline value truncatedValue not properly double-quotedWrap multiline values in double quotes
6Special characters break value$ or # interpreted as interpolation/commentUse single quotes to prevent interpolation, or escape with \
7BOM encoding error.env saved with UTF-8 BOM by Windows editorsSave as UTF-8 without BOM; first variable may be unreadable otherwise
8Docker container env vars emptyenv_file path incorrect or file not in build contextVerify path is relative to compose.yml location
9Spaces around = break parsingKEY = VALUE instead of KEY=VALUERemove spaces around the equals sign
10Variables not available in browser (React/Next)Missing required prefix (NEXT_PUBLIC_ or REACT_APP_)Add the framework-required prefix to expose to client-side code

Production Alternatives

In production, .env files are not recommended. Use these alternatives instead:

Platform Environment Variables

Every major hosting platform provides a UI or CLI for setting environment variables:

# Vercel
vercel env add DATABASE_URL production
vercel env ls

# AWS (Parameter Store)
aws ssm put-parameter \
  --name "/myapp/prod/DATABASE_URL" \
  --value "postgres://..." \
  --type SecureString

# Heroku
heroku config:set DATABASE_URL=postgres://...
heroku config

# Railway
railway variables set DATABASE_URL=postgres://...

# Fly.io
fly secrets set DATABASE_URL=postgres://...

Docker Secrets

For Docker Swarm or Compose, use secrets for sensitive data:

# compose.yml with Docker secrets
services:
  web:
    image: myapp:latest
    secrets:
      - db_password
      - api_key
    environment:
      DB_PASSWORD_FILE: /run/secrets/db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt    # for Compose
    # external: true                    # for Swarm
  api_key:
    file: ./secrets/api_key.txt

# In your app, read from file:
# const secret = fs.readFileSync('/run/secrets/db_password', 'utf8').trim();

Secret Managers (Vault, AWS Secrets Manager)

For enterprise applications, use a dedicated secret manager:

# HashiCorp Vault
vault kv put secret/myapp/production \
  DATABASE_URL="postgres://..." \
  API_KEY="sk-..."

# Read in application
vault kv get -field=DATABASE_URL secret/myapp/production

# AWS Secrets Manager
aws secretsmanager create-secret \
  --name "myapp/production/db" \
  --secret-string '{"url":"postgres://...","password":"..."}'

# Read in Node.js with AWS SDK
import { SecretsManager } from '@aws-sdk/client-secrets-manager';
const client = new SecretsManager({ region: 'us-east-1' });
const { SecretString } = await client.getSecretValue({
  SecretId: 'myapp/production/db'
});
const secrets = JSON.parse(SecretString);

Secret managers provide rotation, audit logging, access control, and encryption at rest -- features .env files cannot offer.

Frequently Asked Questions

Should I commit my .env file to Git?

Never commit .env files that contain secrets (API keys, passwords, tokens) to version control. Instead, commit a .env.example file with empty placeholder values so teammates know which variables are needed. Add .env* patterns to your .gitignore file.

What is the difference between .env, .env.local, and .env.production?

.env contains default values loaded in all environments. .env.local contains local overrides and is not committed to git. .env.production contains production-specific values loaded only when NODE_ENV=production. The loading order is: .env, then .env.local, then .env.[environment], then .env.[environment].local, with later files overriding earlier ones.

Why is process.env.MY_VAR undefined in Node.js?

The most common causes are: 1) You forgot to install and call require("dotenv").config() at the top of your entry file, 2) Your .env file is not in the root directory where Node runs, 3) The variable name has a typo, 4) You are using ES modules and need to use "import dotenv/config" instead.

How do I use .env variables in Docker Compose?

Docker Compose automatically reads a .env file in the same directory as compose.yml for variable substitution using ${VAR} syntax. For passing variables into containers, use the env_file directive to load from a file, or the environment key to set them inline in compose.yml.

Can I use .env files in production?

It is not recommended. In production, use platform-provided environment variables (Vercel, AWS, Heroku dashboards), Docker secrets, or a secret manager like HashiCorp Vault or AWS Secrets Manager. These provide better security with encryption, rotation, audit logging, and access control.

How do I expose .env variables to the browser in React or Next.js?

In Next.js, prefix variables with NEXT_PUBLIC_ (e.g., NEXT_PUBLIC_API_URL). In Create React App, use REACT_APP_ prefix. In Vite, use VITE_ prefix. Only prefixed variables are embedded in the client bundle. Never expose secrets this way -- only use it for public configuration like API endpoints.

𝕏 Twitterin LinkedIn
บทความนี้มีประโยชน์ไหม?

อัปเดตข่าวสาร

รับเคล็ดลับการพัฒนาและเครื่องมือใหม่ทุกสัปดาห์

ไม่มีสแปม ยกเลิกได้ตลอดเวลา

ลองเครื่องมือที่เกี่ยวข้อง

🐳Docker Compose Generator.gi.gitignore GeneratorNXNginx Config Generator

บทความที่เกี่ยวข้อง

Docker Compose Cheat Sheet: Services, Volumes และ Networks

อ้างอิง Docker Compose: นิยาม service, volume, network, environment variable และตัวอย่าง stack

Docker Compose env_file vs environment: ใช้เมื่อไหร่ (พร้อมตัวอย่าง)

เข้าใจความแตกต่างระหว่าง env_file และ environment ใน Docker Compose