用 dotenvx 管理多环境配置:Git 协作、加密与部署
目录(14)
用 dotenvx 管理多环境配置:Git 协作、加密与部署
环境变量刚开始往往很简单:每个人复制一份 .env.example,再填上自己的配置。
随着服务和环境增多,团队很快会遇到这些问题:
- 新增变量后,只能靠群消息提醒其他人;
- 测试、预发布和生产配置散落在不同机器;
- 部署迁移时,不知道哪一份才是当前版本;
- 明文密钥不能进入 Git,但完全脱离 Git 又失去了版本协作;
- 有人误把本地
.env提交,直到凭证泄露后才发现。
dotenvx 提供了一种折中方案:把加密后的 .env 文件纳入版本控制,
把解密私钥放在仓库之外。
它适合配置变化频率不高、希望沿用 Git 协作,又暂时不需要完整配置中心或动态密钥系统的团队。
先明确要解决什么
这套方案把环境配置拆成两部分:
| 内容 | 存放位置 | 是否进入 Git |
|---|---|---|
| 变量名 | 加密后的 .env.* 文件 | 是 |
| 加密值 | 加密后的 .env.* 文件 | 是 |
| 加密公钥 | 加密后的 .env.* 文件 | 是 |
| 解密私钥 | .env.keys、密码管理器或部署平台 | 否 |
| 运行时明文值 | 目标进程的环境变量 | 否 |
公开密钥可以用于加密新值,但不能解密已有值。真正需要保护的是 DOTENV_PRIVATE_KEY
以及不同环境对应的私钥。
这解决的是“配置如何随代码协作”的问题,不等于解决所有 Secrets Management 问题。 审计、动态凭证、自动轮换和细粒度授权仍然需要专门的密钥管理服务。
安装 dotenvx
如果生产环境也通过项目依赖运行 CLI,可以把它固定为普通依赖, 避免部署时因为只安装 production dependencies 而缺少命令:
bun add @dotenvx/dotenvx
如果仅在本地或 CI 使用,也可以放进 devDependencies;采用官方二进制或容器入口时,
则不必把它加入应用依赖。后续示例统一使用 bunx dotenvx。
为不同环境准备文件
例如:
.env.development
.env.test
.env.production
加密前的 .env.development 可以是:
APP_ENV=development
API_BASE_URL=http://localhost:3000
DATABASE_URL=postgresql://app:change-me@localhost:5432/example
这里的连接信息只是本地占位示例,不应该复用到真实环境。
对指定文件执行加密:
bunx dotenvx encrypt -f .env.development
加密后,变量名仍然可读,值会变为密文,同时文件顶部会出现对应的公钥:
DOTENV_PUBLIC_KEY_DEVELOPMENT="03..."
APP_ENV="encrypted:..."
API_BASE_URL="encrypted:..."
DATABASE_URL="encrypted:..."
dotenvx 还会创建或更新 .env.keys:
DOTENV_PRIVATE_KEY_DEVELOPMENT="..."
.env.keys 保存的是解密能力,不能进入 Git。
先处理 .gitignore
推荐明确写出哪些加密文件允许提交,同时始终排除私钥:
.env*
!.env.example
!.env.development
!.env.test
!.env.production
.env.keys
这里有一条很重要的操作纪律:
.env.*必须在完成加密并检查差异后,才能加入暂存区。
如果某个环境文件曾经以明文形式进入 Git 历史,仅在后续提交中加密是不够的。 应立即吊销和轮换其中的凭证,再评估是否需要清理历史记录。
本地运行
开发机可以让 dotenvx 从被忽略的 .env.keys 读取私钥:
bunx dotenvx run -f .env.development -- bun run dev
dotenvx 会在进程启动时解密并注入变量,业务代码仍然只读取 process.env,
不需要知道配置是否来自加密文件。
不要为了“确认有没有解密成功”而把数据库密码、Token 等变量打印到日志。 可以检查非敏感变量,或者只验证变量是否存在。
if (!process.env.DATABASE_URL) {
throw new Error('DATABASE_URL is required')
}
新增和修改配置
使用 set 可以直接写入加密值:
bunx dotenvx set API_BASE_URL https://api.example.com -f .env.production
修改完成后,提交者应检查:
- 变更的变量名是否符合预期;
- Diff 中是否只出现密文,没有明文秘密;
- 是否误改了其他环境;
- 运行时是否仍能成功解密;
- 新增变量是否需要同步到
.env.example。
Git 负责同步加密后的配置版本,团队的密码管理器负责分发开发私钥。 两者不要混在同一个仓库里。
在 CI/CD 中使用
CI 不应上传 .env.keys。把对应私钥配置为流水线的受保护 Secret,
再注入当前步骤的环境变量。
以 GitHub Actions 为例:
- name: Run tests
env:
DOTENV_PRIVATE_KEY_TEST: ${{ secrets.DOTENV_PRIVATE_KEY_TEST }}
run: bunx dotenvx run -f .env.test -- bun test
生产部署也是同一个原则:
- 镜像或发布包只包含加密后的
.env.production; DOTENV_PRIVATE_KEY_PRODUCTION保存在部署平台的 Secret Store;- 平台启动进程时注入私钥;
- dotenvx 在运行时解密并注入业务变量。
当私钥已经由平台注入后,启动命令中不需要再出现明文:
dotenvx run -f .env.production -- node dist/server.js
不要把生产私钥写进 Dockerfile、镜像、启动脚本或加密后的 .env.production。
密钥轮换
成员离职、设备丢失、CI 日志泄露或私钥误发后,应立即轮换。 dotenvx 提供了重新生成密钥并重加密内容的命令:
bunx dotenvx rotate -f .env.production
轮换后需要同步完成三件事:
- 提交重新加密后的
.env.production; - 更新部署平台中的生产私钥;
- 删除密码管理器和设备中的旧私钥。
如果怀疑业务凭证本身也已泄露,轮换 dotenvx 私钥还不够,数据库密码、 API Token 和第三方密钥也必须分别吊销并重建。
一套推荐的团队流程
角色可以这样分工:
| 角色 | 主要责任 |
|---|---|
| 开发者 | 修改配置、加密、检查 Diff,不传播生产私钥 |
| 评审者 | 确认没有明文、环境改动范围正确 |
| CI/CD | 从 Secret Store 注入目标环境私钥 |
| 运维管理员 | 管理生产私钥、权限、轮换和事故响应 |
它防得住什么,防不住什么
能降低的风险
- Git 仓库泄露后,攻击者只能获得密文和公钥;
- 团队成员拉取代码时自动获得最新的配置结构和加密值;
- 环境配置可以像代码一样做 Diff、评审和回滚;
- 部署迁移不再依赖某台机器上的手工
.env。
不能解决的风险
- 攻击者同时拿到加密文件和对应私钥;
- 运行时进程、宿主机或 CI Runner 已被攻破;
- 应用把秘密打印到日志、错误页或监控系统;
- 所有成员共用同一私钥,缺乏细粒度授权和访问审计;
- 数据库密码等长期凭证本身没有轮换机制。
加密让配置可以安全地进入版本协作,但不会让运行环境自动变安全。
什么时候不该用 dotenvx
以下场景更适合云厂商 Secret Manager、Vault 或专门的配置中心:
- 需要短期动态凭证;
- 需要按用户或服务做细粒度权限;
- 需要完整的读取审计;
- 配置必须实时热更新;
- 需要自动轮换并让消费者无感刷新;
- 合规要求不允许长期私钥分发到开发设备。
dotenvx 最适合的定位是:
用较低的迁移成本,让静态环境配置进入可评审、可同步、可部署的 Git 工作流。
理解这个边界,比简单得出“加密后的 .env 可以提交”更重要。