跳到正文

用 dotenvx 管理多环境配置:Git 协作、加密与部署

后端6 min
目录(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

修改完成后,提交者应检查:

  1. 变更的变量名是否符合预期;
  2. Diff 中是否只出现密文,没有明文秘密;
  3. 是否误改了其他环境;
  4. 运行时是否仍能成功解密;
  5. 新增变量是否需要同步到 .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

生产部署也是同一个原则:

  1. 镜像或发布包只包含加密后的 .env.production
  2. DOTENV_PRIVATE_KEY_PRODUCTION 保存在部署平台的 Secret Store;
  3. 平台启动进程时注入私钥;
  4. dotenvx 在运行时解密并注入业务变量。

当私钥已经由平台注入后,启动命令中不需要再出现明文:

dotenvx run -f .env.production -- node dist/server.js

不要把生产私钥写进 Dockerfile、镜像、启动脚本或加密后的 .env.production

密钥轮换

成员离职、设备丢失、CI 日志泄露或私钥误发后,应立即轮换。 dotenvx 提供了重新生成密钥并重加密内容的命令:

bunx dotenvx rotate -f .env.production

轮换后需要同步完成三件事:

  1. 提交重新加密后的 .env.production
  2. 更新部署平台中的生产私钥;
  3. 删除密码管理器和设备中的旧私钥。

如果怀疑业务凭证本身也已泄露,轮换 dotenvx 私钥还不够,数据库密码、 API Token 和第三方密钥也必须分别吊销并重建。

一套推荐的团队流程

修改配置

dotenvx 加密

检查 Git Diff

确认没有明文

提交加密文件

代码评审

CI/部署平台注入私钥

运行时解密并启动

验证非敏感健康检查

角色可以这样分工:

角色主要责任
开发者修改配置、加密、检查 Diff,不传播生产私钥
评审者确认没有明文、环境改动范围正确
CI/CD从 Secret Store 注入目标环境私钥
运维管理员管理生产私钥、权限、轮换和事故响应

它防得住什么,防不住什么

能降低的风险

  • Git 仓库泄露后,攻击者只能获得密文和公钥;
  • 团队成员拉取代码时自动获得最新的配置结构和加密值;
  • 环境配置可以像代码一样做 Diff、评审和回滚;
  • 部署迁移不再依赖某台机器上的手工 .env

不能解决的风险

  • 攻击者同时拿到加密文件和对应私钥;
  • 运行时进程、宿主机或 CI Runner 已被攻破;
  • 应用把秘密打印到日志、错误页或监控系统;
  • 所有成员共用同一私钥,缺乏细粒度授权和访问审计;
  • 数据库密码等长期凭证本身没有轮换机制。

加密让配置可以安全地进入版本协作,但不会让运行环境自动变安全。

什么时候不该用 dotenvx

以下场景更适合云厂商 Secret Manager、Vault 或专门的配置中心:

  • 需要短期动态凭证;
  • 需要按用户或服务做细粒度权限;
  • 需要完整的读取审计;
  • 配置必须实时热更新;
  • 需要自动轮换并让消费者无感刷新;
  • 合规要求不允许长期私钥分发到开发设备。

dotenvx 最适合的定位是:

用较低的迁移成本,让静态环境配置进入可评审、可同步、可部署的 Git 工作流。

理解这个边界,比简单得出“加密后的 .env 可以提交”更重要。

参考资料

分享到