devenv 0.6 实战解析:OCI 容器生成、即时 Shell 激活、Hosts 证书与 allowUnfree/overlays
devenv 0.6 实战解析:OCI 容器生成、即时 Shell 激活、Hosts 证书与 allowUnfree/overlays
本文以 devenv 0.6 官方发布说明为骨架,结合当前仓库源码(容器模块实现、CLI 定义、mkcert 集成、hostctl 集成)与真实示例(examples/fly.io/devenv.nix),系统讲解该版本引入的四大能力:将 Nix 开发环境打包为 OCI 容器、基于缓存快照的即时 Shell 激活、声明式 Hosts 与本地 TLS 证书供给,以及 devenv.yaml 中新增的 allowUnfree 与 overlays 选项。读完你可以掌握 devenv container 的完整用法、direnv 缓存的正确配置方式,以及如何用两行配置把非自由软件和自定义 overlay 接入 pkgs。
版本背景:devenv 0.6 的四个核心改进
devenv 0.6 在约两个月的密集开发后发布,其改进全部来自现有用户的真实反馈,集中在以下四个方面:
- 生成容器:新增
devenv container <name>命令族,可以把 Nix 开发环境构建为 OCI 容器镜像并复制进 registry; - 即时 Shell 激活:修复 direnv 集成的缓存问题,环境只在配置变化时重建,否则秒级复用缓存快照;
- Hosts 与证书供给:可以在
devenv.nix中声明式配置/etc/hosts条目和本地 TLS 证书; - 新
devenv.yaml选项:新增allowUnfree(允许构建非自由软件)与overlays(为输入接入 overlay)。
以下按原发布说明的脉络逐一展开,并补充当前仓库的源码级细节。
生成 OCI 容器:把开发环境打包成镜像
devenv shell 提供的是本机原生开发环境体验,而 devenv container <name> 则能把同样的环境(包括语言运行时、进程、证书等)打包成 OCI 容器,便于分发现成应用并部署到 fly.io 等平台。
命令族:build / copy / run
在 0.6 中,devenv container shell --docker-run 可以直接生成名为 shell 的容器、复制到本地 Docker daemon 并运行。当前仓库的 CLI 中,容器子命令演进为三个明确职责的命令(见 devenv/src/cli.rs):
devenv container build <name>:只构建容器镜像;devenv container copy <name> [--registry <url>] [--copy-args <args>]:把镜像复制到指定 registry;devenv container run <name> [--copy-args <args>]:构建并复制到本地 Docker daemon 后运行。
复制操作底层由 skopeo copy 完成。容器模块在 src/modules/containers.nix 中通过 nix2container 与 mk-shell-bin 两个输入(nix2container.buildImage + skopeo-nix2container)实现镜像构建,默认 registry 为 docker-daemon:,即直接落入本地 Docker daemon。
进入开发环境:生成一个 shell 容器
容器模块默认内置了名为 shell 的容器规格(见 src/modules/containers.nix):其 startupCommand 默认为 bash 本身,因此构建并运行它等价于"进入这个开发环境的交互式 shell"。
以一个 Ruby 项目为例,devenv.nix 只需声明项目名与语言版本:
{
name = "simple-ruby-app";
languages.ruby.enable = true;
languages.ruby.version = "3.2.1";
}
0.6 中运行以下命令即可进入容器内的开发环境:
$ devenv container shell --docker-run
...
(devenv) bash-5.2# ruby --version
ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-linux]
当前版本等价地使用 devenv container run shell。容器内的环境与本地 devenv shell 一致:mkEntrypoint 会先 source 环境的 envScript(对应 src/modules/containers.nix 中的 source ${shell.envScript}),再执行 startupCommand,从而保证 PATH、环境变量和语言运行时全部就位。
运行所有进程 / 单个进程 / 自定义二进制
除 shell 外,容器模块还默认内置了 processes 容器(src/modules/containers.nix),其 startupCommand 指向 config.procfileScript,即把 devenv.nix 中声明的全部进程(processes.*)作为容器入口启动。
你可以通过覆盖 startupCommand 来定制"单个进程"或"自定义产物"场景:
{ pkgs, ... }:
{
name = "my-service";
# 只启动一个进程
startupCommand = "exec my-web-server";
# 或者运行一个自定义构建产物(包路径、字符串或参数列表均可)
# startupCommand = [ "-f" "/var/lib/haproxy/haproxy.cfg" ];
}
从源码看,startupCommand 支持 str、package 或字符串列表三种形态(src/modules/containers.nix);当传入包路径时会被写入镜像 Cmd。WorkingDir 默认是 /env,项目根目录会被复制到镜像内该目录(copyToRoot 默认值为整个 git 仓库,见 src/modules/containers.nix 与 mkHome 实现)。
把容器复制到镜像仓库
要把容器推送到远程 registry,只需提供 registry 地址。仓库自带的 fly.io 部署示例(examples/fly.io/devenv.nix)展示了完整做法——它定义了一个运行 flask 进程的容器,并配置 registry 与推送到 fly.io 所需的认证参数:
{
languages.python.enable = true;
packages = [ pythonPackages.flask ] ++ lib.optionals (!config.container.isBuilding) [ pkgs.flyctl ];
processes.serve.exec = "exec flask --app hello run";
containers.processes = {
name = "simple-python-app";
registry = "docker://registry.fly.io/";
defaultCopyArgs = [
"--dest-creds"
''x:"$(${lib.getExe pkgs.flyctl} auth token)"''
];
};
}
registry即skopeo copy的目标地址前缀,产物形如<registry><name>:<version>;defaultCopyArgs作为skopeo copy的默认参数(这里是--dest-creds),也可以在执行devenv container copy时用--copy-args覆盖;- 复制动作在 devenv 中被实现为一个内部任务
devenv:container:copy(见 src/modules/containers.nix),通过DEVENV_TASK_INPUT传入 copy 脚本、规格与参数。
根据构建目标条件化环境
构建容器时,devenv 会设置 container.isBuilding = true(通过环境变量 DEVENV_CONTAINER 判定,见 src/modules/containers.nix 与 L455-L457)。这让同一个 devenv.nix 可以针对"本机原生环境"与"容器目标"给出不同配置:
{ pkgs, config, lib, ... }:
{
packages = [ pkgs.openssl ]
++ lib.optionals (!config.container.isBuilding) [ pkgs.git ];
}
上面的写法会在本机环境里额外安装 git,而构建容器时省略它。同样,容器构建时 devenv 会把 devenv.tmpdir、devenv.runtime、devenv.root 与 devenv.dotfile 全部重定向到镜像内路径(src/modules/containers.nix),保证容器内状态布局与原生环境自洽。
容器模块的完整配置项
围绕容器功能,当前仓库在 src/modules/containers.nix 中提供了以下可调选项(供 containers.<name>.* 使用):
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
name |
str | 项目名或 containers.mycontainer.name |
容器名,缺省为 <项目名>-<容器名> |
version |
str | "latest" |
镜像 tag |
fromImage |
package / null | null |
基于已有 OCI 基础镜像(nix2container 的 pullImage)构建 |
copyToRoot |
path 或 path 列表 | 整个 git 仓库 | 复制进镜像根目录的路径 |
startupCommand |
str / package / list | 视容器而定 | 容器入口命令(shell 容器默认 bash,processes 容器默认 procfile 脚本) |
entrypoint |
list | 内置 mkEntrypoint |
容器 Entrypoint,会先 source 环境脚本 |
workingDir |
str | /env |
容器工作目录 |
registry |
str / null | "docker-daemon:" |
skopeo copy 的目标 registry |
defaultCopyArgs |
list of str | [] |
复制时的默认 skopeo copy 参数 |
maxLayers |
int / null | 1 |
最大层数 |
enableLayerDeduplication |
bool | true |
层去重(基于 nix2container 的复用策略) |
layers |
list | 自动填充 | 细粒度自定义每一层的 deps、copyToRoot、perms 等 |
即时 Shell 激活:环境只构建一次,其余时间用缓存快照
对于大型 monorepo,开发环境可能达到数 GB 体量,激活耗时数秒。0.6 的目标很明确:环境只在内容发生变化时才构建;未变化时,直接使用缓存快照实现"即时"激活。
通过 direnv 集成,该版本把缓存机制做对了:缓存会正常命中,并且它还会监听每一个 import 的文件变化,一旦 devenv.nix / devenv.yaml 或其任何被引入的模块发生变化,环境才被重新构建。
在使用时需要注意迁移事项(同样适用于团队其他成员):
- 重新运行
devenv init或手动更新到最新版.envrc(模板位于 devenv/init/envrc); - 团队所有成员统一升级到 devenv 0.6,避免缓存格式不一致。
仓库中的 monorepo 测试(tests/monorepo)验证了多项目共享输入、分别激活的场景,是理解该特性适用面的参考样例。Hook 相关的实现与测试位于 devenv/src/commands/hook.rs,其中 hook-should-activate 在每个提示符时被调用,以确定当前目录是否需要激活。
声明式 Hosts 与本地 TLS 证书
0.6 允许把 hosts 条目和证书直接写进 devenv.nix,并在运行 devenv up 启动进程时自动完成本地供给:
{ pkgs, config, ... }:
{
certificates = [
"example.com"
];
hosts."example.com" = "127.0.0.1";
services.caddy.enable = true;
services.caddy.virtualHosts."example.com" = {
extraConfig = ''
tls ${config.env.DEVENV_STATE}/mkcert/example.com.pem ${config.env.DEVENV_STATE}/mkcert/example.com-key.pem
respond "Hello, world!"
'';
};
}
hosts."example.com" = "127.0.0.1"会把该条目写入/etc/hosts。底层由 hostctl 集成模块 实现:它把所有条目按 IP 分组、哈希后通过hostctl replace写入(native 进程管理器下作为devenv:hostctl:setup任务执行,见 src/modules/integrations/hostctl.nix)。由于需要写/etc/hosts,模块会优先使用sudo,必要时提示手动添加条目;certificates为域名列表(支持*.example.com通配),底层由 mkcert 集成模块 实现:首次运行mkcert -install生成本地 CA,再为列表中的每个域名签发证书到$DEVENV_STATE/mkcert/(见 src/modules/integrations/mkcert.nix)。证书是否重新生成由域名列表的 SHA256 哈希决定,配置未变则直接复用;config.env.DEVENV_STATE是 devenv 的状态目录,mkcert 模块同时设置了env.CAROOT、env.DEVENV_MKCERT与env.NODE_EXTRA_CA_CERTS(src/modules/integrations/mkcert.nix),让curl、Node.js 等客户端都能信任本地 CA;- 在 Caddy 示例中,TLS 证书路径直接引用
$DEVENV_STATE/mkcert/下生成的.pem与-key.pem,从而让本地服务跑在真正的 HTTPS 上。
certificates 与 hosts 选项均在对应集成模块中声明(certificates 支持自定义 certFile/keyFile 文件名,见 src/modules/integrations/mkcert.nix;hosts 支持值为字符串或 IP 列表,见 src/modules/integrations/hostctl.nix)。
allowUnfree 与 overlays:两个新 devenv.yaml 选项
allowUnfree:允许非自由软件
devenv.yaml 中设置 allowUnfree: true 后,构建系统会放行许可证受限的软件包:
allowUnfree: true
inputs:
nixpkgs:
url: github:NixOS/nixpkgs/nixpkgs-unstable
rust-overlay:
url: github:oxalica/rust-overlay
overlays:
- default
从后端实现看(devenv-nix-backend/src/backend.rs),allowUnfree 会生成一个 allowUnfreePredicate:当其为真时谓词恒真(放行一切包);否则若配置了 permitted_unfree_packages,则只放行白名单内的包,并对白名单外的非自由包抛出包含"允许方案"提示的错误(建议在 devenv.yaml 中设置 allow_unfree: true 或 nixpkgs.permitted_unfree_packages)。相关配置解析见 devenv-core/src/config.rs 与 devenv-core/src/nix_args.rs。
overlays:把输入接入 pkgs
同一份配置中,inputs.rust-overlay.overlays = <a href="https://link.gitcode.com/i/24d722feea569461844600b63be193d5" target="_blank"> "default" ] 会把 rust-overlay 的 default overlay 自动接入 pkgs。这解决了两个常见痛点:无需手写 imports/overlay 样板,也无需在 devenv.nix 里手动 merge overlay;声明后 overlay 内的包(如最新 Rust 工具链)直接可用。overlays 选项的定义见 [devenv-core/src/config.rs。
同样的写法适用于任意 flake 输入——只要输入导出了 overlay 属性,overlays 列表即可把它应用到 nixpkgs 上。
语言支持更新
0.6 对各语言模块做了大量增强,以下是本次新增或改进的语言(对应模块位于 src/modules/languages):
- Python:新增 virtualenv 创建与 poetry 支持;
- Ruby:一等支持设置
version或versionFile; - Go:获得显著改进;
- PHP:一等支持设置版本,便于配置扩展;
- Scala:允许更换包,JDK 过旧时可选用 scala-cli;
- R:新增指定包的选项;
- Rust:可为 darwin frameworks 查找 headers;
- OCaml:允许使用不同版本的 OCaml;
- Tex Live:新增支持;
- Swift:新增支持;
- Raku:新增支持;
- Gawk:新增支持;
- Racket:新增支持;
- Dart:新增支持;
- Julia:新增支持;
- Crystal:新增支持;
- Unison:新增支持;
- Zig:新增支持;
- Deno:新增支持。
服务支持更新
进程与服务侧同样有更新(对应模块位于 src/modules/services):
- Cassandra:新增;
- CouchDB:新增;
- MariaDB:修正了用户与数据库的处理逻辑;
- MinIO:现在可指定要供给的 buckets。
修复与其他改进
- process-compose:更快的关闭、默认失败重启、正确转义环境变量;
- 模块中支持
assertions; - 修复 overmind root 问题;
- 使
devenv info的输出可由 devenv 模块插拔(当前devenv info命令定义见 devenv/src/commands/info.rs); - 扩充 flake 指南;
- 缺失时自动设置
LOCALE_ARCHIVE; - 大量 option 文档修复;
- 修复 starship 使用自定义配置时的集成问题;
- 在严格 bash 模式下测试 direnv 集成;
- 为 flakes 集成添加
devenvshim。
从当前仓库结构看,0.6 引入的这些能力(容器生成、缓存激活、hosts/证书、allowUnfree/overlays)均已固化为稳定的模块与命令实现,并持续演进:容器模块在 src/modules/containers.nix 中维护,CLI 命令族在 devenv/src/cli.rs 中定义,hosts 与证书分别由 hostctl 与 mkcert 集成模块支撑。你可以直接按本文示例在本地项目 devenv.nix / devenv.yaml 中复现这些配置,再以 devenv container build|copy|run、devenv up 与 direnv 完成实战验证。