Deferred Apps - Only download packages when you first use them

Note: Can’t post to announcements so I posted it here. I hope that is okay

I’ve been using NixOS for a while and kept running into the same pattern: I want certain applications available in my launcher (Discord, Insomnia etc.), but I rarely use some of them. Installing them all means a larger closure, longer rebuilds, and more disk space for apps that might sit unused for weeks.

So I either had to open a terminal, run nix-shell -p <xyz> and hope I remembered the package name correctly or live with having them permanently installed.

Being annoyed by this I did the only sane thing (not really). I built “Deferred Apps” - a NixOS and Home Manager module that creates lightweight wrapper scripts (~1KB each) with proper .desktop files. The apps appear in your launcher immediately (with icons), but the actual packages only gets downloaded when you first click them (via nix shell).

GitHub: GitHub - WitteShadovv/deferred-apps

What it does

  • Creates tiny (~1KB) wrapper scripts with proper desktop entries
  • Auto-detects executable names from nixpkgs metadata (so obs-studio correctly runs obs)
  • Resolves icons from Papirus theme at build time
  • Supports both free packages (pure mode) and unfree packages (impure mode with explicit opt-in)
  • Works with both NixOS and Home Manager
  • Supports nested packages like jetbrains.pycharm

Basic usage

{
  inputs.deferred-apps.url = "github:WitteShadovv/deferred-apps/v0.2.0";
  outputs = { nixpkgs, deferred-apps, ... }: {
    nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
      modules = [
        deferred-apps.nixosModules.default
        {
          programs.deferredApps = {
            enable = true;
            apps = [ "spotify" "discord" "obs-studio" "blender" "gimp" ];
            allowUnfree = true;  # Required for spotify, discord
          };
        }
      ];
    };
  };
}

Tradeoffs to be aware of

  • The first one is (hopefully) kinda obvious: First launch requires network. The package downloads on first use - no offline fallback unless it’s already cached in the Nix store.
  • Unfree packages require --impure. This is a Nix limitation and there is no good way around this afaik (let me know if there is). Free packages stay pure.
  • GC can remove cached packages. By default, running nix-collect-garbage will require re-downloading on next launch. You can enable gcRoot = true to prevent this, but then you need manual cleanup.

How it works
At build time, the module creates a derivation containing:

  • A wrapper script in /libexec that calls nix shell <flakeRef>#<package> --command <exe>
  • A .desktop file pointing to that wrapper
  • An optional terminal command symlink in /bin

The wrapper shows a notification on first launch, runs nix build to download and create a GC root (if enabled), then execs the actual application.

I’d appreciate any feedback, especially around:

  • Edge cases I might have missed
  • Better approaches to the unfree package handling
  • Ideas for improving the security model

PRs and ideas for improvements are welcome.

Cheers

4 Likes

This is really cool, thank you for sharing!

If you know about our command-not-found hook which looks up in which package the requested command is, I think I once saw an extension which would instead run it via a nix-shell. But yeah, I’m sure it was simple, so at this point where you are it probably won’t be a useful source of inspiration.

1 Like

You probably know about GitHub - nix-community/comma: Comma runs software without installing it. [maintainers=@Artturin,@burke,@DavHau] ?

1 Like

Comma is really slow, you pay an eval and database lookup cost on each comma call.

Maybe a better comparison would be Lazy Apps – On demand realization or Build derivation on first run? Those don’t seem to use nix shell hence would theoretically be faster.

2 Likes

Yes! I would argue there are different use cases though as comma is for cli. Deferred Apps is for desktop launcher integration so if you want apps to appear in your GNOME/KDE menu with proper icons.

I would argue comma is leaner for cli as you don’t have to add anything besides comma itself to your config but it’s worse for GUI apps.

You could use both: comma for cli, deferred-apps for GUI apps you want in your launcher without adding too much bloat.

1 Like

I didn’t know about Lazy Apps, seems like a cool project!

Using a flake ref for this feels unnecessarily restrictive. Instead you can do this all in pure nix and then it works for any derivation; not just flakes. This is a snippet I’ve been using:

let
  pkgs = import ./. { };
  lib = pkgs.lib;
  deferred =
    drv: name:
    pkgs.writeShellApplication {
      name = drv.name;
      runtimeInputs = [ pkgs.nix ];
      text =
        let
          exe = builtins.unsafeDiscardStringContext (lib.getExe' drv name);
        in
        ''
          nix-store --realise "${exe}" && exec "${exe}" "$@"
        '';
    };
in
deferred pkgs.hello "hello"

6 Likes

Thanks for the feedback! You’re right that the flake ref approach is more restrictive than necessary for the core mechanism.

I’m actually already working on adding direct derivation support (passing pkgs.foo instead of just “foo”).

That said, deferred-apps is intentionally more than just the wrapper script as I personally want it to handle desktop entry generation and build-time icon resolution.

3 Likes