How to add a non-root user when building a Docker image with Nix

I can use Nix to specify a Docker image:

{ pkgs ? import <nixpkgs> { }
, pkgsLinux ? import <nixpkgs> { system = "x86_64-linux"; }
}:
pkgs.dockerTools.buildImage {
  name = "delme";
  tag = "latest";
  contents = pkgs.buildEnv {
    name = "image-root";
    paths = with pkgsLinux; [
      bashInteractive
      coreutils
      curl
    ];
    pathsToLink = [ "/bin" ];
  };
  config = {
    Cmd = [ "${pkgsLinux.bashInteractive}/bin/bash" ];
  };
}

This works, but the resultant image runs as root. How can I extend this example to add a non-root user (ideally with a specific UID/GID) and specify that the container runs with that user?

Accordingly to the docs config property could contain any options exposed by the Docker spec. The one you need is User.

How can I add a non-root user (that I can then reference in config.User)?

I haven’t tried this with Nix but Docker user is completely independent of the users on the host system where the container is built. So just specify any non-root user and it should work.

Thanks - so I was expecting to need to add a user inside the container (not on the host system) in order to be able to then run the image as that UID - perhaps using something like:

RUN useradd -m --uid=1000 -r -s /bin/bash cloudsdk

Which is the sort of thing I might have done in a vanilla Dockerfile. But apparently that’s not actually necessary, TIL (Gamlor's Blog: On Linux (Unix?) You Can Make Up a User ID On The Fly). So this does work like you say, e.g.

  config = {
    Cmd = [ "${pkgsLinux.bashInteractive}/bin/bash" ];
    User = "1000:1000";
  };

However, I was hoping to add a bit more setup for the user rather than just adopt a UID - in particular I wanted them to have a home directory they could write to. Is there a good recipe for doing that?

I don’t think you need anything special from Nix for that, just use runAsRoot instead of RUN with useradd to explicitly create a user’s home directory, configure groups etc., or just do an mkdir + chown to get a writeable directory.

1 Like

Thanks, that makes sense (I’ve hit some issues trying to use runAsRoot, but will post as a separate thread).

I came across shadowSetup which seems to be designed for exactly this purpose:

  runAsRoot = ''
    #!${pkgs.runtimeShell}
    ${pkgs.dockerTools.shadowSetup}
    groupadd -r redis
    useradd -r -g redis redis
    mkdir /data
    chown redis:redis /data
  '';