DevToolBox免费
博客

.env 文件指南:环境变量最佳实践

10 分钟阅读作者 DevToolBox

环境变量将密钥从源代码中分离,让你无需重新部署即可更改配置。.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.local

Next.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)。测试时不会加载此文件,以保持测试的确定性。

哪个文件用于什么?

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 集成

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/data

environment 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: myapp

10 个常见 .env 错误及修复

以下是开发者最常遇到的 .env 问题:

#错误原因修复
1process.env.VAR 为 undefineddotenv 未加载或在使用后才加载在入口文件最顶部调用 dotenv.config()
2生产环境中变量为空.env 文件未部署;依赖文件而非平台环境变量在托管平台(Vercel、AWS 等)中设置环境变量
3.env 值中包含引号解析器将引号视为字面字符查看解析器文档;大多数会去除双引号但并非全部
4加载了错误的 .env 文件工作目录与预期路径不同使用 path 选项:dotenv.config({ path: ".env.local" })
5多行值被截断值未正确使用双引号包裹用双引号包裹多行值
6特殊字符破坏值$ 或 # 被解释为插值/注释使用单引号防止插值,或用 \ 转义
7BOM 编码错误Windows 编辑器以 UTF-8 BOM 保存 .env保存为不带 BOM 的 UTF-8;否则第一个变量可能无法读取
8Docker 容器环境变量为空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 端点。

𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

🐳Docker Compose Generator.gi.gitignore GeneratorNXNginx Config Generator

相关文章

Docker Compose 速查表:服务、卷和网络

Docker Compose 核心参考:服务定义、卷挂载、网络配置、环境变量和常见栈示例。

Docker Compose env_file vs environment:何时使用哪个(附示例)

理解 Docker Compose 中 env_file 和 environment 的区别。何时使用、变量优先级、.env 文件行为和多环境设置。