为什么 Nix + CI/CD 是绝配?
CI/CD 面临的核心挑战与 Nix 的优势完美对齐:
构建可复现性
传统 CI 中,
apt install 的结果随时间变化,导致"今天能过 CI 明天不行"。Nix + flake.lock 确保相同代码永远产生相同构建结果。环境一致性
开发机、CI 机、生产服务器使用完全相同的 derivation,消除"在我本地没问题"的问题类别。
增量缓存
Nix 的内容寻址缓存使得只有真正变化的部分需要重新构建。Cachix 等二进制缓存服务让团队共享构建结果,CI 速度大幅提升。
GitHub Actions + Nix
使用 cachix/install-nix-action 在 GitHub Actions 中安装 Nix:
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
# 1. 检出代码
- uses: actions/checkout@v4
# 2. 安装 Nix(Determinate Systems 的 Action,推荐)
- uses: DeterminateSystems/nix-installer-action@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
# 3. 配置 Cachix 二进制缓存(可选但强烈推荐)
- uses: cachix/cachix-action@v14
with:
name: my-cache # 你的 Cachix cache 名称
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
# 4. 检查 flake
- name: Check flake
run: nix flake check
# 5. 构建
- name: Build
run: nix build
# 6. 运行测试
- name: Test
run: nix develop --command npm test
使用 nix develop 运行 CI 命令
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Run tests in dev environment
run: |
nix develop .#ci --command sh -c "
cargo build --release
cargo test
cargo clippy -- -D warnings
"
Cachix — 二进制缓存服务
Cachix 是专为 Nix 设计的二进制缓存托管服务。上传构建结果后,团队成员和 CI 可以直接下载,无需重新构建:
# 安装 cachix CLI
nix profile install nixpkgs#cachix
# 登录(获取 API token)
cachix authtoken <YOUR_TOKEN>
# 创建新的 cache
cachix create my-project-cache
# 使用 cache(添加到 Nix 配置)
cachix use my-project-cache
# 这会在 /etc/nix/nix.conf(或 ~/.config/nix/nix.conf)中添加:
# substituters = https://cache.nixos.org https://my-project-cache.cachix.org
# trusted-public-keys = cache.nixos.org-1:... my-project-cache.cachix.org-1:...
# 构建并推送到 cache
nix build .#my-app
cachix push my-project-cache ./result
在 NixOS 中配置二进制缓存
{
nix.settings = {
substituters = [
"https://cache.nixos.org"
"https://nix-community.cachix.org" # nix-community 工具
"https://my-project.cachix.org" # 你的项目缓存
];
trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCUSeBc="
"my-project.cachix.org-1:xxxx..."
];
};
}
nix build 构建产物
# 构建默认包
nix build
# 构建指定包
nix build .#my-app
# 构建并输出到指定路径(不使用 result 符号链接)
nix build .#my-app --out-link /tmp/my-app-build
# 不创建 result 链接(CI 中常用,避免 result 成为 GC root)
nix build .#my-app --no-link
# 查看构建产物的 store 路径
nix build .#my-app --print-out-paths
# 构建所有支持的平台(交叉编译需要额外配置)
nix build .#packages.x86_64-linux.my-app
nix build .#packages.aarch64-linux.my-app
nix flake check — 静态检查
# 检查 flake 的所有 outputs 是否有效
nix flake check
# 只检查,不构建(更快)
nix flake check --no-build
# 检查包括:
# - outputs 结构是否符合 Nix 规范
# - packages.* 是否可以求值(不一定构建)
# - NixOS 配置是否能求值(nixosConfigurations.*)
# - devShells.* 是否有效
# 检查 Nix 代码格式(nixpkgs-fmt)
nix develop --command nixpkgs-fmt --check .
用 Nix 构建 Docker 镜像
pkgs.dockerTools 可以不需要 Dockerfile,直接用 Nix 构建 Docker 镜像,结果完全可复现:
# flake.nix 中定义 Docker 镜像
{ self, nixpkgs, ... }:
let
pkgs = nixpkgs.legacyPackages."x86_64-linux";
myApp = self.packages."x86_64-linux".my-app;
in {
packages."x86_64-linux".docker-image = pkgs.dockerTools.buildImage {
name = "my-app";
tag = "latest";
# 只包含必要的文件(比 FROM ubuntu 的镜像小 10 倍以上)
contents = [
myApp
pkgs.busybox # 基础工具(可选)
pkgs.cacert # TLS 证书(HTTPS 必需)
];
# 等价于 Dockerfile 的 CMD
config = {
Cmd = [ "${myApp}/bin/my-app" ];
Env = [ "NODE_ENV=production" ];
ExposedPorts = { "3000/tcp" = {}; };
};
};
# 流式镜像(更高效,适合大镜像)
packages."x86_64-linux".docker-stream = pkgs.dockerTools.streamLayeredImage {
name = "my-app";
tag = "latest";
contents = [ myApp pkgs.cacert ];
config.Cmd = [ "${myApp}/bin/my-app" ];
};
}
# 构建 Docker 镜像(输出到 /nix/store)
nix build .#docker-image
# 将镜像加载到 Docker
docker load < result
# 验证镜像
docker images | grep my-app
docker run --rm my-app:latest
完整 CI/CD 实战:构建并推送 Docker 镜像
# .github/workflows/docker.yml
name: Build and Push Docker Image
on:
push:
tags: ['v*']
jobs:
docker:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- uses: cachix/cachix-action@v14
with:
name: my-project
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image with Nix
run: nix build .#docker-image
- name: Push to registry
run: |
docker load < result
docker tag my-app:latest ghcr.io/${{ github.repository }}:${{ github.ref_name }}
docker push ghcr.io/${{ github.repository }}:${{ github.ref_name }}
Nix 构建 vs Dockerfile 构建
Nix 构建的 Docker 镜像通常比传统 Dockerfile 构建的小 3-10 倍,因为只包含精确声明的依赖,没有 apt 缓存、构建工具等无用文件。同时构建完全可复现,不受构建时间和网络环境影响。对于 Rust/Go 等静态链接语言,最终镜像可以小到 10-20 MB。
本章小结
Nix 在 CI/CD 中的核心价值:① 用 flake.lock 确保 CI 构建永远可复现;② 用 nix develop --command 保证 CI 和本地开发环境完全一致;③ 用 Cachix 共享二进制缓存加速构建;④ 用 dockerTools 无需 Dockerfile 就能构建更小、更安全的 Docker 镜像。