Using a custom derivation in NixOS flake

Hello, I have a Nix Flake that I use for configuring my NixOS laptop, MacBook and right now I’m writing a configuration that I plan to use on my VPS. I host my personal website/blog using Hugo and I made a derivation for it, that looks like this:

{ pkgs ? import <nixpkgs> {} }:
pkgs.stdenv.mkDerivation {
  name = "blog";
  src = pkgs.fetchFromGitHub {
    owner = "MatejaMaric";
    repo = "blog";
    rev = "bb7193cf1f1b2e1662150400164b0b07e646b4a5";
    hash = "sha256-MAOa7pHEIPijRulc0oz+KCd6dZ1WgShEmnrX1isuHBU=";
  };
  buildInputs = [ pkgs.hugo ];
  buildPhase = ''
    hugo
  '';
  installPhase = ''
    mkdir -p $out/var/www
    cp -r public $out/var/www/matejamaric.com
  '';
}

It’s my understanding that a derivation is basically a package that I can use. However I don’t know how to use it in my current server configuration that looks like this:

{ config, pkgs, ... }: {
  system.stateVersion = "23.11"; # Don't touch this
  imports = [
    # ./hardware-configuration.nix
  ];
  boot.loader.systemd-boot.enable = true;
  boot.loader.efi.canTouchEfiVariables = true;
  networking.hostName = "server";
  time.timeZone = "Europe/Frankfurt";
  i18n.defaultLocale = "en_US.UTF-8";
  nix.settings.experimental-features = ["nix-command" "flakes"];
  networking.firewall.enable = true;
  networking.firewall.allowedTCPPorts = [ 50000 80 443 25 587 993 ];
  virtualisation.vmVariant.virtualisation = {
    graphics = false;
    forwardPorts = [
      { from = "host"; host.port = 50000; guest.port = 50000; }
      { from = "host"; host.port = 8080; guest.port = 80; }
    ];
  };
  users = {
    users.mateja = {
      isNormalUser = true;
      description = "Mateja";
      extraGroups = [ "wheel" ];
      openssh.authorizedKeys.keys = [
        "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINMZcuz2OPjpUGNPIE3/7UUwUIVBztmx478LFUahQaMm mail@matejamaric.com"
      ];
      initialHashedPassword = "$y$j9T$WwsIfZwdnLm84jjd9Q9wV1$fhDEn.TrvZVSaV6VNKpH0RWiPKAvaOhR.3oCkDlETO7";
    };
  };
  environment.systemPackages = with pkgs; [
    git
    tmux
    php83
    php83Packages.composer
    php83Extensions.mbstring
    php83Extensions.xml
    php83Extensions.bcmath
    gitolite
    opensmtpd
    dovecot
    rspamd
    redis
    # (callPackage ./blog {})
  ];
  services.openssh = {
    enable = true;
    ports = [ 50000 ];
    settings.PasswordAuthentication = false;
    settings.KbdInteractiveAuthentication = false;
    settings.PermitRootLogin = "no";
  };
  programs.gnupg.agent = {
    enable = true;
    enableSSHSupport = true;
  };
  services.nginx.enable = true;
  services.nginx.virtualHosts = {
    "matejamaric.com" = {
      enableACME = true;
      addSSL = true; # forceSSL = true;
      root = "/var/www/matejamaric.com";
    };
    "mail.matejamaric.com" = {
      enableACME = true;
      addSSL = true; # forceSSL = true;
      root = "/var/www/mail.matejamaric.com";
    };
    # "git.matejamaric.com" = { ... };
    "yota.yu1srs.org.rs" = {
      enableACME = true;
      forceSSL = true;
      root = "/var/www/yota.yu1srs.org.rs";
      locations."~ \\.php$".extraConfig = ''
        fastcgi_pass  unix:${config.services.phpfpm.pools.yotapool.socket};
        fastcgi_index index.php;
      '';
    };
  };
  security.acme = {
    acceptTerms = true;
    defaults.email = "matejamaricz@gmail.com";
  };
  services.mysql = {
    enable = true;
    package = pkgs.mariadb;
  };
  services.phpfpm.pools.yotapool = {
    user = "nobody";
    settings = {
      "pm" = "dynamic";
      "listen.owner" = config.services.nginx.user;
      "pm.max_children" = 5;
      "pm.start_servers" = 2;
      "pm.min_spare_servers" = 1;
      "pm.max_spare_servers" = 3;
      "pm.max_requests" = 500;
    };
  };
}

My blog derivation and server configuration are all in the same repository (that I use for configuration). I don’t know if that’s okay or should I move the derivation to my blog repository? If I understood correctly, people often make a Flake that contains only the derivation and then they specify it only as input in their configuration Flake, what are the benefits of that?
I’m quite new to Nix and especially the more involved stuff, all answer/explanations are more then welcome! :smiley:

