1. 分发目标的四类形态
一个 Zig 程序的发布通常要同时满足几类用户:
| 形态 | 受众 | 关键约束 |
|---|---|---|
| 静态二进制 tar.gz | 通用用户 | glibc 版本、CPU 基线 |
| 容器镜像 | 服务端/K8s | 体积、多架构、非 root |
| 系统包(deb/rpm/Arch) | 发行版用户 | 依赖声明、安装路径 |
| 包管理器(Homebrew/Scoop/Nix) | macOS/Windows/极客 | formula 维护、自动更新 |
Zig 的独特优势是一次编译产出零运行时依赖的静态二进制,这四类形态都只是「把同一个文件放到不同容器里」。难点不在编译,而在构建矩阵的正确性与元数据的严谨。构建脚本本身的写法,见 /zig-build-system/。
2. 静态链接:musl 还是 glibc
2.1 两个目标三元组的差别
# 动态链接 glibc:体积小,但依赖目标机器的 glibc 版本
zig build-exe src/main.zig -target x86_64-linux-gnu -O ReleaseFast
# 静态链接 musl:单文件,无任何动态依赖
zig build-exe src/main.zig -target x86_64-linux-musl -O ReleaseFast -static
# 交叉编译到 ARM64 静态
zig build-exe src/main.zig -target aarch64-linux-musl -O ReleaseFast
| 维度 | *-linux-gnu | *-linux-musl |
|---|---|---|
| 依赖 | glibc ≥ 编译时版本 | 无 |
| 体积 | 较小 | 较大(含 libc) |
| DNS 解析 | 走 NSS,支持 /etc/nsswitch.conf | 走纯 Zig 实现,配置简单 |
| 兼容性 | 编译机 glibc 越新,能跑的机器越少 | 任何 Linux |
| 适用 | 发行版包、已知运行环境 | 通用发布、容器、嵌入式 |
发布用 musl,发行版包用 gnu。因为发行版包由发行版的构建系统编译,天然匹配该发行版的 glibc;而通用 tar.gz 必须能在任何 Linux 上跑,只能静态。
2.2 CPU 基线:别默认用 native
# 危险:会针对编译机 CPU 生成 AVX-512 指令,在老 CPU 上直接 SIGILL
zig build-exe src/main.zig -target x86_64-linux-musl -mcpu=native
# 安全:基线 x86-64-v2(SSE4.2/AVX,2009 年后 CPU)
zig build-exe src/main.zig -target x86_64-linux-musl -mcpu=x86_64_v2
# 最保守:纯 x86-64(SSE2)
zig build-exe src/main.zig -target x86_64-linux-musl -mcpu=baseline
-mcpu=native 是发布流程里最隐蔽的坑:CI 跑在支持 AVX-512 的机器上,产物发到老 VPS 就崩。经验取值:面向 2015 年后的服务器用 x86_64_v3(AVX2),面向未知环境用 x86_64_v2。ARM64 同理,用 baseline 而非 native。
2.3 构建矩阵
一个典型的发布矩阵是 6 个目标:
x86_64-linux-musl aarch64-linux-musl
x86_64-linux-gnu aarch64-linux-gnu
x86_64-macos aarch64-macos
x86_64-windows-gnu
Zig 本身就能交叉编译到全部这些目标,不需要安装任何交叉工具链——这是它相比 C/C++ 的巨大优势。深入交叉编译的 sysroot 与链接细节,见 /zig-embedded-cross-compile/。
3. 容器镜像
3.1 从 scratch 开始
因为二进制是静态的,容器可以从空镜像开始:
FROM scratch
COPY --chown=65534:65534 zig-app /zig-app
USER 65534:65534
EXPOSE 8080
ENTRYPOINT ["/zig-app"]
镜像体积就是二进制体积,通常 2~15 MB。对比 Ubuntu 基础的 Node 镜像(约 1 GB),差距是数量级。
若需要 CA 证书(HTTPS 客户端)、时区数据、/etc/passwd,加一层极小的 distroless 或手工拷入:
FROM alpine:3.20 AS certs
RUN apk add --no-cache ca-certificates
FROM scratch
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=certs /usr/share/zoneinfo/UTC /usr/share/zoneinfo/UTC
COPY zig-app /zig-app
USER 65534:65534
ENTRYPOINT ["/zig-app"]
3.2 多阶段构建与 BuildKit
编译阶段与运行阶段分离,编译工具链不进最终镜像:
# syntax=docker/dockerfile:1.7
FROM --platform=$BUILDPLATFORM alpine:3.20 AS build
RUN apk add --no-cache curl xz
ARG ZIG_VERSION=0.14.1
RUN curl -fsSL https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz \
| tar -xJ -C /opt && mv /opt/zig-linux-x86_64-${ZIG_VERSION} /opt/zig
ENV PATH="/opt/zig:${PATH}"
WORKDIR /src
COPY build.zig build.zig.zon ./
COPY src ./src
# 只读缓存挂载,加速依赖拉取
RUN --mount=type=cache,target=/root/.cache/zig \
zig build -Doptimize=ReleaseFast -Dtarget=aarch64-linux-musl --prefix /out
FROM scratch
COPY --from=build /out/bin/zig-app /zig-app
ENTRYPOINT ["/zig-app"]
--mount=type=cache 让 Zig 的全局缓存跨构建复用,重复构建从分钟级降到秒级。--platform=$BUILDPLATFORM 让编译在原生架构上跑(快),只让产物针对目标架构——配合 -Dtarget 实现跨架构构建,无需 QEMU 模拟。
3.3 多架构 manifest
用 docker buildx 一次产出多架构镜像并推成 manifest list:
docker buildx create --name zigbuilder --use
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag ghcr.io/me/zig-app:1.4.0 \
--tag ghcr.io/me/zig-app:latest \
--push .
docker pull 时客户端按自身架构自动选层。验证 manifest:
docker manifest inspect ghcr.io/me/zig-app:1.4.0 | jq '.manifests[].platform'
多阶段构建的层缓存策略与 BuildKit 的进阶用法,参见 Docker BuildKit 多阶段构建 。
3.4 OCI 镜像不等于 Docker 镜像
Zig 程序甚至可以直接生成 OCI 镜像 tar,不依赖 Docker 守护进程:
# 手工构造一个最小 OCI layout
mkdir -p oci/blobs/sha256
# 1. 写 config json 与 layer tar,各自算 sha256
# 2. 生成 manifest.json 与 index.json
# 3. 打成 tar 直接推 registry
适合 CI 环境无 Docker 的场景。OCI 规范分 config、manifest、index 三层:config 描述 rootfs 与 entrypoint,manifest 指向 layer blob,index 汇总多架构 manifest。
4. 系统包
4.1 Debian / Ubuntu(.deb)
目录结构固定:
zig-app_1.4.0_amd64/
├── DEBIAN/
│ ├── control # 元数据
│ ├── postinst # 安装后脚本(可选)
│ └── prerm # 卸载前脚本(可选)
└── usr/
├── bin/zig-app
└── share/doc/zig-app/README.md
control 文件:
Package: zig-app
Version: 1.4.0
Architecture: amd64
Maintainer: Leeting Yan <me@example.com>
Section: utils
Priority: optional
Description: A high-performance tool written in Zig
Multi-line long description goes here.
打包命令:
dpkg-deb --build --root-owner-group zig-app_1.4.0_amd64
# 校验
dpkg-deb --info zig-app_1.4.0_amd64.deb
lintian zig-app_1.4.0_amd64.deb
--root-owner-group 保证包内文件属主是 root:root 而非当前用户——漏掉这个参数会导致安装后文件属主异常。
4.2 RPM(Fedora / RHEL / openSUSE)
用 fpm 最省事,也可以写 spec:
fpm -s dir -t rpm \
-n zig-app -v 1.4.0 \
--rpm-os linux \
--architecture x86_64 \
-C pkgroot \
usr/bin/zig-app
手写 spec 的核心段落:
Name: zig-app
Version: 1.4.0
Release: 1%{?dist}
Summary: A high-performance tool written in Zig
License: MIT
URL: https://github.com/me/zig-app
Source0: %{name}-%{version}.tar.gz
%description
A high-performance tool written in Zig.
%install
install -D -m 0755 zig-app %{buildroot}%{_bindir}/zig-app
%files
%{_bindir}/zig-app
%changelog
* Tue Oct 07 2026 Leeting Yan <me@example.com> - 1.4.0-1
- Initial package
RPM 的 %files 必须穷举所有安装的文件,漏一个会导致构建失败(installed but unpackaged files 错误)。这是与 deb 最大的心智差异。
4.3 Arch(PKGBUILD)
pkgname=zig-app
pkgver=1.4.0
pkgrel=1
pkgdesc="A high-performance tool written in Zig"
arch=('x86_64' 'aarch64')
url="https://github.com/me/zig-app"
license=('MIT')
source=("$pkgname-$pkgver.tar.gz::https://github.com/me/zig-app/archive/v$pkgver.tar.gz")
sha256sums=('SKIP')
build() {
cd "$srcdir/$pkgname-$pkgver"
zig build -Doptimize=ReleaseFast --prefix "$pkgdir/usr"
}
Arch 的哲学是从源码构建,pkgdir 就是打包根目录。sha256sums 必须填真实值(上面写 SKIP 只是为了示例),否则 makepkg 会拒绝。
4.4 让 Zig 直接产出包
社区有 zig-bootstrap 与若干 build.zig 辅助模块可以生成 deb/rpm。更稳妥的做法是在 CI 里用 dpkg-deb/fpm 后处理,把打包逻辑与构建逻辑解耦。
5. macOS:Homebrew formula
class ZigApp < Formula
desc "A high-performance tool written in Zig"
homepage "https://github.com/me/zig-app"
version "1.4.0"
license "MIT"
on_macos do
on_arm do
url "https://github.com/me/zig-app/releases/download/v1.4.0/zig-app-1.4.0-aarch64-macos.tar.gz"
sha256 "0a1b2c3d4e5f60718293a4b5c6d7e8f90123456789abcdef0123456789abcdef"
end
on_intel do
url "https://github.com/me/zig-app/releases/download/v1.4.0/zig-app-1.4.0-x86_64-macos.tar.gz"
sha256 "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
end
end
def install
bin.install "zig-app"
end
test do
assert_match "1.4.0", shell_output("#{bin}/zig-app --version")
end
end
本地测试:
brew install --build-from-source ./Formula/zig-app.rb
brew audit --strict --new-formula zig-app
brew test zig-app
Homebrew 的 sha256 是硬校验,每次发版必须更新 formula 并提 PR 到 homebrew-core(或自建 tap)。自建 tap 的流程更简单:
brew tap me/tools
brew install zig-app
macOS 的 Gatekeeper 还会要求二进制签名与公证(notarization),否则用户首次运行会看到「无法验证开发者」。签名需要 Apple Developer 账号:
codesign --force --options runtime --sign "Developer ID Application: ..." zig-app
xcrun notarytool submit zig-app.zip --keychain-profile "AC_PASSWORD" --wait
xcrun stapler staple zig-app
6. 发布工程
6.1 版本一致性
版本号散落在 build.zig.zon、Git tag、容器 tag、formula 四处,必须由单一来源派生。做法是在 build.zig 里读 Git tag:
const version = blk: {
const raw = std.process.Child.run(.{
.allocator = b.allocator,
.argv = &.{ "git", "describe", "--tags", "--always" },
}) catch break :blk "0.0.0-dev";
break :blk std.mem.trim(u8, raw.stdout, " \n\r");
};
const opts = b.addOptions();
opts.addOption([]const u8, "version", version);
exe.root_module.addOptions("build_options", opts);
代码里 @import("build_options").version 即可拿到版本,保证二进制自报版本与 tag 一致。发布工程的通用原则,参见 开源项目发布工程实践
。
6.2 CI 矩阵发布
name: release
on:
push:
tags: ["v*"]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- target: x86_64-linux-musl
os: linux
- target: aarch64-linux-musl
os: linux
- target: x86_64-macos
os: macos
- target: aarch64-macos
os: macos
- target: x86_64-windows-gnu
os: windows
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v1
with:
version: 0.14.1
- run: zig build -Doptimize=ReleaseFast -Dtarget=${{ matrix.target }}
- run: tar -czf zig-app-${{ matrix.target }}.tar.gz -C zig-out/bin zig-app
- uses: actions/upload-artifact@v4
with:
name: zig-app-${{ matrix.target }}
path: zig-app-*.tar.gz
6.3 校验和与签名
发布必须附带 SHA256 校验和,让用户能验证下载完整性:
sha256sum zig-app-*.tar.gz > SHA256SUMS
# macOS 上 GNU sha256sum 不可用,用 shasum -a 256 或 zig 自带工具
更严格的做法是用 minisign 或 GPG 对 SHA256SUMS 签名,用户用公钥验证:
minisign -Sm SHA256SUMS
# 用户侧
minisign -Vm SHA256SUMS -P RWQf...公钥
发布产物清单建议固定为:
zig-app-1.4.0-x86_64-linux-musl.tar.gz
zig-app-1.4.0-aarch64-linux-musl.tar.gz
zig-app-1.4.0-x86_64-macos.tar.gz
zig-app-1.4.0-aarch64-macos.tar.gz
zig-app-1.4.0-x86_64-windows-gnu.zip
SHA256SUMS
SHA256SUMS.minisig
7. 常见陷阱
-mcpu=native泄漏到发布:最常见的线上崩溃原因。CI 里显式指定-mcpu=baseline或x86_64_v2。zig build默认 Debug:忘了-Doptimize=ReleaseFast,产物慢 10 倍且体积大 3 倍。- 容器里
USER忘了设:默认以 root 运行,K8s 的安全策略会直接拒绝。 - deb 的
--root-owner-group:漏掉会让包内文件属主是构建用户。 - Homebrew sha256 未更新:
brew install报SHA256 mismatch。 - 多架构镜像忘了
--push:docker buildx build不加--push时多平台产物只在构建缓存里,本地docker images看不到。 - 静态链接下的 DNS:musl 的解析器不读
/etc/nsswitch.conf,在某些企业环境(如依赖 LDAP 的 DNS)下行为与 glibc 不同。
小结
Zig 的分发链路可以概括成一句话:一次静态编译,分发到四种容器。要点:
- 通用发布用
*-musl静态,发行版包用*-gnu。 - CPU 基线显式指定,永远不用
native。 - 容器从
scratch起,多阶段 + BuildKit 缓存挂载。 - 系统包注意 deb 的属主与 rpm 的
%files穷举。 - 版本号由 Git tag 单一来源派生,写进
build_options。 - 发布必带 SHA256SUMS 与签名。
如果你在考虑用 Nix 做可复现构建与分发,它和 Zig 的静态二进制理念高度契合:derivation 把「输入哈希 → 产物哈希」变成可验证的闭包,正好补上 tar.gz 分发缺少的可复现性。
Zig 把「跨平台发布」这件在 C/C++ 里需要一整套交叉工具链与构建农场的事,压缩成了一条 zig build -Dtarget=... 命令——这是它最被低估的工程价值。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。