环境变量管理:.env、direnv 与多环境切换
叶间雏菊
4 min read
把数据库密码、API 密钥写进源码并提交到仓库,是新手最常犯也最危险的错误之一。环境变量的作用就是把这些"随环境变化、不该进版本库"的值从代码里剥离出来。管理好它们,等于管好了配置与密钥的边界。配置和密钥虽然都走环境变量,但生命周期不同:配置可以进 .env 明文,密钥不该长期明文落盘。
.env:把配置留在文件里
最朴素的方案是项目根目录放一个 .env,键值对一行一个:
DB_HOST=localhost
DB_PORT=5432
API_KEY=sk_xxxxxxxx
约定俗成:.env 进 .gitignore,永不被提交;仓库里留一份 .env.example 写清需要哪些键、示例值。应用程序启动时读取 .env 注入进程环境。问题在于它是静态的——进到测试环境就得手动换一套值,容易忘、容易混。.env 也不解决"变量从哪来"的来源问题:它是结果,不是机制,真正决定变量何时注入的是启动方式和加载器。
direnv:进目录自动切换环境
direnv 解决的是"进不同项目目录要手动 export 不同变量"的重复劳动。它在目录下放一个 .envrc,进入该目录时自动把里面的变量加载进 shell 环境,离开时自动卸载:
# 项目 A 的 .envrc
export DB_HOST=localhost
export DB_PORT=5432
cd project-a # direnv 提示 "direnv: loading .envrc"
cd ../project-b # 上一套变量自动消失,加载 project-b 的
首次在目录里加 .envrc 需要授权:direnv allow。它的核心机制是 shell hook,在每次提示符渲染前检查当前目录。一个小坑:.envrc 里的变量只在当前 shell 会话有效,cron 任务、IDE 内嵌终端不一定继承,CI 里还是得显式传变量。另一个坑是优先级:direnv 卸载的是它自己加载的变量,若你 shell 里已手动 export 同名变量,进入目录后 direnv 的值未必覆盖你手设的,排查时先用 env | grep KEY 看当前值真正来源。
多环境的现实切法
开发、测试、生产三套环境,变量值不同但键名一致。常见组合:
| 环境 |
做法 |
| 本地开发 |
.env + direnv,按目录隔离 |
| CI |
平台的加密变量/密钥管理,运行时注入 |
| 生产 |
容器或平台的密钥服务挂载,不落盘明文 |
经验上,本地用 direnv 管多项目,CI 和生产用平台自带的密钥能力,能避免把生产密钥写进任何可读文件。不要把 .env 当万能仓库——它适合放配置,不适合放高敏感密钥长期明文留存。生产密钥一旦落盘明文,备份、日志、镜像层都可能带着它扩散,回收成本极高。
几个踩坑点
变量名冲突:direnv 卸载的是它自己加载的变量,若你 shell 里已 export 同名变量,行为可能不符合预期。.envrc 里如果写命令而不是纯 export,每次进目录都会执行,别有副作用操作,否则一个 cd 触发意想不到的改动。容器环境下,ENV 指令和运行时 -e 传参优先级不同,运行时传入通常覆盖镜像内默认值,这一层要心里有数,否则会出现"镜像里明明设了却没生效"的困惑。另外,.env 文件本身不要带 export 前缀却被当成 shell 脚本 source,两种格式互不兼容,混用会静默失败。
把配置和密钥从代码里请出去,是工程化的第一步;选对工具只是让它不那么烦。
0 views
0 likes
0 comments