Minimizing NixOS images

Minimizing NixOS images
πŸ“† Tue Oct 21 2025 von Jacek Galowicz
(15 Min. Lesezeit)

This is the second part of our four-part article series in which we have a closer look at how easy it is to create GNU/Linux systems with NixOS that are:

In the different parts of the article series, we handle the following topics step by step:

  1. NixOS appliance images with systemd-repart
  2. Minimizing NixOS images (πŸ‘ˆ this article)
  3. Immutable A/B system partitions with NixOS for over-the-air updates (TBD)
  4. Cross-compiling the image for other platforms (TBD)

Minimizing NixOS system images

This part of our series is about shrinking the size of our simple NixOS appliance example image from 1.5 GB to around 360MB:

The effect of our size reduction steps in config/size-reduction.nix on our system image size

Let’s first make the system image a bit larger and then learn multiple tricks that help us shrink the image.

Adding a minimal desktop

The system we came up with in part 1 was already relatively small, so let’s blow it up a little by adding a slick desktop with the JWM window manager:

Our minimal NixOS appliance system with a minimal desktop

The configuration module of this desktop setup (see here) is not too important in this article, as we are going to concentrate on how to make it smaller. All changes that we are going to do refer to the image state from last time in this repository: https://github.com/applicative-systems/simple-systemd-repart-nixos-image/tree/part2 You should be able to follow all the steps in this article from there if you like.

Let’s see how large it is right now:

$ nix build .#image
$ du -sh result/image.raw 
1.5G	result/image.raw

This is, at first glance, a bit surprising, because X and JWM together are never this large. To find out what really drives up the image size, the number alone doesn’t help us much.

Trick nr 1: nix-store --query

The nix-store --query command (link to documentation) is handy to find out which dependencies a Nix derivation really has. Let’s learn how to use it briefly.

As a first step, we build the toplevel derivation of our system configuration, which is, compared to the image attribute, the uncompressed system derivation that goes into the image. The output of the nix build --print-out-paths command is the resulting output path in the Nix store. We can wrap this further into a subshell $(...) to reuse the output path in the command nix-store --query --requisites, which lists us all the (transitive) dependencies as a flat list:

$ nix build --print-out-paths .#nixosConfigurations.appliance.config.system.build.toplevel
/nix/store/l9vcxgvmyrc8vg0g08pz8qimrphr4yyg-nixos-system-nixos-25.11.20251019.5e2a59a

