mise oci:用 mise.toml 声明式构建、运行与推送 OCI 容器镜像
mise oci用 mise.toml 声明式构建、运行与推送 OCI 容器镜像【免费下载链接】misedev tools, env vars, task runner项目地址: https://gitcode.com/GitHub_Trending/mi/misemise oci是 mise 的实验性子命令族它把一份mise.toml直接转成一个符合 OCI Image Layout 规范的容器镜像并且每个安装的工具单独成为一个 OCI 层。本文以 mise-oci.md 为主线结合 src/oci、src/cli/oci 的源码实现与 e2e/oci 端到端测试完整讲解build/run/push三个命令的用法、分层与复用原理、[oci]配置节、认证与多架构发布帮助你把“工具环境”以可复现、可复用、可增量推送的容器镜像形式交付。什么是 mise oci把工具集变成可交付的镜像mise oci build的核心设计是将当前项目的mise.toml作为唯一输入产出磁盘上的 OCI 镜像布局默认输出到./mise-oci其中每个已安装的工具版本对应一个独立的 OCI 层工具安装目录以/mise/installs/plugin/version/为根打包进镜像。这一设计在源码中有明确的注释佐证。在 src/cli/oci/mod.rs 中写道Each tool becomes its own OCI layer, so bumping any single tool version only invalidates one content-addressable blob — unlike a Dockerfile where changing an earlyRUNinvalidates every layer above it.即改动单个工具版本时只有该工具的层需要重新构建其他工具的层、基础镜像层和镜像配置可以直接复用。与“Dockerfile 中靠前RUN一旦变化、其上层全部失效”的经典模型形成鲜明对比。当然镜像配置config、manifest 以及输入发生变化的层仍需更新同时一个 Python 版本的变更也可能会使依赖它的 pipx 层失效。实验性功能如何启用该功能目前是实验性的必须显式开启mise settings set experimentaltrue # 或仅在单次调用时开启 MISE_EXPERIMENTAL1 mise oci build …在 build.rs、run.rs、push.rs 的入口处都会调用Settings::get().ensure_experimental(mise oci …)做强制校验。需要注意的是标志位、输出布局和默认值在未来的版本中可能发生变化。三个命令一览命令作用mise oci build在磁盘上生成 OCI 镜像布局mise oci run构建或复用镜像并通过 podman/docker 在容器内执行命令mise oci push构建或复用镜像并推送到 registry三个子命令在 src/cli/oci/mod.rs 中以枚举形式注册共享 common.rs 中的构建编排逻辑。快速开始在Linux 主机、目标架构一致的环境下准备一份项目配置[settings] experimental true [tools] node 24构建本地镜像布局再用容器引擎验证其中的可执行文件mise oci build -o ./mise-oci mise oci run --image-dir ./mise-oci -- node --version要点默认基础镜像是debian:bookworm-slim建议把生成的mise-oci/目录加入.gitignore它创建的是一个工具环境并不会自动拷贝你的应用代码也不会安装项目的包依赖。开发时建议用 volume 挂载或者用[oci].copy见下文把需要进镜像的文件打进去。用外部工具检查布局时可安装skopeo后执行skopeo inspect oci:./mise-oci发布镜像前先完成 推送认证并选择一个你有写入权限的仓库mise oci push --image-dir ./mise-oci ghcr.io/OWNER/IMAGE:TAG将上方大写占位符替换为真实值即可。该命令与本地构建、运行检查相互独立。分层模型一个工具一层以如下mise.toml为例[tools] node 20 python 3.12 jq 1.8.1mise oci build大致产生如下层次基础镜像层如debian:bookworm-slim—— 从 registry 原样拷贝因此 registry 的跨仓库去重dedup机制可以生效mise 二进制位于/usr/local/bin/mise可用--no-mise跳过配置的[bootstrap.packages]apt 或 apk如果有的话安装进基础 rootfs 后作为一个 package 层输出每个工具一层根目录为/mise/installs/plugin/version/并以dev.mise.tool.short和dev.mise.tool.version注解配置的[dotfiles]如果有的话作为镜像文件烘焙合成的/etc/mise/config.toml将/mise引用为数据目录。各层上标注的注解就是后续“层复用”的缓存键。在 src/oci/builder.rs 中定义了全部相关注解常量pub(crate) const ANNOTATION_TOOL_SHORT: str dev.mise.tool.short; pub(crate) const ANNOTATION_TOOL_VERSION: str dev.mise.tool.version; pub(crate) const ANNOTATION_LAYER_PREFIX: str dev.mise.layer.prefix; pub(crate) const ANNOTATION_LAYER_OWNER: str dev.mise.layer.owner; pub(crate) const ANNOTATION_LAYER_RELOCATION: str dev.mise.layer.relocation;因此升级 Node.js 后无关的工具归档仍可复用而生成的配置与 manifest 仍会反映新版本。能否复用还取决于镜像内的路径、文件属主以及重定位relocation输入是否一致——这正是ReuseKeytool、version、prefix、owner、relocation 五元组的语义见 builder.rs。mise oci buildmise oci build [-o PATH] [--from REF] [--tag REF] [--mount-point PATH] [--copy HOST_PATH:IMAGE_PATH]... [--no-mise] [--owner UID[:GID]]-o, --output PATH— 输出目录默认./mise-oci--from REF— 基础镜像引用覆盖[oci].from与oci.default_from设置使用scratch表示无基础镜像构建-t, --tag REF— 写入index.json的 tag即org.opencontainers.image.ref.name注解--mount-point PATH— 镜像内 mise 安装目录默认/mise必须是绝对路径--copy HOST_PATH:IMAGE_PATH— 将主机文件或目录拷贝到镜像内的绝对路径可重复传入多个每个 payload 作为独立的内容寻址层排在工具层之后--no-mise— 不把当前运行的 mise 二进制嵌入/usr/local/bin/mise--owner UID[:GID]— 所有生成层条目的数字属主默认取[oci].user_id/[oci].group_id再退化为0:0省略 GID 时默认等于 UID。只影响文件属主不影响镜像的USER指令。关于基础镜像与挂载点的解析优先级在 src/oci/builder.rs 中有明确实现mount_point的取值顺序是--mount-point[oci].mount_pointoci.default_mount_point设置且强制要求以/开头否则会直接报错——因为相对路径会让容器内MISE_DATA_DIR依赖工作目录而错误解析工具。from的优先级链则是--from[oci].fromoci.default_from设置 内置默认debian:bookworm-slimscratch会被过滤掉表示无基础镜像。build结束后会在终端打印输出目录、manifest 摘要以及每个工具层工具名、版本、摘要、字节数的清单见 build.rs。另外需要特别留意 build.rs 中的AFTER_LONG_HELP说明镜像只包含项目配置项目根目录及以下层级中的工具~/.config/mise/config.toml中的个人工具默认不打包除非传--include-globalasdf / vfox 插件在 v1 中不受支持请改用 core、aqua、ubi、github、cargo、npm、go、pipx、spm、http 等后端默认把当前主机上运行的 mise 二进制嵌入镜像因此在与目标镜像相同 OS/arch 的主机上构建或传--no-mise。mise oci run构建或复用镜像并在其中执行命令行为类似docker run/podman runstdin/stdout/stderr 均继承。mise oci run [--engine ENGINE] [--image-dir DIR] [--from REF] [--mount-point PATH] [--no-mise] [--owner UID[:GID]] [-i] [-t] [-e KEYVAL]... [--volume HOST:CONTAINER]... [-w DIR] [--keep] -- cmd [args...]--engine—auto默认优先选择 podman、podman或docker--image-dir— 跳过构建直接使用已存在的 OCI 布局--owner UID[:GID]— 全新构建时为生成层条目指定数字属主不能与--image-dir组合使用-i、-t、-e、--volume、-w、--keep— 与docker run的用法一致地透传给底层引擎。特别注意没有-v短选项因为 mise 把-v保留给了--verbose请使用--volume或--mount源码中 run.rs 将--mount定义为--volume的 alias。示例# 交互式 shell mise oci run -it -- bash # 一次性命令注入环境变量 挂载卷 工作目录 mise oci run -e DEBUG1 --volume $PWD:/work -w /work -- npm test # 复用之前构建好的布局 mise oci build -o ./img mise oci run --image-dir ./img -- node --version运行前提本机必须安装podman原生支持 OCI layout或dockermise 通过docker load将镜像灌入 daemon。mise 本身不内置容器运行时。此外默认情况下命令退出后会同时移除容器--rm与已加载的镜像避免在 podman/docker 存储中堆积传--keep则保留镜像docker 下以mise-oci:run-*tag 保存podman 下保留 pull 的镜像 ID见 run.rs。mise oci push构建或复用镜像并使用mise 内置的 registry 客户端推送——无需 skopeo、crane 或 docker daemon。只有 registry 尚未拥有的 blob 才会上传因此对基本不变的工具集反复推送传输量极小当基础镜像就位于目标 registry 时其 blob 会走跨仓库挂载cross-repository mount完全不产生字节传输。大体积层会分块上传并显示进度条瞬时网络故障会带退避重试http_retries控制重试次数。层复用Layer reuse工具层的缓存键为工具、版本、镜像内前缀、文件属主与先前推送的镜像匹配的层会直接从 registry 复用而不是重新构建——完全跳过本地的 tar/gzip 打包工作。被复用的工具甚至不需要在本地安装这让 CI 推送非常快只有版本真正变化的工具才需要安装与打包。默认情况下缓存就是目标 ref 本身该 tag 下先前推送的镜像--cache-from REF从同一仓库的其他 tag 复用层——适合每次推送都使用唯一 tag 的场景mise oci push --cache-from ghcr.io/me/dev:latest ghcr.io/me/dev:$GIT_SHA--no-cache禁用复用从本地安装重新构建每一层docker 风格的逃生舱口——复用机制信任 registry 中层内容与注解匹配而不是在本地逐字节重建。一个注意事项环境变量推导JAVA_HOME这类exec_env变量是在本地安装上执行的。对于未被复用的工具且本地未安装时大多数后端仍能正确推导路径但少数特殊后端可能贡献不完整的环境变量——如果镜像配置看起来不对可传--no-cache并保证工具已安装重推。[e2e/oci/test_oci_push_layer_reuse](https://link.gitcode.com/i/47ef5fd3229d2998045889861f53cd6b) 端到端测试完整验证了这一行为首次 push 构建全部层卸载 jq 后再次 push 同一 tag输出1 tool layer(s) reused from previous image且 jq 层摘要与 PATH 完全一致--cache-from推送到新 tag 时层摘要不变--cache-from若指向不同仓库会报错--no-cache在工具未安装时会失败install path does not exist。mise oci push [--image-dir DIR] [--from REF] [--mount-point PATH] [--no-mise] [--owner UID[:GID]] REGISTRY_REFREGISTRY_REF— 完整限定的目标引用如ghcr.io/me/devenv:latest必须包含 registry 主机名push.rs 会强制校验包含/。环回地址 registrylocalhost:5000/…走明文 HTTP与 docker 的“默认不安全”惯例一致非环回的明文 HTTP registry如家庭实验室的registry.lan:5000必须通过oci.insecure_registries设置显式放行[settings.oci] insecure_registries [registry.lan:5000]--image-dir— 推送已存在的 OCI 布局而不是现场构建--owner UID[:GID]— 全新构建时指定生成层条目的数字属主不能与--image-dir组合。示例# 一步完成构建 推送 mise oci push ghcr.io/me/devenv:latest # 推送之前构建好的镜像 mise oci build -o ./img mise oci push --image-dir ./img ghcr.io/me/devenv:v1推送认证Push authentication凭据按以下顺序从与 docker、podman 相同的来源解析$REGISTRY_AUTH_FILE$XDG_RUNTIME_DIR/containers/auth.jsonpodman~/.config/containers/auth.json~/.docker/config.json或$DOCKER_CONFIG/config.json内联的auths条目与凭据助手credsStore/credHelpers如docker-credential-osxkeychain、docker-credential-ecr-login都受支持——因此只需要普通的docker login ghcr.io或podman login ghcr.io即可。当找不到任何凭据时mise 会匿名推送适合本地 registry并给出警告。对于 ghcr.iotoken 需要具备write:packages权限范围。[oci]配置节mise.toml中的[oci]节提供声明式配置[oci] from debian:bookworm-slim # 基础镜像引用 tag ghcr.io/me/devenv:v1 # 构建镜像的默认 tag workdir /workspace # WORKDIR entrypoint [] # ENTRYPOINT cmd [] # CMD user 1000:1000 # USER user_id 1000 # tar 层条目 UID文件属主 group_id 1000 # tar 层条目 GID默认取 user_id mount_point /mise # 镜像内工具安装位置 [[oci.copy]] host dist/my-app image /usr/local/bin/my-app [[oci.copy]] host assets image /srv/app/assets # 额外烘焙进镜像配置的环境变量仅镜像内生效——不会遮蔽 MISE_*。 [oci.env] NODE_ENV production # 烘焙进镜像配置的标签。 [oci.labels] org.opencontainers.image.source https://github.com/me/my-appOciConfig结构体定义在 src/oci/mod.rs所有字段均可选且未知字段会被 serde 的deny_unknown_fields拒绝。copy 示例要求dist/my-app与assets必须真实存在。关于user/user_id/group_id的区分[oci].user设置镜像的USER指令它不会创建账号、家目录或可写工作区——应使用数字 UID/GID 或基础镜像中已存在的用户[oci].user_id与[oci].group_id设置层文件的属主未配置group_id时默认取解析后的user_id。配置合并的优先级规则为CLI 标志 [oci]节 oci.default_from/oci.default_mount_point设置。当存在多层mise.toml全局 项目时各节按字段逐项合并更具体的文件在每个字段上胜出——这在 common.rs 的merged_oci_config_from中实现采用“最具体优先迭代 先到先得first-Some-wins”语义。[[oci.copy]]语义细节拷贝源可以是文件、目录或符号链接目录内容落在image路径下不会带上源目录名镜像路径必须是绝对路径且不允许包含.或..分量——src/oci/mod.rs 的validate_image_path会逐一校验同时拒绝空路径与根路径/父目录自动创建可执行位保留属主遵循--owner或[oci].user_id/[oci].group_idcopy 层以dev.mise.copy镜像路径注解便于审查时识别[[oci.copy]]中相对的host路径相对于声明它的配置文件所在目录解析common.rs 的resolve_copy_paths且有对应单测CLI 传入的相对路径相对于当前工作目录分层配置拷贝到同一镜像路径时较不具体的条目先输出最具体的配置最终生效CLI 拷贝最后输出。[bootstrap]与[dotfiles]进入 OCI 镜像mise oci build会把项目作用域内的[bootstrap.packages]与[dotfiles]应用到镜像——这是mise bootstrap中声明式包管理与 dotfile 部分的 OCI 等价物。传--include-global可将全局配置中的这两部分也纳入[bootstrap.packages] apt:curl latest [dotfiles] /etc/profile.d/project.sh { source profile.sh, mode copy } ~/.config/app/config.toml { source config.toml, mode template }包管理的规则与 src/oci/packages.rs 的实现对应OCI 构建支持 Debian/Ubuntu 基础镜像上的apt:条目与 Alpine/Wolfi 基础镜像上的apk:条目mise 将基础镜像解包到临时 rootfs调用匹配的主机包管理器向该 rootfs 安装再把文件系统变更输出为一个 OCI 层并以dev.mise.system.packagesapt或dev.mise.system.packagesapk注解一次构建只能使用与基础镜像匹配的包管理器混用apt:与apk:会被拒绝主机必须提供apt-get与dpkgapt 层或apkapk 层apk 的包脚本在 chroot 内执行因此 apk 层目前要求Linux 主机上以 root 运行 mise构建时会向 apk 传--no-cache并在成层前清理临时包管理器缓存与日志文件。dotfile 的处理镜像构建中symlink与symlink-each条目按文件内容拷贝——主机符号链接通常指向检出路径在容器内会断链因此镜像收到的是解析后的内容以~/开头的目标写入/root/下。需要强调的是[bootstrap.macos.defaults]与命令式的bootstrap任务不会由mise oci build执行——macOS 默认值不适用于 Linux OCI 镜像容器特有的启动工作应放在镜像的 entrypoint 或 command 中。若配置中确有 macOS defaults 条目common.rs 的reject_unsupported_system_defaults会直接报错拒绝构建。相关设置项设置默认值说明oci.default_fromdebian:bookworm-slim未指定时的默认基础镜像oci.default_mount_point/mise镜像内工具的安装位置选择基础镜像时必须考虑与打包二进制及其共享库的兼容性。默认镜像基于 glibcAlpine/musl 基础镜像要求 musl 兼容或合适的静态二进制——更换--from并不会为不同的 libc 重新编译已安装的工具。运行时所需的系统库必须存在于镜像中。镜像内的环境变量镜像配置的Env按如下顺序构建靠后者胜出基础镜像的环境变量来自所拉取--from镜像的 configmise.toml中的[env]节完全解析——模板已展开、.env文件已读取每个工具的exec_env()——如JAVA_HOME、GOROOT、GEM_HOME路径会从主机安装目录重定位rebased到镜像内路径[oci].env条目合成的 PATH镜像内各工具 bin 路径加上继承的 PATHMISE_DATA_DIR/mise与MISE_CONFIG_DIR/etc/mise——总是最后应用确保不会被遮蔽。::: warning 安全警告[env]中的秘密会被烘焙进镜像[env]节中的任何内容——包括从.env文件加载的值——都会写入镜像 config JSON任何执行docker inspect/skopeo inspect的人都能看到。不要把秘密放在这里。运行时请使用docker run -e、secret 挂载或编排器的 secret 能力[oci].env只放可以安全常驻镜像的值。mise 会发出警告提示烘焙进镜像的[env]变量数量。 :::支持的 backend构建器接受内置 backend打包每个选定工具的安装目录并重定位受支持的可执行文件路径与 shebang。但构建器的接受并不保证工具是自包含的系统库、外部运行时或安装目录之外的路径可能仍然需要。请把所需运行时与工具一起声明并用项目实际运行的命令验证产物镜像。asdf与vfox插件包括自定义 vfox 后端插件会被拒绝它们的安装钩子可能写出 per-version 目录之外的内容而按工具分层的模型无法可靠捕获这些内容。builder.rs 的reject_unsupported_backends在构建早期完成这一校验。Registry 基础镜像支持基础镜像可从任何 OCI Distribution v2 registry 拉取——Docker Hub、ghcr.io、quay.io、自建等均可。公共镜像的匿名 token 认证自动处理当你已登录docker login/podman login时使用对应凭据因此私有基础镜像也能正常工作。支持摘要digest引用mise oci build --from REGISTRY/IMAGEsha256:FULL_DIGEST将占位符替换为真实的镜像引用与完整 SHA256 摘要。使用 digest 会固定基础镜像可变 tag 在后续构建中可能解析到新的基础镜像。可复现性在同一主机上以不变的输入重复运行mise oci build会得到字节一致的工具层摘要跨机器时层摘要可能漂移因为编译产物pyc 字节码、生成的 node-gyp 输出等可能嵌入绝对路径。若要完全可复现的镜像配置时间戳设置SOURCE_DATE_EPOCHSOURCE_DATE_EPOCH$(git log -1 --format%ct) mise oci build这也是 e2e/oci 各测试脚本开头统一export SOURCE_DATE_EPOCH1700000000的原因。跨平台构建OCI 镜像面向 Linux。在 macOS 或 Windows 上构建出的镜像其os字段是linux但嵌入的二进制mise 与每个工具层仍是主机原生架构——在容器内执行时会报Exec format error。源码层面src/oci/mod.rs 的normalize_arch与normalize_os负责将 Rust 风格平台名x86_64、aarch64、macos、windows归一化为 OCI 规范值amd64、arm64、linux并确保非 Linux 主机构建出的镜像仍可在 Linux 上运行。请在Linux 主机或已安装 mise 与工具安装依赖的 Linux 开发容器中构建。普通debian镜像并不包含 mise也不要把 macOS 或 Windows 的工具安装挂载进容器冒充 Linux 安装。mise 在主机与镜像平台不匹配时会发出警告。多架构镜像Multi-arch单台主机只能构建单一平台但mise oci push --update-index让每个架构各跑一个 runner 来组装多架构 tag每次 push 按 digest 上传本平台的 manifest并把 tag 指向一个保留其他平台条目的 OCIimage index。下面这个 GitHub Actions job 一次构建一个架构假设项目已有mise.toml、发布到 GHCR、且 workflow 对包有写入权限name: Publish development image on: workflow_dispatch permissions: contents: read packages: write concurrency: group: mise-development-image cancel-in-progress: false jobs: publish: strategy: max-parallel: 1 matrix: runner: [ubuntu-24.04, ubuntu-24.04-arm] runs-on: ${{ matrix.runner }} env: MISE_EXPERIMENTAL: 1 steps: - uses: actions/checkoutv6 - uses: jdx/mise-actionv4 - name: Authenticate to GHCR env: GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: printf %s $GHCR_TOKEN | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin - name: Publish this architecture run: mise oci push --update-index ghcr.io/${GITHUB_REPOSITORY,,}/dev:latest请选择仓库可用的 runner 标签。矩阵串行化max-parallel: 1防止两个平台 push 互相竞争workflow 级concurrency防止同一 workflow 的并发运行同时更新同一 tag。补充行为重复推送同一平台会替换其条目不产生重复原本单架构的 tag 升级为 index 时不会丢失已有平台层复用在 index 上同样生效——缓存会解析到与构建平台匹配的条目。需要注意 index 更新是读-改-写Distribution API 没有条件写不同 runner 并发推送同一 tag 可能产生竞争——请按上述方式串行化。已知限制v1asdf/vfox后端被拒绝原因见上文跨平台构建会产出损坏的镜像二进制是主机原生的请在 Linux 主机上构建基础镜像必须提供兼容的 libc 及其他运行时库mise oci run需要容器引擎podman 或 docker——mise 没有内置容器运行时推送则不需要任何外部工具。参见mise oci build完整 CLI 参考mise oci run完整 CLI 参考mise oci push完整 CLI 参考构建编排源码src/oci/builder.rs、src/oci/mod.rs端到端测试e2e/oci/test_oci_push_layer_reuse、e2e/oci/test_oci_push_update_index规范参考OCI Image Spec 与 OCI Distribution Spec本文涉及 OCI image-layout 与 Distribution v2 API 时均以其为准【免费下载链接】misedev tools, env vars, task runner项目地址: https://gitcode.com/GitHub_Trending/mi/mise创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考