Creating a systemd service that runs a nix command?

Heya! I am still quite a newbie to nix. I want to run my server app as a systemd service, and want to leverage it’s nix flake to run.

I’ve set up the following

systemd.services.myservice = {
  enable = true;
  ...
  serviceConfig = {
     ExecStart = "nix run git+https://mygitrepo/server";
   }
}

However, systemctl doesn’t know where nix is located. How can I reference nix within my configuration.nix?

Also if my approach is completely off, I’m curious if there is another way.

You need either:

systemd.services.myservice = {
  enable = true;
  ...
  path = [ pkgs.nix ];
  serviceConfig = {
     ExecStart = "nix run git+https://mygitrepo/server";
   }
}

or:

systemd.services.myservice = {
  enable = true;
  ...
  serviceConfig = {
     ExecStart = "${pkgs.nix}/bin/nix run git+https://mygitrepo/server";
   }
}

This is true for any package used in systemd units, as they do not inherit the system $PATH.

1 Like

Well, to answer the question, you need to give it the full path to nix with something like ExecStart = "${pkgs.nix}/bin/nix ......"; But this probably isn’t how you want to do this.

You should most likely be taking that other flake as an input to your own flake and referring to its outputs directly, instead of using nix run in a the service. i.e. In flake.nix you can have

inputs.mygitrepo.url = "git+https://mygitrepo/server";

and then you can use something like ExecStart = "${mygitrepo.apps.${pkgs.system}.program}"; where mygitrepo is the argument passed to your flake’s outputs function. Adjust for whatever that other repo’s outputs actually are.

3 Likes

Even though you received answers to your question, please let me also say, that using nix run in the service is not the way you should do this.

  1. It introduces a dependency on the network being online
  2. It updates the service at random
  3. Starting the service might get delayed for necessary builds

To mitigate those problems the proper way was to add the remote flake as an input to your flake and then use their packages in the service to start their program.

Ok, that seems like a more sensible approach.

To follow up, I can’t quite figure out where my package is located.

This is the output of my flake

      in {
        legacyPackages = scope';

        packages.default = main;

        devShells.default = pkgs.mkShell {
          inputsFrom = [ main ];
          buildInputs = devPackages ++ [
            # You can add packages from nixpkgs here
            pkgs."postgresql"
            pkgs."go-migrate"
          ];
        };

My flake is based on this, so it’s doing some magic.

And I suspect that the flake executable should be in packages. However, running nix flake show, I get

├───devShells
│   ├───aarch64-darwin
│   │   └───default: development environment 'nix-shell'
│   ├───aarch64-linux
│   │   └───default: development environment 'nix-shell'
│   ├───x86_64-darwin
│   │   └───default: development environment 'nix-shell'
│   └───x86_64-linux
│       └───default: development environment 'nix-shell'
├───legacyPackages
│   ├───aarch64-darwin omitted (use '--legacy' to show)
│   ├───aarch64-linux omitted (use '--legacy' to show)
│   ├───x86_64-darwin omitted (use '--legacy' to show)
│   └───x86_64-linux omitted (use '--legacy' to show)
└───packages
    ├───aarch64-darwin
error: cannot build '/nix/store/jhzwkza62p2l1qhq2mgbp1n1014vawyn-opam.json.drv' during evaluation because the option 'allow-import-from-derivation' is disabled

Not really sure how to figure it out from there. I just know that nix run works.

I tried to put default, I.e ExecStart = "myPkg.packages.${pkgs.default}.default but that didn’t work.

How about:

ExecStart = "${myPkg.packages.${pkgs.system}.default}/bin/<binary>";

If that doesn’t do it, what error do you get? If it’s the IFD one you’d either need to significantly rewrite the flake, *or" allow IFD - didn’t realize that was already made an error.

How about:

ExecStart = "${myPkg.packages.${pkgs.system}.default}/bin/<binary>";

If that doesn’t do it, what error do you get? If it’s the IFD one you’d either need to significantly rewrite the flake, *or" allow IFD - didn’t realize that was already made an error.

Ah of course, it should be referenced as a variable.

So I realized that I needed to put the entire systemd part into a flake,
and wrote the following

{
  inputs.myApp.url = "git+https://myAppUrl.com"

  outputs = {self,myApp, ...}: {
    systemd.services.myApp = {
      enable = true;
      description = "MyApp";
      unitConfig = {
        Type = "simple";
      };
      serviceConfig = {
        execStart = "${myApp.packages.x86_64-linux.default}/bin/main";
      };
      wantedBy = [ "multi-user.target" ];
     };
   };
}

I substituted pkgs.system as I didn’t know how to reference it. What is
the correct way to import it in a flake?

I imported the flake into my configuration.nix

{ pkgs, ... }: {
  imports = [
    ./flake.nix
    ...
  ];
  ...
};

The error I am getting now is

error: flake 'path:/etc/nixos' does not provide attribute
'packages.x86_64-linux.nixosConfigurations."nix".config.system.build.nixos-rebuild'
....

I’m thinking the flake setup is wrong for the systemd service.

You do not. You can create a nixosModule if you want to, but this is not necessary. Just put what you have there in your NixOS configuration and it will work fine.

@NobbZ blog post on the topic might help figure out how to get the package into your configuration.nix if that’s where you failed: Getting inputs to modules in a nix-flake | NobbZ' Blog


If you want to create a nixosModule, you’d put it in the same flake as the package, and you would do something like this:

# /projects/myApp/flake.nix
{
  outputs = {self, ...}: {
    packages.${system}.default = { <some package definition> };

    nixosModules.default =
      # For illustration, probably want to break this definition out to a separate file
      { config, pkgs, lib, ... }: {
        options = {
          services.myApp.enable = lib.mkEnableOption "myApp";
        };

        config = lib.mkIf config.services.myApp.enable {
          systemd.services.myApp = {
            # Insert systemd config here
            serviceConfig.ExecStart = "${self.packages.${pkgs.system}.default}/bin/main";
          };
        };
      };
  };
}

Then you can import that from your other flake:

# /etc/nixos/flake.nix
{
  inputs = {
    myApp.url = "git+https://myAppUrl.com";
  };

  outputs = { myApp, ... } @ inputs: {
    # You may want to use a name other than "nix" for your hostname, by the way
    nixosConfigurations.nix = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux"; # Probably
      specialArgs = {
        inherit inputs;
      };
      modules = [
        ./configuration.nix
      ];
    };
  };
}
# /etc/nixos/configuration.nix
{ inputs, ... }: {
  imports = [
    inputs.myApp.nixosModules.default
  ];

  services.myApp.enable = true;
}

