DEV Community

NACAMURA Mitsuhiro
NACAMURA Mitsuhiro

Posted on • Originally published at m15a.dev

How much smaller are NixNG container images compared to NixOS, really?

NixNG

NixNG is a Linux distribution currently under development, derived
from NixOS. It is being positioned as a lightweight alternative to
NixOS, specifically targeting container environments.

The project achieves its reduced size by omitting systemd from
NixOS and minimizing the default package set. Since NixOS container
images are a bit heavy, a lighter alternative is definitely welcome.
Dropping systemd also opens up possibilities for using
lighter-weight init systems like runit, which I find to be an exciting
prospect.

But just how much smaller is NixNG in practice, compared to an image
based on the original NixOS? I created both images to compare their
sizes.

The experiment

The official Nix image is available on DockerHub. I built
a comparable NixNG image that includes only the Nix package manager and
then compared the sizes of the two.

Distro Package Base image/Build
Official Nix Nix 2.32.2 DockerHub nixos/nix:2.32.2
NixNG Nix 2.32.2 Built using the method below

I ensured that the platform was consistent: x86_64-linux.

NixNG build flake setup

I set up a minimal flake project for the test.

flake.nix:

{
  inputs = {
    nixng.url = "github:nix-community/NixNG";
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
  };
  outputs =
    {
      self,
      nixng,
      nixpkgs,
      ...
    }:
    {
      examples.nixng-nix = import ./examples/nixng-nix.nix {
        inherit (nixng) nglib;
        inherit nixpkgs;
        system = "x86_64-linux";
      };
    };
}
Enter fullscreen mode Exit fullscreen mode

The system of interest for comparison is defined in
examples.nixng-nix, based on
an example from the NixNG repository:

examples/nixng-nix.nix:

{
  nglib,
  nixpkgs,
  system,
}:

nglib.makeSystem {
  inherit nixpkgs;
  inherit system;
  name = "nixng-nix";
  config =
    { pkgs, ... }:
    {
      dumb-init = {
        enable = true;
        type.shell = { };
      };
      nix = {
        enable = true;
        package = pkgs.nixVersions.nix_2_32;
        config = {
          experimental-features = [
            "nix-command"
            "flakes"
          ];
          sandbox = false;
        };
      };
    };
}
Enter fullscreen mode Exit fullscreen mode

Build and load the OCI image

I generated and loaded the Docker image tar file:

$ docker run --rm --platform linux/amd64 -v $(pwd):/work -w /work \
    nixos/nix:2.32.2 bash -c '
    {
        echo "filter-syscalls = false"
        echo "experimental-features = nix-command flakes"
    } >> /etc/nix/nix.conf
    nix build .#examples.nixng-nix.config.system.build.ociImage.stream
    ./result > nixng-nix-image.tar
    '

$ docker image load -i nixng-nix-image.tar 
Enter fullscreen mode Exit fullscreen mode

Note: Since I use Docker Desktop on an ARM Mac, I
had to add filter-syscalls = false to the Nix configuration
to avoid an error during the build.

Results

Image sizes

With the official Nix image already available, I checked the sizes:

$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
nixos/nix    2.32.2    f9b3c7811e27   55 years ago   436MB
nixos/nix    latest    f9b3c7811e27   55 years ago   436MB
nixng-nix    latest    26361447b07f   55 years ago   356MB
Enter fullscreen mode Exit fullscreen mode

The official image was 436MB, while the NixNG image came in
at 356MB. That's a size reduction of 80MB (approximately 18%).

/nix/store/ contents

I was curious to see exactly which packages were omitted.
First, I looked at the package count in the /nix/store/ directory:

$ docker run --platform linux/amd64 nixos/nix:2.32.2 \
    sh -c 'ls /nix/store' 2>/dev/null | rg '^[a-z0-9]+-' | wc -l
     121
$ docker run --platform linux/amd64 nixng-nix \
    sh -c 'ls /nix/store' 2>/dev/null | rg '^[a-z0-9]+-' | wc -l
      81
Enter fullscreen mode Exit fullscreen mode

The official Nix image contains 121 packages, compared to 81
in NixNG—a reduction of 40 packages.

Next, I looked at the detailed differences. After preprocessing the
package names to normalize version and date differences:

$ docker run --platform linux/amd64 nixos/nix:2.32.2 \
    sh -c 'ls /nix/store' 2>/dev/null | rg '^[a-z0-9]+-' \
    | cut -d'-' -f2- \
    | sed -Ee 's#[0-9]{4}(-?[0-9]{2}(-?[0-9]{2})?)?[a-z]?#DATE#' \
        -e 's#[0-9]+\.[0-9]+([.p-][0-9]+)?#VERSION#' \
    | sort \
    | uniq \
    > nixos-nix-nix-store.txt

