Zig 打包与分发:容器镜像、系统包与 Homebrew

Zig 产出的是零依赖静态二进制,这让分发路径比多数语言简单得多。本文覆盖 musl 静态链接与交叉编译矩阵、scratch/distroless 容器镜像与多架构 manifest、deb/rpm/Arch 打包、Homebrew formula,以及 CI 矩阵发布与校验签名。

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 的分发链路可以概括成一句话:一次静态编译,分发到四种容器。要点:

  1. 通用发布用 *-musl 静态,发行版包用 *-gnu。
  2. CPU 基线显式指定,永远不用 native。
  3. 容器从 scratch 起,多阶段 + BuildKit 缓存挂载。
  4. 系统包注意 deb 的属主与 rpm 的 %files 穷举。
  5. 版本号由 Git tag 单一来源派生,写进 build_options。
  6. 发布必带 SHA256SUMS 与签名。

如果你在考虑用 Nix 做可复现构建与分发,它和 Zig 的静态二进制理念高度契合:derivation 把「输入哈希 → 产物哈希」变成可验证的闭包,正好补上 tar.gz 分发缺少的可复现性。

Zig 把「跨平台发布」这件在 C/C++ 里需要一整套交叉工具链与构建农场的事,压缩成了一条 zig build -Dtarget=... 命令——这是它最被低估的工程价值。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「系统编程」更多文章

  1. Zig 终端 TUI 开发:终端控制、布局与交互
  2. Zig GPU 计算:Vulkan Compute 与着色器绑定
  3. Zig WASI 与组件模型:沙箱运行时与宿主嵌入