环境变量将密钥从源代码中分离,让你无需重新部署即可更改配置。.env 文件已成为本地管理环境变量的标准方式。本指南涵盖语法规则、框架配置、安全、环境特定文件、Docker 集成、常见错误和生产替代方案。
.env 语法规则
.env 文件是纯文本文件,每行一个变量。以下是每个开发者都应该知道的规则:
基本语法
每行遵循 KEY=VALUE 模式,等号周围不加空格。
# .env
DATABASE_URL=postgres://localhost:5432/mydb
API_KEY=sk-1234567890abcdef
PORT=3000
DEBUG=true引号规则
值可以不加引号、用单引号或双引号,行为各不相同:
# 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"- 不加引号:尾部空白被去除,无转义序列。
- 单引号:值按字面量处理,无插值,无转义。
- 双引号:支持转义序列(\n, \t)和变量插值。
多行值
使用双引号和 \n 表示换行,或在双引号内使用实际换行:
# 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-----"注释
以 # 开头的行是注释。在某些解析器中,不加引号的值可以使用行内注释:
# 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变量插值
使用 ${VAR} 语法引用其他变量(大多数解析器在双引号值中支持):
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}框架配置
Node.js (dotenv)
Node.js 最流行的 .env 加载器。安装并配置:
# 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.localNext.js、Vite 和 Create React App 会自动加载 .env 文件,无需额外安装包。
Python (python-dotenv)
在 Python 项目中加载 .env:
# 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)
在 Go 项目中加载 .env:
// 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)
Laravel 和大多数 PHP 框架使用:
// 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)
Rails 和其他 Ruby 项目使用:
# 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安全:永远不要提交 .env 文件
.env 文件包含密钥(API 密钥、数据库密码、令牌)。将其提交到版本控制是第一大安全错误。
.gitignore 模式
立即将以下模式添加到 .gitignore 文件:
# .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使用 .env.example
提交一个值为空的 .env.example 文件,让团队成员知道需要哪些变量:
# .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已经提交了 .env?
如果你不小心提交了 .env 文件,将其从跟踪中移除并轮换所有密钥:
# 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环境特定文件
大多数框架支持多个 .env 文件用于不同环境。理解加载顺序至关重要。
加载顺序(Next.js / Vite / CRA)
文件按以下优先级加载(后面的文件覆盖前面的):
# 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 始终被 git 忽略(将其添加到 .gitignore)。测试时不会加载此文件,以保持测试的确定性。
哪个文件用于什么?
| File | Purpose | Git |
|---|---|---|
| .env | Default values, non-secret config | Commit |
| .env.example | Template with empty values | Commit |
| .env.local | Local secrets & overrides | Ignore |
| .env.development | Dev-specific (shared) | Commit |
| .env.production | Production-specific (non-secret) | Commit |
| .env.test | Test environment config | Commit |
| .env.production.local | Production secrets (local only) | Ignore |
Docker 和 Docker Compose 集成
env_file 指令
Docker Compose 可以直接将 .env 文件加载到容器中:
# 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/dataenvironment vs env_file
你也可以内联环境变量。区别如下:
# 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:从文件加载,保持 compose.yml 整洁,便于按环境切换。
- environment:在 compose.yml 中可见,适合非敏感值,支持变量替换。
.env 用于 Docker Compose 变量
Docker Compose 自动读取项目根目录的 .env 文件,用于 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: myapp10 个常见 .env 错误及修复
以下是开发者最常遇到的 .env 问题:
| # | 错误 | 原因 | 修复 |
|---|---|---|---|
| 1 | process.env.VAR 为 undefined | dotenv 未加载或在使用后才加载 | 在入口文件最顶部调用 dotenv.config() |
| 2 | 生产环境中变量为空 | .env 文件未部署;依赖文件而非平台环境变量 | 在托管平台(Vercel、AWS 等)中设置环境变量 |
| 3 | .env 值中包含引号 | 解析器将引号视为字面字符 | 查看解析器文档;大多数会去除双引号但并非全部 |
| 4 | 加载了错误的 .env 文件 | 工作目录与预期路径不同 | 使用 path 选项:dotenv.config({ path: ".env.local" }) |
| 5 | 多行值被截断 | 值未正确使用双引号包裹 | 用双引号包裹多行值 |
| 6 | 特殊字符破坏值 | $ 或 # 被解释为插值/注释 | 使用单引号防止插值,或用 \ 转义 |
| 7 | BOM 编码错误 | Windows 编辑器以 UTF-8 BOM 保存 .env | 保存为不带 BOM 的 UTF-8;否则第一个变量可能无法读取 |
| 8 | Docker 容器环境变量为空 | env_file 路径不正确或文件不在构建上下文中 | 确认路径相对于 compose.yml 的位置 |
| 9 | = 周围的空格导致解析失败 | KEY = VALUE 而非 KEY=VALUE | 删除等号周围的空格 |
| 10 | 浏览器中变量不可用(React/Next) | 缺少必需的前缀(NEXT_PUBLIC_ 或 REACT_APP_) | 添加框架要求的前缀以暴露给客户端代码 |
生产替代方案
在生产环境中,不建议使用 .env 文件。请使用以下替代方案:
平台环境变量
每个主要托管平台都提供 UI 或 CLI 来设置环境变量:
# 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
对于 Docker Swarm 或 Compose,使用 secrets 处理敏感数据:
# 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();密钥管理器(Vault, AWS Secrets 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);密钥管理器提供轮换、审计日志、访问控制和静态加密——这些是 .env 文件无法提供的功能。
常见问题
应该将 .env 文件提交到 Git 吗?
永远不要将包含密钥(API 密钥、密码、令牌)的 .env 文件提交到版本控制。相反,提交一个值为空的 .env.example 文件,让团队成员知道需要哪些变量。在 .gitignore 文件中添加 .env* 模式。
.env、.env.local 和 .env.production 有什么区别?
.env 包含所有环境加载的默认值。.env.local 包含本地覆盖,不提交到 git。.env.production 包含仅在 NODE_ENV=production 时加载的生产特定值。加载顺序为:.env、.env.local、.env.[环境]、.env.[环境].local,后面的文件覆盖前面的。
为什么 Node.js 中 process.env.MY_VAR 是 undefined?
最常见的原因是:1) 忘记安装并在入口文件顶部调用 require("dotenv").config(),2) .env 文件不在 Node 运行的根目录中,3) 变量名拼写错误,4) 使用 ES 模块需要用 "import dotenv/config"。
如何在 Docker Compose 中使用 .env 变量?
Docker Compose 自动读取与 compose.yml 同目录的 .env 文件,使用 ${VAR} 语法进行变量替换。要将变量传入容器,使用 env_file 指令从文件加载,或使用 environment 键在 compose.yml 中内联设置。
生产环境可以使用 .env 文件吗?
不建议。在生产环境中,使用平台提供的环境变量(Vercel、AWS、Heroku 控制面板)、Docker secrets 或密钥管理器如 HashiCorp Vault 或 AWS Secrets Manager。这些提供更好的安全性,包括加密、轮换、审计日志和访问控制。
如何在 React 或 Next.js 中将 .env 变量暴露给浏览器?
在 Next.js 中,变量名前缀为 NEXT_PUBLIC_(如 NEXT_PUBLIC_API_URL)。在 Create React App 中使用 REACT_APP_ 前缀。在 Vite 中使用 VITE_ 前缀。只有带前缀的变量才会嵌入客户端包。永远不要用这种方式暴露密钥,仅用于公共配置如 API 端点。