环境变量与 .env 文件:从入门到实战
说实话,环境变量这东西,看似简单,但坑特别多。
我在刚入门的时候也踩过雷——把数据库密码写死在配置里,结果代码被同事fork到GitHub上…嗯,那个周末我过得相当难忘。
今天我们就来聊聊,怎么用环境变量和 .env 文件,把敏感信息安全地藏起来。
为什么需要环境变量?
你想想看,如果你的数据库密码写死在代码里,每次要改密码都要重新打包部署,多麻烦。
更可怕的是,代码一旦被上传到公开仓库,密码就等于公开了。
环境变量就是来解决这个问题的——它把配置从代码里抽出来,放到运行时的上下文里去。
我个人习惯用环境变量来管理以下信息:
- 数据库连接字符串 — 用户名、密码、主机地址
- 第三方 — AWS、Stripe、SendGrid 等服务的令牌
- 应用配置项 — 运行环境(开发/测试/生产)、日志级别、端口号
- 加密密钥 — 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
});
环境变量的优先级
这里有个顺序问题,很多人搞混。
环境变量的值来自多个地方,它们有个优先级顺序(从高到低):
- 系统环境变量 — 直接在操作系统里设的(比如 export、set、系统设置)
- 命令行临时变量 — 比如
DB_PASS=xxx node app.js - dotenv 加载的
.env文件 - 代码里的默认值 — 比如
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,生产环境用系统变量 — 两者不要混用 - 永远不要提交
.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('✅ 环境变量验证通过');
你想想看,这么做了之后,再也不用担心上线前忘记配环境变量了。