How to use `nix develop` within a GitLab CI pipeline?

I am trying to run a GitLab CI pipeline with a custom runner and nix flake.

Here is the simplified .gitlab-ci.yml of my project that reproduces my problem:

stages:
  - test

default:
  image: alpine:latest
  before_script: &nix_setup
    - export TZ=Etc/UTC
    - eval $(ssh-agent -s)
    - echo "${SSH_PRIVATE_KEY}" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - echo "${SSH_KNOWN_HOSTS}" > ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts
    - mkdir -p ~/.config/nix
    - echo "experimental-features = nix-command flakes" > ~/.config/nix/nix.conf
    - echo $PATH
    - nix develop .#ci -c echo $PATH
    - nix develop .#ci -c init-pg --help
    - nix develop .#ci
    - echo $PATH
    - init-pg --help

test:
  stage: test
  before_script:
    - *nix_setup
  script:
    - echo "Hello World!"

nix develop is supposed to provide me with a custom init-pg program to initialize a PostgreSQL database. However, as opposed to my local development environment, running nix develop in the GitLab CI pipeline does not seem to do much because I get the error init-pg: not found:

Running with gitlab-runner 15.5.1 (v15.5.1)
  on nix_mymachine_************ ********
Preparing the "docker" executor 00:03
Using Docker executor with image alpine:latest ...
Pulling docker image alpine:latest ...
Using docker image sha256:49176f190c7e9cdb51ac85ab6c6d5e4512352218190cd69b08e6fd803ffbf3da for alpine:latest with digest alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4 ...
Preparing environment 00:04
Running on runner-********-project-********-concurrent-0 via mymachine...
Getting source from Git repository 00:05
Fetching changes with git depth set to 50...
Initialized empty Git repository in /builds/acme/postgresql-cluster/.git/
Created fresh repository.
Checking out ******** as mybranch...
Skipping Git submodules setup
Executing "step_script" stage of the job script
Using docker image sha256:49176f190c7e9cdb51ac85ab6c6d5e4512352218190cd69b08e6fd803ffbf3da for alpine:latest with digest alpine@sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4 ...
$ /nix/store/k7xz7ksajcr4z80ywbhsd0h4738kn7c5-setup-container
installing 'nix-2.11.0'
installing 'nss-cacert-3.83'
installing 'git-2.38.1'
installing 'openssh-9.1p1'
unpacking channels...
$ export TZ=Etc/UTC
$ eval $(ssh-agent -s)
Agent pid 42
$ echo "${SSH_PRIVATE_KEY}" | tr -d '\r' | ssh-add -
Identity added: (stdin) (gitlab.com CI for ACME)
$ mkdir -p ~/.ssh
$ chmod 700 ~/.ssh
$ echo "${SSH_KNOWN_HOSTS}" > ~/.ssh/known_hosts
$ chmod 644 ~/.ssh/known_hosts
$ mkdir -p ~/.config/nix
$ echo "experimental-features = nix-command flakes" > ~/.config/nix/nix.conf
$ echo $PATH
/nix/var/nix/profiles/default/bin:/nix/var/nix/profiles/default/sbin:/bin:/sbin:/usr/bin:/usr/sbin
$ nix develop .#ci -c echo $PATH
/nix/var/nix/profiles/default/bin:/nix/var/nix/profiles/default/sbin:/bin:/sbin:/usr/bin:/usr/sbin
$ nix develop .#ci -c init-pg --help
Initialize a PostgreSQL cluster for a given environment.
Usage:
  /nix/store/bbcflvw77idq1v060gqp77rw616v1ybw-init-pg-unstable-2020-03-16/bin/init-pg --help                            Display this help message.
  /nix/store/bbcflvw77idq1v060gqp77rw616v1ybw-init-pg-unstable-2020-03-16/bin/init-pg <options>                         Options.
Options:
  --env  ENV                           Environment. Required.
  --port PORT                          Port. Required.
$ nix develop .#ci
$ echo $PATH
/nix/var/nix/profiles/default/bin:/nix/var/nix/profiles/default/sbin:/bin:/sbin:/usr/bin:/usr/sbin
$ init-pg --help
/bin/sh: eval: line 162: init-pg: not found
Cleaning up project directory and file based variables
ERROR: Job failed: exit code 127

