资讯中心

Windows 下构建 arm64 deb 安装包:三大误区与完整流程

📅 2026/9/28 18:59:11
Windows 下构建 arm64 deb 安装包:三大误区与完整流程
干过这类事的朋友应该能理解接到“在 Windows 上打一个 arm64 的 deb 安装包”这种需求的时候第一反应多半是有点懵的。我这次的任务是给一台跑 Debian 系统的 ARM 架构设备发布一个命令行小工具但我的开发机是一台 Windows 笔记本。折腾完整个流程之后回头复盘真正绕弯的地方其实不在“Windows”也不在“deb”本身而在三个我一开始想当然的假设觉得 deb 必须上 Linux 才能打、觉得 arm64 只是把包描述里的架构字段改一改就行、觉得包打出来就等于能装、能跑。这篇就把这三个误区摊开讲清楚同时把我最后跑通的完整流程放出来供同样在 Windows 上做 Linux 包交付的兄弟参考。1. 任务背景与方案选型1.1 这到底是个什么任务先交代一下场景。目标设备是 ARM64AArch64架构的 Debian 板子跑的是精简版系统没有图形界面我用一个小型命令行工具去采集设备状态并通过 HTTP 上报。交付方式是 .deb 安装包这样运维同学拿到包以后执行dpkg -i或者apt install ./xxx.deb就能装好卸载也很干净。开发机上装的是 Windows 11项目仓库在 Git 里代码本地编译能过。问题来了我需要产出的是一个给“Linux ARM 架构”用的安装包而我的开发环境是“Windows x86_64”。任务拆开看其实是三件事先为 arm64 Linux 交叉编译出可执行文件再把这个文件按照 deb 包的规范封装成归档最后在目标设备上实测安装与运行。这一步接一步的问题分别踩中了那三个误区。1.2 三条打包路线的对比我在动手之前简单列了一下可行方案各有利弊我直接把关键参数和取舍列出来方案核心工具风险与成本适用场景AWSL 2 里完成一切WSL 发行版 dpkg-deb / lintian依赖 WSL 环境路径转换时有坑但最接近原生 Linux经常持续交付 Linux 包的团队BDocker 容器挂载目录Linux 容器 fpm / dpkg-deb需要 Docker Desktop容器与宿主机路径分离但环境可复现配合 CI 或本地一键构建CWindows 原生打包Ruby fpm或手动构造 ar 归档fpm 对环境依赖多权限位处理麻烦偶尔打一包、不想装 Docker 时使用我最后选的是方案 B 为主、方案 C 备用的混合办法。理由很简单Docker 容器里是完整的 Linux 环境dpkg-deb、lintian、fpm这些工具都是官方的连交叉编译的gcc-aarch64-linux-gnu也能直接 apt 装上对 Windows 而言只要求装个 Docker Desktop把项目目录挂载进去构建产物再落回 Windows 磁盘等于既用了 Linux 的打包工具链又没离开 Windows 的工作习惯。1.3 为什么坚持“在 Windows 上”完成有人会问既然都要用 Docker 了为什么不干脆在 Linux 虚拟机里做我当时的实际约束是团队协作和文件流转都围绕 Windows 办公环境运维交付流程里需要从我的开发机直接拿到产物并做签名登记临时再拉一台 Linux 虚拟机反而要多维护一套环境。而且这个需求不是一次性任务之后每次版本迭代都要在 Windows 上重新打 arm64 deb 包。所以我要的不是“这一次能打出来”而是一条可重复执行的构建路径。Docker 方案的自然好处是把整个打包流程写成一个脚本或 Dockerfile 后Windows 上的任何一台机器都能复现代不用再折腾环境。2. 第一个误区deb 包只能在 Linux 环境里打2.1 deb 包的物理结构到底是什么先打破一个心理障碍deb 不是一个神秘的“Linux 专属格式”它本质上就是一个 Unix 世界的归档文件。一个标准的 deb 包内部由三部分组成debian-binary纯文本文件内容通常是2.0表示包格式版本control.tar.gz存放软件包元信息比如control、md5sums、conffilesdata.tar.xz实际要安装到系统里的文件按根目录结构排列比如usr/bin/xxx。这三部分被ar归档工具封装成一个整体。Linux 下的dpkg-deb做的事情本质上就是“组装这个归档”和“从归档里还原文件”。理解了这一点就会明白只要能在某种环境下构造出这三部分内容deb 包就能打出来不一定非得是 Linux 操作系统本身。我当时想当然地把“deb”和“Linux 桌面环境”绑在一起以为离开 Linux 就寸步难行。其实 Windows 上完全可以手工构造 ar 归档只是这样太原始、容易出错不值得在生产流程里用。2.2 在 Windows 上干这件事的三条路第一条Ruby fpm。fpm是一个把任意目录变成 deb、rpm 等格式的打包工具在 Windows 上装上 Ruby 和 DevKit 后gem install fpm就能用。它能在 Windows 文件系统上直接生成 deb不需要 Linux。但是有个经典坑Windows 的 NTFS 没有 Unix 权限位fpm 打包时不能自动识别“这个文件是否可以执行”所以最终 deb 里的可执行文件可能没有x权限装到 Linux 上直接“Permission denied”。第二条Docker 容器。这是我最推荐的方式。Windows 上的 Docker Desktop 可以无缝启动 Linux 容器容器内部就是完整的 Debian/Ubuntu 环境。把 Windows 的项目目录用-v D:\project:/build方式挂载进去在容器里安装dpkg-dev、fpm、gcc-aarch64-linux-gnu然后执行打包命令生成的 deb 文件会落在 Windows 磁盘上。权限问题也能顺手解决因为容器内文件系统是真正的 Linux 文件系统chmod x生效。第三条WSL 2。装了 WSL 2 之后子系统本身就是 Linux路径直接就能访问 Windows 盘用起来最像“在 Linux 里”。但如果你已经在用 Docker Desktop它底层多半也是 WSL 2 后端再开一个发行版实例意义不大而且 WSL 的发行版如果只是临时用维护成本略高。2.3 我最终选择容器方案的原因我选容器方案还有一个很实际的理由交叉编译器。我的目标设备是 arm64如果程序是非解释型的编译产物必须用交叉编译器生成 ARM 指令集的 ELF。Windows 原生的交叉编译工具链不是没有但配置起来相当折腾而 Debian 容器里一条apt install gcc-aarch64-linux-gnu就全齐了。后面小节会详细讲但这里先点一句打包很快真正决定成败的是你有没有把“能在目标架构上跑的二进制”放进去。3. 第二个误区arm64 不只是一个字段3.1 交叉编译的准备工作这是我这次最深刻的一个教训。我当时以为只要在 fpm 或者 dpkg 的时候把Architecture字段写成arm64包打出来就能用。现实是架构字段只负责“告诉 apt 这个包装到哪种系统上”它不负责“让包里那个文件真的能在 ARM 上运行”。deb 包里放的如果是编译好的二进制程序那这个文件必须已经是 ARM64 指令集的 ELF。换句话说在 Windows 的GOARCHamd64下编译出来的程序即使包名写着_arm64.deb装到 ARM 设备上也会直接报“cannot execute binary file: Exec format error”。提前准备的工作很简单确认目标架构名。Debian 系官方架构名是arm64对应 AArch64Android 上常见的arm64-v8a不能直接搬过来用因为 apt 认识的是arm64。我一开始受过 Android 习惯影响差点写成arm64-v8a。3.2 不同语言工具的交叉编译做法如果你的交付物是二进制程序具体准备方式取决于语言Go 是最省心的。设置两个环境变量即可$env:GOOSlinux $env:GOARCHarm64 $env:CGO_ENABLED0 go build -o mytool .建议关掉 CGO这样产物是纯静态链接不依赖目标机的 glibc装哪都能跑。如果必须开启 CGO 并依赖 C 库那就需要配合aarch64-linux-gnu-gcc在 Windows 上直接交叉编译会麻烦很多。Rust 的步骤也很标准安装目标后编译即可rustup target add aarch64-unknown-linux-gnu cargo build --release --target aarch64-unknown-linux-gnu注意aarch64-unknown-linux-gnu这个目标默认是动态链接到 glibc 的。如果你的目标设备是精简镜像、glibc 版本较老同样可能遇到运行库版本不匹配的问题。想走得稳可以研究aarch64-unknown-linux-musl的静态目标。C/C 项目通常需要明确的交叉编译工具链在 Debian 容器里就是apt install gcc-aarch64-linux-gnu aarch64-linux-gnu-gcc -static -o mytool main.c-static能避免目标设备缺共享库的麻烦如果坚持动态链接至少要在目标设备上确认相关.so都在这个排查成本比较高。3.3 最容易翻车的点动态链接和架构识别我用 Go 写的工具因为默认静态链接当时信心满满。但后来在容器里跑了一个file命令才踏实file mytool # 输出形如ELF 64-bit LSB executable, ARM aarch64, statically linked如果你看到dynamically linked就必须继续查它依赖了哪些库。可以执行readelf -d mytool | grep NEEDED我就在这个问题上栽过某一次用 C 写的小工具没有加-static动态链接到了容器里较新版本的libc.so.6而目标设备的libc6版本比较老安装时依赖都没问题因为控制信息只声明了libc6没有精确到版本一运行就报/lib/aarch64-linux-gnu/libc.so.6: version GLIBC_2.34 not found。这个真的要提前查清楚。3.4 顺带提醒脚本类程序也要注意架构如果你的包里的主程序是 Python 脚本或者 Shell 脚本没有二进制的架构问题但依赖的第三方 Python 包可能包含编译好的.so这时候还是得要求“依赖里写明 Python 版本和平台”。所以“arm64 只是个字段”这个想法对纯脚本成立对编译产物和含原生扩展的脚本都不成立。4. 第三个误区deb 打出来不等于能安装4.1 可执行权限是怎么丢的这是我在 Windows 上打包遇见的最具欺骗性的问题。deb 包里的文件在安装时会按照归档里记录的权限位还原到系统里如果原始归档里没有标记可执行权限装完以后/usr/bin/mytool的权限是-rw-r--r--用户执行时就只能看到Permission denied。Windows 上为什么容易丢权限因为 NTFS 文件系统没有完整的 Unix 权限模型你从 Windows 复制到 Linux 容器或者用 Windows 工具做归档时x信息本来就是缺失的。我在第一次试打包时用 fpm 直接在 Windows 里指向一个打包好的目录出来的 deb 装到 Linux 上就是“没有执行权限”。解决办法其实不复杂在容器内归档之前对可执行文件执行一次chmod x。用 fpm 时也可以显式声明模式fpm -s dir -t deb \ --name mytool \ --version 1.0.0 \ --architecture arm64 \ --deb-user root \ --deb-group root \ --deb-file-mode 755 \ ./mytool/usr/bin/mytool如果不用 fpm而是用dpkg-deb则需要在容器里保证源目录内权限正确后再执行dpkg-deb --build --root-owner-group root ./package_dir。4.2 架构字段和版本号的坑架构字段前面讲过必须写arm64。而版本号也是一个容易出错的地方。Debian 的版本号规则是[epoch:]upstream_version[-debian_revision]upstream_version只允许数字、字母和. - ~这几个符号不允许下划线。如果你习惯性地传--version 1.0.0_betafpm 会自动把下划线变成别的符号或者直接报错安装时会提示 “bad version”。我建议版本号一律保持a.b.c或a.b.c-build这种格式简单不容易错。4.3 包内路径结构deb 包内的文件路径是相对根目录展开的。也就是说你希望程序最终落在/usr/bin/mytool归档里的路径就应该是usr/bin/mytool不能带前导/也不能是 Windows 风格路径D:\...。我在构建目录时采用的是先做好包含usr/bin结构的 staging 目录再把整个usr目录放进去这样结构清晰、不容易乱。4.4 安装前必须跑的验证清单我最后摸索出一个固定流程每次打完包都先做一遍可以提前拦截大部分问题用dpkg-deb --info mytool_1.0.0_arm64.deb检查元信息确认 Architecture、Depends、Size 等用dpkg-deb -c mytool_1.0.0_arm64.deb检查归档内的路径和权限位在容器内实际执行dpkg -i或apt install ./mytool_1.0.0_arm64.deb看有没有依赖问题安装后直接执行which mytool mytool --version确认可执行位和程序本身没问题。如果是干净容器直接docker exec进容器里安装验证就行。这比“打完包就发出去”的信任感要强得多。5. 完整实操流程从 Windows 到 ARM 设备5.1 工具准备清单我在 Windows 上准备这些东西一次性解决环境问题Docker Desktop并确保 Docker 能启动 Linux 容器项目代码目录比如D:\work\mytool一个用于交叉编译和打包的 Debian 镜像基础我用的是debian:bullseye或debian:bookworm都可以。Docker Desktop 在 Windows 上第一次跑 Linux 容器会有点慢但第二次开始就快了。用 Docker 而不是直接用 WSL 单独维护环境是为了让整个构建流程可以放进一个脚本里后续任何同事在 Windows 上都能一键复现。5.2 一个完整的构建脚本我最终落地的方式是先写一个小的构建脚本然后在 Windows 上用 Docker 挂载目录执行。下面是脚本的主干你可以根据项目改。#!/bin/bash set -e # 1. 编译准备 cd /build export GOOSlinux export GOARCHarm64 export CGO_ENABLED0 # 2. 交叉编译 Go 程序 go build -o /out/mytool . # 3. 组装 staging 目录 mkdir -p /work/usr/bin cp /out/mytool /work/usr/bin/mytool chmod x /work/usr/bin/mytool # 4. 制作 deb 包 cd /work fpm -s dir -t deb \ -n mytool \ -v 1.0.0 \ -a arm64 \ --deb-user root \ --deb-group root \ --deb-file-mode 755 \ --description A small tool for device status collection \ --url https://example.com/mytool \ --license MIT \ -p /out/mytool_1.0.0_arm64.deb \ ./usr/usr # 5. 验证 dpkg-deb --info /out/mytool_1.0.0_arm64.deb dpkg-deb -c /out/mytool_1.0.0_arm64.deb注意-p /out/...指定输出路径/out是容器内挂载点对应 Windows 上的某个目录。假设项目在D:\work\mytool保存脚本为build.sh后在 Windows PowerShell 里执行docker run --rm -v D:\work\mytool:/build -v D:\work\output:/out -w /build debian:bookworm bash -c apt update apt install -y golang ruby ruby-dev build-essential dpkg-dev gem install fpm --no-document bash build.sh这行命令会把整个构建过程自动化容器里自动装好 Go、Ruby、fpm然后执行脚本产物直接写到D:\work\output。容器的沙箱化环境还有个额外好处不会把 Windows 本地的 PATH 变量污染带进去编译环境每次都是干净的。5.3 实际安装验证的日志长什么样为了说明效果我把验证步骤贴出来。进入目标设备或容器apt install ./mytool_1.0.0_arm64.deb正常输出大体是Reading package lists... Done Building dependency tree... Done Reading state information... Done Selecting previously unselected package mytool. (Reading database ... 52348 files and directories currently installed.) Preparing to unpack mytool_1.0.0_arm64.deb ... Unpacking mytool (1.0.0) ... Setting up mytool (1.0.0) ...然后执行which mytool # /usr/bin/mytool mytool --version # mytool version 1.0.0如果能走到这一步说明架构、权限、依赖、路径这四关都过了。我自己的经验是第一次真正的失败就是在最后一步mytool --version直接Permission denied当时立刻意识到是权限问题。5.4 从 Windows 到 ARM 设备整个链条的最后一段是把 deb 文件传到目标设备。我在 Windows 上常用scpOpenSSH 自带scp D:\work\output\mytool_1.0.0_arm64.deb userdevice:/tmp/目标设备上执行安装即可。如果你的网络环境内网有 apt 仓库也可以先把 deb 放到仓库里再apt install mytool但这不是本文重点。6. 常见问题排查表格与避坑心得6.1 高频问题速查表我把这次实际操作中遇到过的、以及同行常踩的问题整理成一个表方便直接对照现象根本原因处理方式安装后执行报Permission denied文件没有可执行权限容器内chmod x或 fpm 参数--deb-file-mode 755安装时报package architecture (amd64) does not match system (arm64)deb 内 Architecture 字段错误fpm 加-a arm64或用 dpkg-deb 时确保 control 里写arm64运行时提示cannot execute binary file: Exec format error二进制不是 ARM64 指令集重新交叉编译用file命令检查产物格式安装时提示缺少依赖比如libc6 ( 2.34)动态链接到高版本 glibc尽量静态链接或确认目标设备 glibc 版本安装完成后找不到命令路径结构不对检查 deb 包内路径是否usr/bin/, 不要带前导/dpkg -i执行时提示bad version版本号含下划线等非法字符版本号严格用字母、数字、.、-、~Windows fpm 打包后 deb 能安装但程序无法运行可能权限/架构/依赖之一有问题优先在 Linux 容器内复核全套四个验证命令6.2 几个零碎但很容易忽略的提醒第一个是符号链接。如果你的软件包内需要放符号链接比如一些服务习惯把配置指向.config用 Windows 工具做归档时符号链接经常会被当成普通文本文件。容器内制作 staging 时用 Linux 的ln -s生成真实符号链接验证时用ls -l检查是否显示-这一步别省略。第二个是关于md5sums控制文件。dpkg-deb --build会自动生成但如果你用 fpm它也会自动处理。不需要手工维护只是提醒你不要在打包过程中随意修改归档内的控制文件否则会造成安装校验问题。第三个小技巧多次打包后我发现把“验证命令”也放进同一个脚本出问题的概率会显著下降。不要省这几十秒验证命令不需要执行到目标设备上只需要在干净容器里跑一次apt install ./xxx.deb就能拦截大部分低级错误。6.3 我这次印象最深的一条教训要说印象最深其实是“权限陷阱”不是架构陷阱。我一开始花了大量时间研究交叉编译觉得 arm64 是最难的关。实际上Go 的交叉编译一行命令就解决了反而是 Windows 打包时代理出来的权限问题让我在最后一步栽了跟头。这个事很典型地说明了一件事做跨平台交付的时候对“源平台”和“目标平台”差异的盲区往往才是真正的敌人。最后再分享一个小经验如果你也经常要在 Windows 上做 Linux 包交付我强烈建议把整套流程沉淀成脚本加一个简单的说明文档放进仓库的build/目录里。我这次用的脚本后续同事直接复制到自己的 Windows 机器上改一下版本号就能跑不用重新摸索。打包这事看着不复杂但每一步的“想当然”都可能让最终产物在目标设备上崩掉把验证步骤固化下来比临时记在脑子里可靠得多。以后再遇到类似需求我也会第一时间先确认目标架构和链接方式而不是急着动手打包。

看完文章,想为自己的企业也做一次专业网站诊断?

尧图顾问免费为您评估现有网站,并给出建站/改版建议与报价方案。

免费获取方案