$ docker run --platform linux/amd64 nixng-nix \
    sh -c 'ls /nix/store' 2>/dev/null | rg '^[a-z0-9]+-' \
    | cut -d'-' -f2- \
    | sed -Ee 's#[0-9]{4}(-?[0-9]{2}(-?[0-9]{2})?)?[a-z]?#DATE#' \
        -e 's#[0-9]+\.[0-9]+([.p-][0-9]+)?#VERSION#' \
    | sort \
    | uniq \
    > nixng-nix-nix-store.txt

Enter fullscreen mode Exit fullscreen mode

Comparing the diff reveals the omitted components:

--- nixos-nix-nix-store.txt 2025-11-08 21:45:41
+++ nixng-nix-nix-store.txt 2025-11-08 21:45:48
@@ -1,6 +1,5 @@
 acl-VERSION
 attr-VERSION
-audit-VERSION-lib
 aws-c-auth-VERSION
 aws-c-cal-VERSION
 aws-c-common-VERSION
@@ -14,73 +13,45 @@
 aws-checksums-VERSION
 aws-crt-cpp-VERSION
 aws-sdk-cpp-VERSION
-base-system
 bash-interactive-VERSION
-bash-interactive-VERSION-man
 bash-VERSION
 boehm-gc-VERSION
 boost-VERSION
 brotli-VERSION-lib
 busybox-VERSION
 bzip2-VERSION
-channel-nixos
-coreutils-full-VERSION
-coreutils-VERSION
+ca-certificates.crt
 curl-VERSION
-curl-VERSION-bin
-curl-VERSION-man
-db-VERSION
-dns-root-data-DATE
-editline-VERSION
-expat-VERSION
-findutils-VERSION
+dumb-init-VERSION
+editline-VERSION-unstable-DATE
 gcc-VERSION-lib
 gcc-VERSION-libgcc
-gdbm-VERSION-lib
-gettext-VERSION
-git-minimal-VERSION
+generate-shadow
 glibc-VERSION
-glibc-VERSION-bin
-gmp-with-cxx-VERSION
-gnugrep-VERSION
-gnused-VERSION
-gnutar-VERSION
-groff-VERSION
-gzip-VERSION
-gzip-VERSION-man
+group
+hwloc-VERSION-lib
 iana-etc-DATE
+icu4c-VERSION
 keyutils-VERSION-lib
 krb5-VERSION-lib
-ldns-VERSION
-less-668
-less-668-man
 libarchive-VERSION-lib
 libblake3-VERSION
-libbsd-VERSION
-libcap-ng-VERSION
-libcap-VERSION-lib
-libcbor-VERSION
 libcpuid-VERSION
-libedit-DATE-VERSION
-libfido2-VERSION
 libgit2-VERSION-lib
 libidn2-VERSION
-libmd-VERSION
-libpipeline-VERSION
 libpsl-VERSION
 libseccomp-VERSION-lib
 libsodium-VERSION
 libssh2-VERSION
 libunistring-VERSION
-libxcrypt-VERSION
 libxml2-VERSION
-linux-pam-VERSION
 llhttp-VERSION
 lowdown-VERSION-lib
-man-db-VERSION
-manifest.nix
 ncurses-VERSION
+ndig4f4dx8bmrmyr5vfm19g02r9l9ggm-source
 nghttp2-VERSION-lib
+nghttp3-VERSION
+ngtcp2-VERSION
 nix-cmd-VERSION
 nix-expr-VERSION
 nix-fetchers-VERSION
@@ -89,32 +60,20 @@
 nix-store-VERSION
 nix-util-VERSION
 nix-VERSION
-nss-cacert-VERSION
-openssh-VERSION
+nix.conf
+nixng-nixng-nix
+onetbb-DATE.VERSION
 openssl-VERSION
+passwd
 pcre2-VERSION
-pcsclite-VERSION-lib
+profile
 publicsuffix-list-0-unstable-DATE
 readline-VERSION
-root-profile-env
 s2n-tls-VERSION
-shadow-VERSION
-source
+service-dir
 sqlite-VERSION
-systemd-minimal-libs-VERSION
-tbb-DATE.VERSION
-tbb-DATE.VERSION-dev
-tcb-VERSION
-util-linux-minimal-VERSION-bin
-util-linux-minimal-VERSION-lib
-util-linux-minimal-VERSION-login
-util-linux-minimal-VERSION-mount
-util-linux-minimal-VERSION-swap
-wget-VERSION
-which-VERSION
+tzdata-DATE
 xgcc-VERSION-libgcc
 xz-VERSION
-zlib-ng-VERSION
 zlib-VERSION
 zstd-VERSION
-zstd-VERSION-bin
Enter fullscreen mode Exit fullscreen mode

In addition to the removal of systemd (systemd-minimal-libs),
in NixNG, many other packages have also been omitted.

Final thoughts

To be blunt, the default advice for minimizing container size is
often: "Use Alpine or Distroless!"

However, if committed to using Nix in a project and want to
save a bit on cloud storage or bandwidth costs, NixNG should be
a viable option.


Translated from the original post
at https://m15a.dev/ja/posts/nixng-image-size/.

Top comments (0)