I do not understand what happens. As we can see in the logs, it seems that after nix develop .#ci ran, the next command does not run in the interactive shell. But it can successfully run if I use nix develop .#ci -c init-pg --help.

I have many commands to run so I would like to avoid to prefix all of them with nix develop .#ci -c <COMMAND>.

Any idea of what is happening here?

For information, the custom GitLab runner I use is very close to the one documented. It is deployed on a NixOS machine with nixops. Here is the module:

{ lib, pkgs, ... }:
let
  secretsDir = "/var/keys";
  registrationConfigBasename = "gitlab-runner-registration-config";
  registrationConfigFile = "${secretsDir}/${registrationConfigBasename}";
in
{
  deployment.keys = {
    "${registrationConfigBasename}" = {
      text = let
        serverUrl = "https://gitlab.com";
        registrationToken = builtins.readFile ../secrets/gitlab-registration-token;
      in ''
        CI_SERVER_URL=${serverUrl}
        REGISTRATION_TOKEN=${registrationToken}'';
      destDir = "${secretsDir}";
      user = "root";
      group = "root";
      permissions = "0640";
    };
  };

  boot.kernel.sysctl."net.ipv4.ip_forward" = true;
  virtualisation.docker.enable = true;

  services.gitlab-runner = {
    enable = true;
    services = {

      nix = {
        inherit registrationConfigFile;
        dockerImage = "alpine";
        dockerVolumes = [
          "/nix/store:/nix/store:ro"
          "/nix/var/nix/db:/nix/var/nix/db:ro"
          "/nix/var/nix/daemon-socket:/nix/var/nix/daemon-socket:ro"
        ];
        dockerDisableCache = true;
        preBuildScript = pkgs.writeScript "setup-container" ''
          mkdir -p -m 0755 /nix/var/log/nix/drvs
          mkdir -p -m 0755 /nix/var/nix/gcroots
          mkdir -p -m 0755 /nix/var/nix/profiles
          mkdir -p -m 0755 /nix/var/nix/temproots
          mkdir -p -m 0755 /nix/var/nix/userpool
          mkdir -p -m 1777 /nix/var/nix/gcroots/per-user
          mkdir -p -m 1777 /nix/var/nix/profiles/per-user
          mkdir -p -m 0755 /nix/var/nix/profiles/per-user/root
          mkdir -p -m 0700 "$HOME/.nix-defexpr"

          . ${pkgs.nix}/etc/profile.d/nix.sh

          ${pkgs.nix}/bin/nix-env -i ${lib.concatStringsSep " " (with pkgs; [ nix cacert git openssh ])}

          ${pkgs.nix}/bin/nix-channel --add https://nixos.org/channels/nixpkgs-unstable
          ${pkgs.nix}/bin/nix-channel --update nixpkgs
        '';
        environmentVariables = {
          ENV = "/etc/profile";
          USER = "root";
          NIX_REMOTE = "daemon";
          PATH = "/nix/var/nix/profiles/default/bin:/nix/var/nix/profiles/default/sbin:/bin:/sbin:/usr/bin:/usr/sbin";
          NIX_SSL_CERT_FILE = "/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt";
        };
      };
    };
  };
}

This expands $PATH before entering the shell, please use nix develop .#ci -c printenv PATH instead.

As we are running in a non-interactive environment, this enters the shell and exits immediately, it basically is a noop.

Please use nix develop .#ci -c init-pg …

1 Like

Thank you, that was really helpful to understand what was going on.

That made me realize that nix develop may not be what I need to setup my CI environment. Before flakes, I was using nix-env -i -f default.nix in my GitLab CI pipeline to install my custom environment (using buildEnv) which provides init-pg.

Is there a equivalent command with the new nix commands that works with flakes?

I tried to package my CI environement with buildEnv and call nix shell .#my-ci-env, without success.

pkgs.buildEnv {
  name = "ci-packages";
  paths = [
    pkgs.bash
    # other packages and my custom init-pg
    # ...
  ];
};

nix shell has the very same ptoblem that nix develop has as well. You need -c.

You can try nix profile install which would be the most straight forward way to “translate” a nix-env -i to something that can be used with flakes.

Thank you, I will look into that.

But how is everyone setting up their CI environment? Is there an idiomatic nix way of doing it?

This works successfully!

I use -c with nix develop and nix shell if I ever need it. Most of the time I’m just running nix build.