I don’t know if that’s okay or should I move the derivation to my blog repository?

Either option is correct, but I’d recommend placing it in the blog repo if you own it, and expose it in your flake’s packages section.

As far as the derivation goes, I’d suggest changing the first two lines as follows:

{ stdenv }:
stdenv.mkDerivation {

Using import <nixpkgs> {} anywhere is considered bad practice (pinning is better).

Also, instead of using src = pkgs.fetchFromGitHub, I’d recommend adding it as a flake input. It makes versioning easier, and you always know where to look.

I don’t know how to use it in my current server configuration

To build your derivation, you need to use pkgs.callPackage ./myDerivation.nix {} somewhere. In that NixOS configuration, it would look as follows:

{ config, pkgs, ... }: 

let
  blog = pkgs.callPackage path/to/blog {};
in
{
  system.stateVersion = "23.11"; # Don't touch this
  ...
}

You could also make it available directly in your pkgs instance via overlays (that’s what I’d usually do).

people often make a Flake that contains only the derivation and then they specify it only as input in their configuration Flake, what are the benefits of that?

I guess it depends on the use case. Exposing the derivation in your flake’s package as part of the same repository is usually what most people do.

Exposing a derivation on a specific repo makes sense if you want to share it with other flakes.

That’s my 2 cents, hope that helps!

3 Likes

Hey @gvolpe, thanks! That helped me a lot! :smiley:
I changed my derivation to this:

{ stdenv, fetchFromGitHub, hugo }:
stdenv.mkDerivation {
  name = "blog";
  src = fetchFromGitHub {
    owner = "MatejaMaric";
    repo = "blog";
    rev = "bb7193cf1f1b2e1662150400164b0b07e646b4a5";
    hash = "sha256-MAOa7pHEIPijRulc0oz+KCd6dZ1WgShEmnrX1isuHBU=";
  };
  buildInputs = [ hugo ];
  buildPhase = ''
    hugo
  '';
  installPhase = ''
    mkdir -p $out/var/www
    cp -r public $out/var/www/matejamaric.com
  '';
}

And added line (callPackage ./blog {}) to my configuration inside of:

  environment.systemPackages = with pkgs; [
    ...
    (callPackage ./blog {})
  ];

Here’s the commit if somebody finds it easier to look at the diff.
Everything builds without an error, although I thought that when the derivation is installed, Nix is going to link the built package on root (/) using it’s output structure. In other word, I thought /var/www/matejamaric.com was going to be linked to the same path in my derivation output, which is not the case. How can I achieve this?

You could also make it available directly in your pkgs instance via overlays (that’s what I’d usually do).

I’m interested, can you show me an example?

1 Like

I also made a Flake in my blog repository. Somebody might find it as an useful example, so here it is:

{
  description = "Nix Flake package for my blog";
  inputs.nixpkgs.url = "nixpkgs/nixos-23.11";
  outputs = { self, nixpkgs }:
    let
      pkgName = "matejasblog";
      supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ];
      forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
      nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; });
    in
    {
      packages = forAllSystems (system:
        let
          pkgs = nixpkgsFor.${system};
        in {
          ${pkgName} = pkgs.stdenv.mkDerivation {
            name = pkgName;
            src = ./.;
            buildInputs = [ pkgs.hugo ];
            buildPhase = ''
              hugo
            '';
            installPhase = ''
              mkdir -p $out/var/www
              cp -r public $out/var/www/matejamaric.com
            '';
          };
        }
      );
      devShells = forAllSystems (system:
        let
          pkgs = nixpkgsFor.${system};
        in {
          default = pkgs.mkShell {
            buildInputs = with pkgs; [ git rsync hugo ];
          };
        }
      );
      defaultPackage = forAllSystems (system: self.packages.${system}.${pkgName});
    };
}

With it, I can use nix build . and nix develop . when working on my blog.

1 Like

When you instantiate pkgs, you can set some overlays, e.g.

{
  blogOverlay = f: p: {
    blog = p.callPackage path/to/blog {};
  };

  pkgs = import inputs.nixpkgs {
     system = "x86_64-linux"; # modify accordingly
     overlays = [ blogOverlay ];
  };
} 

Then you should be able to call pkgs.blog directly anywhere in the code using this pkgs instance.

2 Likes

Your derivation is under $out/var/www/matejamaric.com, where $out would translate into some /nix/store/... path; it will never be under any / root path.

In general, that’s not something you’d want to do. Instead, you may look into making your blog a service via systemd or a standalone server where any dependency can find where the actual /var/www/ is in your nix store.

2 Likes

I marked your original answer as the solution. Thanks again for all the help @gvolpe! :smiley:

1 Like