Evaluation is the first step when it comes to building your project. It is performed by your agent as follows:

  1. At the top-level of your repository, the agent will search for a Nix file to evaluate in the following order: nix/ci.nix, ci.nix or default.nix

  2. NIX_PATH will be empty. Make sure you pin nixpkgs and not rely on <nixpkgs> search path. This is to ensure reproducability and purity also when it comes to nixpkgs

  3. If any of the attributes fail to evaluate, job evaluation phase will be marked as failed and error messages will be shown inline per attribute


Top level

The top-level value in nix/ci.nix, ci.nix or default.nix may be a function⁠, as long as all arguments have default values. Such a function will be invoked and the result must be a value⁠.

Attribute set traversal

The value resulting from Top level is traversed according to the following rules:

  • If the current value is a derivation, build it asynchronously

  • If the current value is a functor, ignore the attributes, call it and traverse it

  • If the current value is an attribute set and

    • If it is the root value, traverse one level of attributes.

    • If the attribute set contains recurseForDerivations = true, traverse one level of attributes.

    • Otherwise, ignore

  • Otherwise, ignore

Note that for an attribute set a it must be the root or it must have a.recurseForDerivations == true, which can be added with pkgs.recurseIntoAttrs. This must be repeated in every layer of the nested attribute sets. Additionaly, recursion into nested attribute sets is limited to a depth of 10 attributes or functor calls.

Example of nested attribute sets

This is an example ci.nix that builds myDrvs.hello.

  pkgs = builtins.fetchTarball {
    url = "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz";
  myDrvs = pkgs.recurseIntoAttrs {
    hello = pkgs.hello;

Platforms and build matrix

Evaluation is always performed on an x86_64-linux agent. To build for other platforms, you have to explicitly pass the system parameter to Nixpkgs.

To build your project for multiple platforms, the recommended approach is to make the top-level value of ci.nix an attribute set with an attribute per platform. Make sure to wrap the values in recurseIntoAttrs. The minimal repo from the Getting Started guide has an example.

This technique can be nested to achieve a build matrix where multiple independent variables are varied.

If some derivations can not be built on a certain platform or configuration, you can use lib.optionalAttrs, lib.filterAttrs or other functions to remove those.

Differences with nix-build

The agent expects the root of the file to be an attribute set and traverses it similarly to how nix build does it. A few differences exists:

  • Hercules CI allows all attribute names, including ones with characters like . that are ignored by nix build

  • The root must not be a list

Differences with Hydra

Hydra is not compatible with nix-build. It rejects the recurseForDerivations attribute.

The ci.nix format can be built by Hydra using a conversion function, but Hydra will not understand Hercules CI-specific attributes when introduced.

stripRecurseForDerivations =
  lib.filterAttrsRecursive (n: _: n != "recurseForDerivations");

Hydra reads meta.hydraPlatforms to determine whether to build for a platform. This logic is not present in the agent, but can be implemented in Nix with a filtering function if necessary.