Translated from nix.dev: Building and running Docker images
Nix's dockerTools can build Docker images from package closures. Containers do not have to run NixOS; image size, caching, and build time depend on the actual dependencies and layering strategy. The build environment requires Nix, while the loading and running examples require Docker.
Why Build Docker Images with Nix?#
| Feature | Nix dockerTools | Dockerfile |
|---|---|---|
| Image Contents | Organized around declarations and runtime closures | Organized around the base image and individual instructions |
| Reproducibility | Pinning nixpkgs, source code, and dependencies improves reproducibility; not every build is inherently bit-for-bit identical | Pinning the base-image digest, dependencies, and build process also improves reproducibility |
| Layer Caching | buildLayeredImage automatically organizes dependency layers, with a configurable layer count | Layer structure is determined mainly by instructions and multistage-build design |
| Security Updates | Update inputs, then rebuild, load, and deploy the image | Update the base image/dependencies, then rebuild and deploy |
Updating Nix inputs does not automatically patch running containers. A built image is a separate layer from running instances, data volumes, and network permissions. The import <nixpkgs> examples below depend on the current search path; real projects should use pinned nixpkgs.
Basic Configuration#
Create 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!" ];
};
}Build the Image#
nix-build docker-image.nix
docker load < resultRun the Container#
docker run my-nixos-app:latestBuild an Image for a Web Service#
{ 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 is only image metadata; it does not automatically publish a host port. Use docker run --rm -p 127.0.0.1:8080:80 nixos-web-server:latest for local verification.
Multistage Builds#
In Nix, build the application derivation first, then put its runtime outputs into the image. A compiler is not included in the runtime image merely because it was used during the build. This Go example requires source code and a replacement vendorHash; it cannot be copied and run unchanged.
{ 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" ];
};
}Optimizing Layered Images#
Use buildLayeredImage for better layer caching:
{ 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";
};
}Tips for Reducing Image Size#
{ 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" ];
};
}Push the Image to a Registry#
# 加载镜像
docker load < result
# 打标签
docker tag my-nixos-app:latest registry.example.com/my-app:v1.0
# 推送
docker push registry.example.com/my-app:v1.0Practical Examples#
Database Images
This example retains the historical PostgreSQL 14 version scope. A database needs more than binaries: it also needs a user, data-directory ownership, and initial setup. Images should not contain a real database or password. Initialize a separate named volume first, then run the service.
{ 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" ];
};
}Initialization and startup steps (empty volume, local verification only):
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:14Container engines may differ in how they initialize and assign ownership to new volumes. First check that UID 999 can write to the volume. The example above does not enable network access. Before enabling TCP, publishing ports, and configuring authentication, explicitly configure listen_addresses and pg_hba.conf, and inject credentials through runtime secrets. Database upgrades also require the backup and migration procedures for the relevant versions.
References & further reading
Adapted from technical notes. Examples use generic names.