This is probably overkill though, unless you want to define options for your service or something.

Thank you! That link was really helpful. With that, I’ve updated my flake.nix

{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs";
  inputs.myApp.url = "git+https://myapp.com"

  outputs = {selt, nixpkgs, myApp, ...}@inputs: {
    nixosSystem.nixos = nixpkgs.lib.nixosSystem = {
      specialArgs = {inherit inputs;};
      system = "x86_64-linux";
      modules = [
        ./configuration.nix 
      ];
    };
   };
}

And then my config

{myApp, ...} :
{ pkgs, ...}: {
  imports = [ ...]; #removed flake import
  systemd.services.myApp = { # Same as before
  ...
  };
  ...

}

Now the error is though

error: failed to extract archive (Can't create '/run/user/0/nix-xxxxx/NixOS-nixpkgs-xxxx/gkps/development/tools/wasmi/default.nix')

   ...while fetching the input 'github:NixOS/nixpkgs'
   ... 

I tried unstable too but that didn’t help.

Thank you for all the help up until now :).

Hum, what’s the full error? Are you out of disk space or something?

Full error is

[nix-shell:/etc/nixos]# nixos-rebuild switch --flake /etc/nixos --show-trace
error: failed to extract archive (Can't create '/run/user/0/nix-148004-0/NixOS-nixpkgs-b5680f5/pkgs/development/tools/wasmi/default.nix')

       … while fetching the input 'github:NixOS/nixpkgs'

       … while updating the flake input 'nixpkgs'

       … while updating the lock file of flake 'path:/etc/nixos?lastModified=1705593106&narHash=sha256-JK7oxdDQXLQaDzLBhPzejVoJPz9%2fyIW6Ogw1KBwC2oU='

Result of df

Filesystem     1K-blocks    Used Available Use% Mounted on
devtmpfs           99128       0     99128   0% /dev
tmpfs             991244       0    991244   0% /dev/shm
tmpfs             495624    4560    491064   1% /run
tmpfs             991244     384    990860   1% /run/wrappers
/dev/sda1       39052844 3773400  33642876  11% /
tmpfs             198248      12    198236   1% /run/user/0

/etc/nixos is owned by root, I assume, and you’re executing this as a normal user? If so, you can’t write to the flake.lock that’s automatically created and filled with entries for all your inputs if you don’t have one yet.

I’d recommend moving your config to a directory owned by your user, and running:

nixos-rebuild switch --flake ~/projects/system-config --use-remote-sudo

Argh, such a simple error. Yes, I was not root as I had ran nix-shell -p vim, I thought I would still remain in root after that.

Running it now, I get the error

error: flake 'path:/etc/nixos' does not provide attribute 'packages.x86_64-linux.nixosConfigurations."nix".config.system.build.nixos-rebuild', 'legacyPackages.x86_64-linux.nixosConfigurations."nix".config.system.build.nixos-rebuild' or 'nixosConfigurations."nix".config.system.build.nixos-rebuild'

Not sure if I’ve missed something after following the tutorial of the
blog post you linked?

Double check that you actually named your nixosConfigurations entry nix, and that everything is spelled correctly.

Double check that you actually named your nixosConfigurations entry nix, and that everything is spelled correctly.

I was able to get it working now!

I had to change my flake to

{
  description = "Run Ann Server as a systemd service";
  inputs.nixpkgs.url = "github:NixOS/nixpkgs?ref=nixpkgs-unstable";
  inputs.webApp.url = "git+githost.com/webApp";


  outputs = {self,nixpkgs, webApp, ...}@inputs: {
    nixosConfigurations.nix = nixpkgs.lib.nixosSystem {
      specialArgs = {inherit inputs;};
      system = "x86_64-linux";
      modules = [
        ./configuration.nix
      ];
    };
  };
}

Where nix is presumably my hostname.

Then to finally update my systemd config in configuration.nix

  systemd.services.ann = {
      enable = true;
      description = "MyApp";
      unitConfig = {
        Type = "simple";
      };
      serviceConfig = {
        ExecStart = "${inputs.myapp.packages.${pkgs.system}.default}/bin/myapp";
      };
      wantedBy = [ "multi-user.target" ];
  };

Follow up question: In order to now update my app, will I just need to
push my changes and then run nixos-rebuild switch?

Thanks so much everyone for the help!

Not quite, you will also need to run (in /etc/nixos, or wherever your system config flake lives):

nix flake lock --update-input myApp

or, to update all your inputs (including nixpkgs):

nix flake update

This will update the contents in flake.lock to point to the newest commits for the branches of each of your inputs, which will then be used by nixos-rebuild switch to build your configuration. If you don’t update flake.lock, nix will just build the same commit over and over again.

Updates are deliberately explicit, you don’t want stuff just changing from underneath your feet with a tool that advertises reproducibility.

1 Like