翻译自 nix.dev: Building and running Docker images
Nix 的 dockerTools 可以从软件包 closure 构建 Docker 镜像。它不要求容器运行 NixOS;大小、缓存和构建时间取决于实际依赖与分层策略。构建环境需要 Nix,加载和运行示例需要 Docker。
为什么用 Nix 构建 Docker 镜像?#
| 特性 | Nix dockerTools | Dockerfile |
|---|---|---|
| 镜像内容 | 根据声明与运行时 closure 组织内容 | 根据基础镜像和各条指令组织内容 |
| 可复现性 | 固定 nixpkgs、源代码和依赖可提高可复现性;不是所有构建天然位级相同 | 固定基础镜像 digest、依赖和构建过程也能提高可复现性 |
| 层缓存 | buildLayeredImage 自动组织依赖层,可调层数 | 层结构主要由指令与多阶段构建设计决定 |
| 安全更新 | 更新输入并重新构建、加载和部署镜像 | 更新基础镜像/依赖并重新构建和部署 |
更新 Nix 输入不会自动修补已运行容器。构建好的镜像与运行实例、数据卷、网络权限是不同层。以下 import <nixpkgs> 示例依赖当前搜索路径;实际项目应使用锁定的 nixpkgs。
基础配置#
创建一个 docker-image.nix:
{ pkgs ? import <nixpkgs> {} }:
pkgs.dockerTools.buildImage {
name = "my-nixos-app";
tag = "latest";
copyToRoot = pkgs.buildEnv {
name = "image-root";
paths = [
pkgs.bash
pkgs.coreutils
pkgs.cowsay
];
pathsToLink = [ "/bin" ];
};
config = {
Cmd = [ "/bin/cowsay" "Hello from NixOS!" ];
};
}构建镜像#
nix-build docker-image.nix
docker load < result运行容器#
docker run my-nixos-app:latest构建运行 Web 服务的镜像#
{ pkgs ? import <nixpkgs> {} }:
pkgs.dockerTools.buildImage {
name = "nixos-web-server";
tag = "latest";
copyToRoot = pkgs.buildEnv {
name = "image-root";
paths = [ pkgs.nginx ];
pathsToLink = [ "/bin" ];
};
extraCommands = "mkdir -p tmp";
# 明确提供配置;包内二进制不会自动拥有可写 /var 和 /etc/nginx
config = {
Cmd = [ "${pkgs.nginx}/bin/nginx" "-c" "${pkgs.writeText "nginx.conf" ''
daemon off;
pid /tmp/nginx.pid;
error_log /dev/stderr;
events {}
http {
access_log /dev/stdout;
client_body_temp_path /tmp/client_body;
proxy_temp_path /tmp/proxy;
fastcgi_temp_path /tmp/fastcgi;
uwsgi_temp_path /tmp/uwsgi;
scgi_temp_path /tmp/scgi;
server { listen 80; location / { return 200 "Nix container\n"; } }
}
''}" ];
ExposedPorts."80/tcp" = {};
};
}ExposedPorts 只是镜像元数据,不会自动发布宿主机端口。可用 docker run --rm -p 127.0.0.1:8080:80 nixos-web-server:latest 做本地验证。
多阶段构建#
在 Nix 中先构建应用 derivation,再把运行时产物放入镜像;编译器不会仅因为参与构建就被包含在运行镜像里。此 Go 例子需准备源代码并替换 vendorHash,不能直接复制运行。
{ pkgs ? import <nixpkgs> {} }:
let
# 构建应用
myApp = pkgs.buildGoModule {
pname = "my-app";
version = "1.0.0";
src = ./.; # 含 go.mod、go.sum 及主程序
vendorHash = pkgs.lib.fakeHash; # 用实际依赖 hash 替换;无依赖时按 builder 合约设 null
};
in
pkgs.dockerTools.buildLayeredImage {
name = "my-go-app";
tag = "latest";
contents = [ myApp pkgs.cacert ];
config = {
Cmd = [ "/bin/my-app" ];
};
}分层镜像优化#
使用 buildLayeredImage 可以创建更好的层缓存:
{ pkgs ? import <nixpkgs> {} }:
pkgs.dockerTools.buildLayeredImage {
name = "layered-app";
tag = "latest";
# 基础层
contents = with pkgs; [
bash
coreutils
curl
];
# 应用层
extraCommands = ''
mkdir -p /app
cp ${./my-app} /app/
'';
config = {
Cmd = [ "/app/my-app" ];
WorkingDir = "/app";
};
}镜像瘦身技巧#
{ pkgs ? import <nixpkgs> {} }:
pkgs.dockerTools.buildImage {
name = "minimal-app";
tag = "latest";
# 使用 pkgsStatic 静态链接
copyToRoot = pkgs.buildEnv {
name = "image-root";
paths = [ pkgs.pkgsStatic.hello ];
pathsToLink = [ "/bin" ];
};
# 移除不必要的包
runAsRoot = ''
#!${pkgs.runtimeShell}
rm -rf /share/doc /share/man
'';
config = {
Cmd = [ "/bin/hello" ];
};
}推送镜像到仓库#
# 加载镜像
docker load < result
# 打标签
docker tag my-nixos-app:latest registry.example.com/my-app:v1.0
# 推送
docker push registry.example.com/my-app:v1.0实际应用示例#
数据库镜像
此例保留 PostgreSQL 14 的历史版本范围。数据库不仅需要二进制,还需要用户、数据目录所有权和首次初始化;镜像不应内置真实数据库或密码。先初始化独立命名卷,再运行服务。
{ pkgs ? import <nixpkgs> {} }:
pkgs.dockerTools.buildLayeredImage {
name = "nixos-postgres";
tag = "14";
contents = [ pkgs.postgresql_14 ];
extraCommands = ''
mkdir -p etc var/lib/postgresql/data tmp
echo 'postgres:x:999:999:PostgreSQL:/var/lib/postgresql:/bin/false' > etc/passwd
echo 'postgres:x:999:' > etc/group
'';
fakeRootCommands = ''
chown -R 999:999 var/lib/postgresql
'';
config = {
User = "999:999";
Cmd = [ "/bin/postgres" "-D" "/var/lib/postgresql/data" ];
Volumes."/var/lib/postgresql/data" = {};
ExposedPorts = {
"5432/tcp" = {};
};
Env = [ "PGDATA=/var/lib/postgresql/data" ];
};
}初始化与运行步骤(空卷、仅本地验证):
docker load < result
docker volume create pg-example-data
docker run --rm --mount source=pg-example-data,target=/var/lib/postgresql/data \
nixos-postgres:14 /bin/initdb -D /var/lib/postgresql/data
docker run --rm --mount source=pg-example-data,target=/var/lib/postgresql/data \
nixos-postgres:14不同容器引擎对新卷初始化和所有权的行为可能不同;先检查 UID 999 对卷是否可写。上例未开放网络访问。启用 TCP、发布端口和设置认证前,需单独声明 listen_addresses 与 pg_hba.conf,并通过运行时 secret 注入凭据。数据库升级还需遵循对应版本的备份与迁移流程。
参考与延伸
根据技术笔记重新整理,示例使用通用名称。