Nix Docker Image Building
Type: Build Tooling Relation: NixOS package manager applied to container image generation
The Nix package manager can build Docker-compatible images without a Dockerfile or Docker daemon at build time.
Core Functions
dockerTools.buildImage
Produces a Docker-compatible tarball loaded with docker load:
pkgs.dockerTools.buildImage {
name = "my-app";
tag = "latest";
contents = [ pkgs.hello ];
config.Cmd = [ "${pkgs.hello}/bin/hello" ];
}
dockerTools.buildLayeredImage
Separate layers per store path — better for caching and incremental pulls:
pkgs.dockerTools.buildLayeredImage {
name = "my-app";
tag = "latest";
contents = [ pkgs.python3 myApp ];
config.Cmd = [ "${myApp}/bin/serve" ];
}
dockerTools.streamLayeredImage
Streams the image directly to docker load without materializing the full tarball on disk.
Why This Matters
| Property | Dockerfile | Nix dockerTools |
|---|---|---|
| Reproducibility | apt-get update drifts over time | Same expression → same image, bit-for-bit |
| Image size | Base OS layer + app layers (100s of MB) | Only closure of declared deps (10-50 MB typical) |
| Build-time deps | Requires Docker daemon (or BuildKit) | Pure Nix store derivation, no daemon |
| Layer granularity | One layer per RUN instruction | One layer per store path (with buildLayeredImage) |
| Imperative steps | RUN shell commands during build | Everything expressed as Nix derivations |
Tradeoffs
- No
RUNcommands — No imperative shell execution during build. Everything must be expressed as derivations. This is the point, but it’s a different mental model. - Learning curve — Nix language + nixpkgs conventions are non-trivial.
- Debugging — No shell in the image by default (must explicitly add
pkgs.bashInteractive). - glibc/musl — Nix uses glibc by default. Watch for incompatibility with Alpine-based expectations.
Flake Pattern
# flake.nix
{
outputs = { self, nixpkgs }: let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
in {
packages.x86_64-linux.docker = pkgs.dockerTools.buildLayeredImage {
name = "myservice";
tag = "latest";
contents = [ self.packages.x86_64-linux.default ];
config = {
Cmd = [ "${self.packages.x86_64-linux.default}/bin/myservice" ];
ExposedPorts."8080/tcp" = {};
};
};
};
}
nix build .#docker
docker load < result
docker run myservice:latest
Styrene Relevance
Nix-built containers complement the NixOS fleet pattern. Where NixOS provides the host OS declaratively, dockerTools extends the same reproducibility guarantees to containerized workloads — useful for services deployed via Komodo or graduated to styrene-lab/<repo> as OCI artifacts.
Resources
Related
- NixOS — Fleet device OS using the same Nix ecosystem
- mTLS via Vault PKI — Container security at ingress