$ nix-store --query --requisites $(nix build --print-out-paths .#nixosConfigurations.appliance.config.system.build.toplevel)
/nix/store/0ja5daq2yay3svg6f48spyfby7pgy5bm-xgcc-14.3.0-libgcc
/nix/store/7ndy5820h69n1pgd13viph1qjq6zhhkj-libunistring-1.3
/nix/store/wwqyxsvmg41q5ckfvvv6wa50nwvz9dv4-libidn2-2.3.8
/nix/store/qhw0sp183mqd04x5jp75981kwya64npv-glibc-2.40-66
/nix/store/2mwbf3viqfqd3lr946slqby9y4k7d9lw-attr-2.5.2
/nix/store/8b9srwwmrwmh1yl613cwwj7gydl87br6-gcc-14.3.0-libgcc
# ...

This list is complete but not very clear. With --tree instead of --requisites, we would get a nice ASCII tree. In this case, however, we would like to know which of these paths is how large to see better what we might want to remove.

Piping the output of nix-store through multiple other commands will help us further:

$ nix-store --query --requisites \
  $(nix build --print-out-paths .#nixosConfigurations.appliance.config.system.build.toplevel) \
  | xargs du -sm | sort -n | tail -n 10
43	/nix/store/cwyd97h7wf5sprgvpg44j6rjws1bbjkm-systemd-257.9
44	/nix/store/m9disdwacfbmfpl9x6v7h7011rbahz7f-gtk+3-3.24.49
50	/nix/store/iapbh1n1796kfhf0xma5axzc1s7q9an7-ghostscript-with-X-10.06.0
61	/nix/store/7mby64683vhyp6p0d0zza6gp9grzxa5i-flite-2.2-lib
63	/nix/store/k2y9xpcdqxma66kag80nhbv0pq2bznzf-perl-5.40.0
114	/nix/store/62fdlzq1x1ak2lsxp4ij7ip5k9nia3hc-python3-3.13.7
144	/nix/store/s7harqyx0qh3l56zcnhxzn21fnby69i6-linux-6.12.53-modules
229	/nix/store/wv2kaj5gq1h2wq9n4bhda4cinw9nzy78-mesa-25.2.5
541	/nix/store/9a6nrvm1w4f21zbnb5gxnafgygh5sp2s-llvm-21.1.1-lib
648	/nix/store/y0n2rsgi3lfdsgvh2rdkglnb2c07a4gs-source

This is what the pipe stages do:

So in the last line of the output, we can see that there is a 648 MB folder that the system depends on. Without even knowing what this is, why do we depend on that?

Trick nr 2: nix why-depends

The next useful app that we learn how to use now is nix why-depends (link to documentation). This command accepts two Nix store paths and prints how they depend on each other:

$ nix why-depends \
  $(nix build --print-out-paths .#nixosConfigurations.appliance.config.system.build.toplevel) \
  /nix/store/y0n2rsgi3lfdsgvh2rdkglnb2c07a4gs-source
/nix/store/l9vcxgvmyrc8vg0g08pz8qimrphr4yyg-nixos-system-nixos-25.11.20251019.5e2a59a
└───/nix/store/izd9z9v05fyqr2iyyxkfpza70midavva-system-path
    └───/nix/store/1slsf969zw6r9h6qkydzl9yjrj3g4i12-speech-dispatcher-0.12.1
        └───/nix/store/0xiz0p480nw10kij351m3xv1016g0s6n-mbrola-3.3-unstable-2024-01-29
            └───/nix/store/y0n2rsgi3lfdsgvh2rdkglnb2c07a4gs-source

As we can see, the package mbrola depends on the source folder, which is a dependency of speech-dispatcher. I don’t remember having speech tools activated in the system, but it might have been added by some other option implicitly. Let’s find out by grepping for speech-dispatcher in the nixos/modules folder of the nixpkgs repository:

$ cd ~/src/nixpkgs
$ cd nixos/modules
$ git grep speech-dispatcher
services/accessibility/speechd.nix:    enable = mkEnableOption "speech-dispatcher speech synthesizer daemon";
...

A quick look into this NixOS module shows that the option services.speechd.enable pulls this dependency in.

Trick nr 3: definitionsWithLocations in the REPL

So is this attribute really set to true, and if yes, where?

We can inspect every attribute of our NixOS configuration in the REPL by opening the flake with :lf . and then scoping into the interesting attributes inside the system configuration object in the flake:

$ nix repl
nix-repl> :lf .
nix-repl> nixosConfigurations.appliance.config.services.speechd.enable
true

This tells us that the attribute is set, but not where. The NixOS module library has something for us in this case: By scoping not into the .config subattribute set of our config but the .options set, each attribute path gets additional information:

nix-repl> :p nixosConfigurations.appliance.options.services.speechd.enable.definitionsWithLocations
[
  {
    file = "/nix/store/7agp54mgffm9m1wc1kgmkm37pvy18qhf-source/nixos/modules/services/misc/graphical-desktop.nix";
    value = true;
  }
]

definitionsWithLocations is a list of file paths of the NixOS modules that change the value of this particular attribute. Here, we can see that the graphical-desktop.nix module sets the attribute to true but uses lib.mkDefault, so we can simply disable it in another module.

Let’s create a new NixOS module in our repository and disable it from there:

# file: config/size-reduction.nix
{
  services.speechd.enable = false;
}

A quick rebuild of our image shows that we just shaved off nearly 700 MB of image size!

$ du -sh $(nix build --print-out-paths .#image)/image.raw
809M	/nix/store/jamgmzhdpw5ngq4bvswshwvkpa9h2av8-image/image.raw

When we look again at the output of the last nix-store --query call, we see that there are also roughly 500MB of llvm in our image. Let’s see how we depend on that:

$ nix why-depends \
  $(nix build --print-out-paths .#nixosConfigurations.appliance.config.system.build.toplevel) \
  /nix/store/9a6nrvm1w4f21zbnb5gxnafgygh5sp2s-llvm-21.1.1-lib
/nix/store/m2imxi8h26ragsm4hbx2306qwqk36kxf-nixos-system-nixos-25.11.20251019.5e2a59a
└───/nix/store/v0blfaffq7dy1ns1kw30maij7rghjs2h-etc
    └───/nix/store/0gp173081ixsg8257vfy8gbydncqdbdn-tmpfiles.d
        └───/nix/store/sifclniwajf131w7125m4pib6b3mvmbx-graphics-driver.conf
            └───/nix/store/ppa88s51r5gmx0ma7vjawd03yr5m6nbw-graphics-drivers
                └───/nix/store/wv2kaj5gq1h2wq9n4bhda4cinw9nzy78-mesa-25.2.5
                    └───/nix/store/9a6nrvm1w4f21zbnb5gxnafgygh5sp2s-llvm-21.1.1-lib

It seems like graphics drivers pull llvm in. Another quick glance into the NixOS modules collection shows where this comes from:

~/src/nixpkgs/nixos/modules $ git grep "graphics-drivers"
hardware/graphics.nix:    name = "graphics-drivers";

In this module file, we see what option this is. Let’s say we don’t need graphics acceleration in our appliance, so we disable it:

# file: config/size-reduction.nix
{
  services.speechd.enable = false;
  hardware.graphics.enable = false;
}

The image size now shrinks by another ~200 MB! (200 MB is less than the 500 MB size of the llvm package size in the nix-store --query output, but that was of the uncompressed image.)

$ du -sh $(nix build --print-out-paths .#image)/image.raw
605M	/nix/store/lsnwnv13g3kfb9p6s1d0qnwcaladsliz-image/image.raw

What next?

$ nix-store --query --requisites $(nix build --print-out-paths .#nixosConfigurations.appliance.config.system.build.toplevel) | xargs du -sm | sort -n | tail -n 5
43	/nix/store/cwyd97h7wf5sprgvpg44j6rjws1bbjkm-systemd-257.9
50	/nix/store/iapbh1n1796kfhf0xma5axzc1s7q9an7-ghostscript-with-X-10.06.0
63	/nix/store/k2y9xpcdqxma66kag80nhbv0pq2bznzf-perl-5.40.0
114	/nix/store/62fdlzq1x1ak2lsxp4ij7ip5k9nia3hc-python3-3.13.7
144	/nix/store/s7harqyx0qh3l56zcnhxzn21fnby69i6-linux-6.12.53-modules

Let’s also say we don’t need Python on the image. Why do we depend on it?

$ nix why-depends $(nix build --print-out-paths .#nixosConfigurations.appliance.config.system.build.toplevel) /nix/store/62fdlzq1x1ak2lsxp4ij7ip5k9nia3hc-python3-3.13.7
/nix/store/a0k9bkkqy0jnx5xafsal078v8bis0lkg-nixos-system-nixos-25.11.20251019.5e2a59a
└───/nix/store/ysqpqx7vxwpskq27frnf68n3f868rsil-system-path
    └───/nix/store/4mvcirs8pkzha4x0lf95fv5pbqn9kjvk-iproute2-6.16.0
        └───/nix/store/62fdlzq1x1ak2lsxp4ij7ip5k9nia3hc-python3-3.13.7

The package iproute2 seems to depend on Python. The reason for this is that the package contains a Python script called routel for which Python was added in this patch. When I prepared the code for the NixCon 2025 lightning talk about systemd-repart and systemd-sysupdate, I discovered this dependency and made it optional in this upstream PR. At the time of writing this article, the change is still not out of the staging process.

To use this patch now, we still have to add an override manually, but this is quick and easy. If you find overlays confusing, please have a look at our article about Nixpkgs overlays, or book our Nix & NixOS 101 training, where we explain this thoroughly with practical exercises!

# file: config/size-reduction.nix
{
  services.speechd.enable = false;
  hardware.graphics.enable = false;

  nixpkgs.overlays = [
    (final: prev: {
      iproute2 = prev.iproute2.overrideAttrs (old: {
        outputs = old.outputs ++ [ "scripts" ];
        postInstall = old.postInstall or "" + ''
          moveToOutput sbin/routel "$scripts"
        '';
      });
    })
  ];
}

Adding this patch triggers a longer rebuild of a few components. Afterward, we see that the image size has not changed a bit:

$ du -sh $(nix build --print-out-paths .#image)/image.raw
605M	/nix/store/rks27sybrh6lnf0ba637x3921pzk50wl-image/image.raw

Why is that? Let’s have a look at whether the Python dependency is still there, and if yes, why:

$ nix-store --query --requisites $(nix build --print-out-paths .#nixosConfigurations.appliance.config.system.build.toplevel) \
  | xargs du -sm | sort -n | tail -n 5
43	/nix/store/c2f1xjfhzkibcgq21qn2jg17dfwvlfiy-systemd-257.9
50	/nix/store/6h5almf9n8my5jrajvjpczcxs288mzp1-ghostscript-with-X-10.06.0
63	/nix/store/k2y9xpcdqxma66kag80nhbv0pq2bznzf-perl-5.40.0
114	/nix/store/62fdlzq1x1ak2lsxp4ij7ip5k9nia3hc-python3-3.13.7
144	/nix/store/s7harqyx0qh3l56zcnhxzn21fnby69i6-linux-6.12.53-modules

$ nix why-depends $(nix build --print-out-paths .#nixosConfigurations.appliance.config.system.build.toplevel) /nix/store/62fdlzq1x1ak2lsxp4ij7ip5k9nia3hc-python3-3.13.7
/nix/store/ihd470driwfazxwvnmb7l275527bwknb-nixos-system-nixos-25.11.20251019.5e2a59a
└───/nix/store/6g6lks2da5sipbwnh2ckcdv34mc06ggg-etc
    └───/nix/store/49nly5iiyna0g8gq0nwc685m1js4rbyd-pipewire-1.4.8
        └───/nix/store/gyibnnr6drkljnfl6dksdjmv1gmkzljc-gstreamer-1.26.5
            └───/nix/store/62fdlzq1x1ak2lsxp4ij7ip5k9nia3hc-python3-3.13.7

Now we can see that we still depend on python3 via gstreamer via pipewire. (nix why-depends also provides the -a flag that lists all dependency chains in one combined view)

We can disable pipewire via services.pipewire.enable:

# file: config/size-reduction.nix
{
  services.speechd.enable = false;
  hardware.graphics.enable = false;

  nixpkgs.overlays = [
    (final: prev: {
      iproute2 = prev.iproute2.overrideAttrs (old: {
        outputs = old.outputs ++ [ "scripts" ];
        postInstall = old.postInstall or "" + ''
          moveToOutput sbin/routel "$scripts"
        '';
      });
    })
  ];

  services.pipewire.enable = false;
  services.libinput.enable = false;
}

In the latest NixOS module snippet, we can also see that I already disabled services.libinput.enable, which would be the next thing that depends on Python.

With all the changes we applied, the image is finally smaller:

$ du -sh $(nix build --print-out-paths .#image)/image.raw
464M	/nix/store/06lyjrhh4ls3qypfm27a38gkqwgif9pf-image/image.raw

And now, what’s next?

$ nix-store --query --requisites $(nix build --print-out-paths .#nixosConfigurations.appliance.config.system.build.toplevel) \
  | xargs du -sm | sort -n | tail -n 5
31	/nix/store/qhw0sp183mqd04x5jp75981kwya64npv-glibc-2.40-66
43	/nix/store/c2f1xjfhzkibcgq21qn2jg17dfwvlfiy-systemd-257.9
50	/nix/store/6h5almf9n8my5jrajvjpczcxs288mzp1-ghostscript-with-X-10.06.0
63	/nix/store/k2y9xpcdqxma66kag80nhbv0pq2bznzf-perl-5.40.0
144	/nix/store/s7harqyx0qh3l56zcnhxzn21fnby69i6-linux-6.12.53-modules

Let’s remove Perl. This is a bit more complicated because NixOS historically contained a lot of Perl. There was, however, an amazing initiative by employees of the company secunet et al to enable for perlless NixOS systems that was also sponsored by the Sovereign Tech Fund and guided by the NixOS foundation. The reason for this was initially security, as one can argue systems without interpreters provide less surface for security incidents. In our case, it is also useful because it results in a smaller system.

The perlless.nix NixOS profile can now be included in NixOS configurations. It reconfigures everything that initially used Perl in NixOS. On top of that, it adds a check that compares all Nix store paths that go into the system configuration against the substring β€œperl” and if any of them match, this becomes an error. This way, we can enforce that certain packages do not become part of our system image.

Let’s include it like this in our size-reduction.nix module:

# file: size-reduction.nix
{ lib, pkgs, modulesPath, ... }:
{
  imports = [
    (modulesPath + "/profiles/perlless.nix")
  ];

# ...

Unfortunately, we get errors while rebuilding the image which come from the Nix store paths check:

...
nixos-system-nixos> System closure ... contains the following disallowed paths:
nixos-system-nixos> /nix/store/25aba0kkl0mgnf6nfiidlx0xman99nmr-perl5.40.0-XML-Parser-2.46
...

This means that although Perl has been mostly removed by the perlless.nix profile, something else in our NixOS config pulls Perl in. To find out where it comes from, let’s temporarily silence the error with:

# file: config/size-reduction.nix
...
  system.forbiddenDependenciesRegexes = lib.mkForce [];
...

The image now builds again, and we can have a look at the nix why-depends output:

$ nix why-depends $(nix build .#nixosConfigurations.appliance.config.system.build.toplevel --print-out-paths) /nix/store/k2y9xpcdqxma66kag80nhbv0pq2bznzf-perl-5.40.0
/nix/store/qdn9nzsrgg1z6lasmzbairsqvrs23q18-nixos-system-nixos-25.11.20251019.5e2a59a
└───/nix/store/7ivirgy36j236yprr9akq4dg30f4hjl2-system-path
    └───/nix/store/4y6rpi6hsmnm120xbp3f5xmwa0vgqcqp-xdg-utils-1.2.1
        └───/nix/store/k2y9xpcdqxma66kag80nhbv0pq2bznzf-perl-5.40.0

As we can see, we still depend on Perl because of the package xdg-utils. While studying the relevant NixOS module that pulls in xdg-utils, I realized that there is no easy way to avoid this dependency without losing other settings.

The approach might be quite crude and hacky, but we can simply substitute the package with another one that we already have. If the image still works with this change, it might be β€œgood enough”.

Let’s have a look at how our config/size-reduction.nix module looks like right now:

# file: config/size-reduction.nix
{ lib, pkgs, modulesPath, ...  }:
{
  imports = [
    (modulesPath + "/profiles/perlless.nix")
  ];

  # This line is just for debugging the closure. Remove later.
  system.forbiddenDependenciesRegexes = lib.mkForce [];

  services.speechd.enable = false;
  hardware.graphics.enable = false;
  services.pipewire.enable = false;
  services.libinput.enable = false;

  nixpkgs.overlays = [
    (final: prev: {
      iproute2 = prev.iproute2.overrideAttrs (old: {
        outputs = old.outputs ++ [ "scripts" ];
        postInstall = old.postInstall or "" + ''
          moveToOutput sbin/routel "$scripts"
        '';
      });

      xdg-utils = final.bash; # fake xdg-utils that doesn't pull in Perl etc.
    })
  ];
}

The last entry in the overlay function simply swaps out xdg-utils with bash.

After rebuilding the image (and this time without the disabled system.forbiddenDependenciesRegexes attribute), we can see that the image finally got smaller again:

$ du -sh $(nix -L build .#image --print-out-paths)/image.raw
445M    /nix/store/dw14wdwh788bh6skla7icg33qlac5pna-image/image.raw

What else shall we remove?

$ nix-store --query --requisites $(nix build .#nixosConfigurations.appliance.config.system.build.toplevel --print-out-paths) \
  | xargs du -sm | sort -n | tail -n 5
25      /nix/store/z5879apljwncxk6zhxn1jzqxjwvcfqdx-initrd-linux-6.12.53
31      /nix/store/qhw0sp183mqd04x5jp75981kwya64npv-glibc-2.40-66
43      /nix/store/c2f1xjfhzkibcgq21qn2jg17dfwvlfiy-systemd-257.9
50      /nix/store/6h5almf9n8my5jrajvjpczcxs288mzp1-ghostscript-with-X-10.06.0
144     /nix/store/s7harqyx0qh3l56zcnhxzn21fnby69i6-linux-6.12.53-modules

$ nix why-depends $(nix build .#nixosConfigurations.appliance.config.system.build.toplevel --print-out-paths) /nix/store/6h5almf9n8my5jrajvjpczcxs288mzp1-ghostscript-with-X-10.06.0
/nix/store/fgzdif74337l2kzmzxxv74gswq3wl74g-nixos-system-nixos-25.11.20251019.5e2a59a
└───/nix/store/nda23x95yqgddjpfh5jy2ic0s7gbvbd0-etc-metadata.erofs
    └───/nix/store/2p25dgmwsjx8zzrgws0xb8w3sx37j39v-set-environment
        └───/nix/store/95v1w7bizryzvjzqndbyk42x6sw5k9r3-desktops
            └───/nix/store/bk93a8skvjs8i1r78bf7si5i6qppf261-none+jwm-xsession
                └───/nix/store/b0fxbxxd6m6yd27b6wjwwaqfgmnk857i-xsession
                    └───/nix/store/hwr8yd5i7d8y8vx7d1v2j93n7v5ijs1i-feh-3.11.2
                        └───/nix/store/w7kdl0i6mq7b5gkcj79myarj3y4f18pb-imlib2-1.12.5
                            └───/nix/store/547nm0x7sks8p9gn1nwcclvx9wqnycxw-libspectre-0.2.12
                                └───/nix/store/6h5almf9n8my5jrajvjpczcxs288mzp1-ghostscript-with-X-10.06.0

Apparently, we depend on ghostscript-with-X because our xsession config pulls in the package feh via this module line. A quick study of this module reveals that the easiest way to get rid of the package and its dependencies is again a quick overlay entry like feh = final.bash; in our size-reduction.nix module.

This relieves another 50 MB from our image size:

$ du -sh $(nix -L build .#image --print-out-paths)/image.raw
395M    /nix/store/l7ga4qp8f86zhvfmpp2kc18czaqh23ss-image/image.raw

What else now? A glance over the full list of dependencies reveals that we depend on multiple font files:

$ nix-store --query --requisites $(nix build .#nixosConfigurations.appliance.config.system.build.toplevel --print-out-paths) \
  | xargs du -sm | sort -n | grep fonts
1       /nix/store/kparrvs35yik7cxkqakiipclk89n0m7k-fc-52-nixos-default-fonts.conf
1       /nix/store/q1y9dk7c1kcz4dgphj0swmxfvn4rr174-dejavu-fonts-minimal-2.37
5       /nix/store/p8p7d197nd4piydldjjjy0rpnarylngs-liberation-fonts-2.1.5
5       /nix/store/xjlixfqkknasxn8932v68pj8gz283kms-gyre-fonts-2.005
10      /nix/store/g4bd1makq0dcz2scpy0gbva5m3m2wiv9-dejavu-fonts-2.37
11      /nix/store/vff10wqvd98mslb868agi43sy4q0cpa9-noto-fonts-color-emoji-2.051

$ nix why-depends $(nix build .#nixosConfigurations.appliance.config.system.build.toplevel --print-out-paths) /nix/store/vff10wqvd98mslb868agi43sy4q0cpa9-noto-fonts-color-emoji-2.051
/nix/store/0y8pl6saddcslb7ziywn361mc2498cym-nixos-system-nixos-25.11.20251019.5e2a59a
└───/nix/store/f8gccbfpcfpc9js3p8zfyxllap7jvabx-etc-metadata.erofs
    └───/nix/store/plkc6mic2srxfv569maiykw445n6p93n-fontconfig-etc
        └───/nix/store/3f9ivginglbha34sj4f6s03k6a1jd2nr-fontconfig-conf
            └───/nix/store/4vdcd3vgb2757njcfnc597sr860qispj-fc-00-nixos-cache.conf
                └───/nix/store/vff10wqvd98mslb868agi43sy4q0cpa9-noto-fonts-color-emoji-2.051

These are apparently pulled in via fontconfig. When running git grep fontconfig-etc in the nixpkgs repository, we quickly find config/fonts/fontconfig.nix, which provides us with the following attribute paths to drop most fonts:

  fonts.enableDefaultPackages = false;
  fonts.fontconfig.enable = false;
  fonts.packages = lib.mkForce [ pkgs.dejavu_fonts ];

At this point, I just wrote down what I thought would reduce the closure size, not knowing if this would break the system. However, everything is set up in a way that we can simply run and test it via nix run.

This change results in another drop from 395 MB to 362 MB, and it turns out that the system still works very well:

$ du -sh $(nix -L build .#image --print-out-paths)/image.raw
362M    /nix/store/a9rcwc5h5x6m73978hd7kf5lxghj8ar4-image/image.raw

I have seen that we can drop another 3 MB by disabling sudo and the firewall, but this might not be worth it.

We could get more juice out of the image and get it below 300MB by recompiling the kernel with a config that disables as many modules as possible or comes without modules at all. But as this is very hardware and application-specific, this might be an exercise for some other day.

On top of that, it would in general be possible to make systems even smaller, but not without the tradeoff of disabling parts of the default modules of the NixOS upstream distribution and rewriting them by hand to our customized needs. This is, of course, always possible and sometimes the right choice, depending on your product use cases and requirements.

Summary and outlook

Let’s have another look at our size reduction steps:

The effect of our size reduction steps in config/size-reduction.nix on our system image size

The tools nix-store --query and nix why-depends provide great help in finding out what the biggest space consumers are in our image. This way, we were able to systematically remove everything that we found unnecessary.

The actual removal requires to be able to read and understand NixOS modules and nixpkgs overlays. Both are complicated abstractions (often too intricate for copy&paste and LLM-based efforts) but also immensely powerful. Learning these abstractions is a small investment with a huge payoff - I personally learned them more than 8 years ago and keep doing things easily and quickly that would be much more difficult in legacy Linux distros. Back then, it took me quite some time. Our training customers, of course, learn all these heavy tricks in one week of training, along with examples, guided explanations, and practical exercises. The result is that they find these tools boring and simply use them to drive their projects every day.

The final repository is here on GitHub: https://github.com/applicative-systems/simple-systemd-repart-nixos-image/tree/part2

In the next part of this series, we are going to build a system with immutable A/B partitions that can update itself over the air (OTA) using systemd-sysupdate. Stay tuned!

We help many customers transfer from other Linux-based solutions to NixOS or improve their existing NixOS-based solutions. From that experience, we can help you copy the successful patterns of winning organizations and avoid the patterns that have not worked well elsewhere, instead of having to make this experience yourself from scratch. No matter if you just need a quick consultation on how to build something with Nix or if we can help you by lending developer time, schedule a quick call with us or e-mail us.

Jacek Galowicz

About Jacek Galowicz

Jacek is the founder of the Nixcademy and interested in functional programming, controlling complexity, and spread Nix and NixOS all over the world. He also wrote a book about C++ and gave university lectures about software quality.

Nixcademy on Twitter/X

Follow us on X

Nixcademy on LinkedIn

Follow us on LinkedIn

Get Online Nix(OS) Classes

Nixcademy online classes

Are you looking for...

  • a guided learning experience?
  • motivating group exercises?
  • a personal certificate?
Nixcademy Certificate

The Nixcademy Newsletter: Receive Monthly Nix(OS) Blog Digests

Nixcademy Newsletter

Receive a monthly digest of blog articles and podcasts with useful summaries to stay connected with the world of Nix and NixOS!

Stay updated on our latest classes, services, and exclusive discounts for newsletter subscribers!

Subscribe Now