环境变量与 .env 文件:从入门到实战

说实话,环境变量这东西,看似简单,但坑特别多。

我在刚入门的时候也踩过雷——把数据库密码写死在配置里,结果代码被同事fork到GitHub上…嗯,那个周末我过得相当难忘。

今天我们就来聊聊,怎么用环境变量和 .env 文件,把敏感信息安全地藏起来。

为什么需要环境变量?

你想想看,如果你的数据库密码写死在代码里,每次要改密码都要重新打包部署,多麻烦。

更可怕的是,代码一旦被上传到公开仓库,密码就等于公开了。

环境变量就是来解决这个问题的——它把配置从代码里抽出来,放到运行时的上下文里去。

我个人习惯用环境变量来管理以下信息:

  1. 数据库连接字符串 — 用户名、密码、主机地址
  2. 第三方 — AWS、Stripe、SendGrid 等服务的令牌
  3. 应用配置项 — 运行环境(开发/测试/生产)、日志级别、端口号
  4. 加密密钥 — JWT签名密钥、加密盐值

这样做的好处很明显:同一个代码库,可以部署到不同的环境,只要环境变量不一样,跑出来的就是不同配置的应用。

关键点:环境变量是运行时的,不是代码里的。你只要把变量名写进代码,真正的值永远留在服务器上。

.env 文件:开发者的“小抄”

在开发环境,我们不可能每次都手动设置环境变量。太累了。

.env 文件就是解决这个痛点的——它把环境变量存成一个文件,方便本地调试。

一个典型的 .env 文件长这样:

# 数据库配置
DB_HOST=localhost
DB_PORT=5432
DB_USER=myapp_user
DB_PASS=supersecret123

# 外部服务
STRIPE_=sk_test_xxxxxxxxxxxx
SENDGRID_=SG.xxxxx.xxxxx

# 应用配置
NODE_ENV=development
APP_PORT=3000
JWT_SECRET=my-local-secret-key

格式其实很简单:KEY=VALUE,每行一个,# 开头的行是注释。

但这里有个大坑⚠️ —— 千万别把 .env 提交到Git仓库里。

我以前有一次赶进度,顺手把 .env 文件加进了 git add .,结果那几分钟里所有开发同事都看到我的本地密钥了……还好只是开发环境的,不然就出大事了。

正确的做法是:

  • 把 .env 添加到 .gitignore 文件里
  • 创建一个 .env.example 或 .env.sample 文件,里面只写变量名,不写真实值
  • 在项目文档里告诉新同事:「复制 .env.example 为 .env,然后填上你自己的密钥」

我踩过的坑:有一次团队新成员没看文档,直接复制了别人的 .env 文件,结果测试环境被用了生产环境的数据库——数据全乱套了。从那以后,我强制在CI里加了检查:发现 .env 文件就立刻中断构建。

dotenv 库:环境变量的统一管理

光有 .env 文件还不够,你的代码还得能读这个文件。

不同语言有自己的实现方式,但最流行的是 dotenv 这个库。它把 .env 文件里的内容加载到 process.env 里,让代码可以直接使用。

在Node.js项目里,安装特别简单:

npm install dotenv

然后在你应用的入口文件最前面加上一行:

// 加载 .env 文件到 process.env
require('dotenv').config();

// 现在就可以用 process.env 来读取变量了
const dbPassword = process.env.DB_PASS;
const stripeKey = process.env.STRIPE_;

console.log('数据库密码(只在开发环境显示):', dbPassword);

注意 require('dotenv').config() 这一行必须放在代码的最前面。为什么?因为后续的代码才能读到这些变量。如果放在中间加载,前面那些代码就读不到了。

dotenv 还支持一些高级配置:

配置项 说明 使用场景
path 指定 .env 文件的路径 项目里有多个环境文件(.env.development、.env.production)时用
override 是否覆盖已有的环境变量 默认不覆盖(已有的变量优先级更高),需要覆盖时设为 true
encoding 文件编码 默认 utf-8,一般不用改

举个例子,如果你的项目有单独的测试环境配置:

// 加载测试环境的 .env 文件
dotenv.config({
  path: '.env.test',
  override: true
});

环境变量的优先级

这里有个顺序问题,很多人搞混。

环境变量的值来自多个地方,它们有个优先级顺序(从高到低):

  1. 系统环境变量 — 直接在操作系统里设的(比如 export、set、系统设置)
  2. 命令行临时变量 — 比如 DB_PASS=xxx node app.js
  3. dotenv 加载的 .env 文件
  4. 代码里的默认值 — 比如 process.env.PORT || 3000

dotenv 默认不会覆盖系统环境变量。所以如果你在正式环境里已经设了 DB_PASS,那 .env 文件里同名的变量是不会生效的。

这个设计很合理——因为生产环境应该靠系统环境变量来控制,.env 文件只是开发时用的辅助工具。

建议:在部署流程里,永远不要依赖 .env 文件。生产环境应该通过 CI/CD 或者云平台控制台来设置环境变量。我一般会在 CI 脚本里专门写一步「注入环境变量」,而把 .env 文件留在开发机里。

当环境变量不够用时

你可能会遇到这种情况:项目需要的变量太多,有几十个,都放在 .env 里显得很乱。

这时候可以考虑分类管理:

  • 用多个 .env 文件:.env.db、.env.api、.env.cache
  • 或者用 YAML/JSON 配置文件,但要确保它们不被提交到仓库
  • 我个人更倾向于用云平台的密钥管理服务(比如 AWS Secrets Manager、HashiCorp Vault),但这些是后面课程的内容

不过对于大部分中小型项目,单一 .env 文件加上 dotenv 就足够了。保持简单,别过度设计。

SVG 图:环境变量与 .env 工作流程

环境变量与 .env 集成工作流程 👩‍💻 开发人员 📄 .env 文件 ⚙️ dotenv 库 🖥️ 系统环境变量 🚀 应用程序 数据库 / 注意 .env 文件只用于开发 生产环境用系统变量 永不上传到仓库 ① ② ③ ④

我的经验总结

用了几年的环境变量和 .env 文件,我总结了几条铁律:

  • 开发环境用 .env,生产环境用系统变量 — 两者不要混用
  • 永远不要提交 .env — 在 .gitignore 里把它写死
  • 变量名用 UPPER_SNAKE_CASE — 这样一眼就能看出来是环境变量
  • 提供 .env.example 文件 — 新同事来了不用猜,直接复制粘贴就能跑起来
  • 在应用启动时验证必须的变量 — 如果缺少某个关键变量,直接报错退出,别等运行到一半才崩溃

加载完 .env 文件之后,加一段验证代码是个好习惯:

// 验证必要环境变量是否存在
const requiredEnvVars = ['DB_PASS', 'STRIPE_', 'JWT_SECRET'];
const missing = requiredEnvVars.filter(key => !process.env[key]);

if (missing.length > 0) {
  console.error('❌ 缺少必要的环境变量:', missing.join(', '));
  process.exit(1);
}

console.log('✅ 环境变量验证通过');

你想想看,这么做了之后,再也不用担心上线前忘记配环境变量了。