Merge pull request #285223 from DanielSidhion/update-dockertools-envhelpers

doc: update environment helpers in dockerTools docs, add fakeNss section
This commit is contained in:
Silvan Mosberger 2024-02-08 16:31:33 +01:00 committed by GitHub
commit b94e9dd7a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 244 additions and 65 deletions

View File

@ -1088,103 +1088,200 @@ If you don't specify a `name` attribute, you'll encounter an evaluation error an
## Environment Helpers {#ssec-pkgs-dockerTools-helpers}
Some packages expect certain files to be available globally.
When building an image from scratch (i.e. without `fromImage`), these files are missing.
`pkgs.dockerTools` provides some helpers to set up an environment with the necessary files.
You can include them in `copyToRoot` like this:
When building Docker images with Nix, you might also want to add certain files that are expected to be available globally by the software you're packaging.
Simple examples are the `env` utility in `/usr/bin/env`, or trusted root TLS/SSL certificates.
Such files will most likely not be included if you're building a Docker image from scratch with Nix, and they might also not be included if you're starting from a Docker image that doesn't include them.
The helpers in this section are packages that provide some of these commonly-needed global files.
```nix
buildImage {
name = "environment-example";
copyToRoot = with pkgs.dockerTools; [
usrBinEnv
binSh
caCertificates
fakeNss
];
}
```
Most of these helpers are packages, which means you have to add them to the list of contents to be included in the image (this changes depending on the function you're using to build the image).
[](#ex-dockerTools-helpers-buildImage) and [](#ex-dockerTools-helpers-buildLayeredImage) show how to include these packages on `dockerTools` functions that build an image.
For more details on how that works, see the documentation for the function you're using.
### usrBinEnv {#sssec-pkgs-dockerTools-helpers-usrBinEnv}
This provides the `env` utility at `/usr/bin/env`.
This is currently implemented by linking to the `env` binary from the `coreutils` package, but is considered an implementation detail that could change in the future.
### binSh {#sssec-pkgs-dockerTools-helpers-binSh}
This provides `bashInteractive` at `/bin/sh`.
This provides a `/bin/sh` link to the `bash` binary from the `bashInteractive` package.
Because of this, it supports cases such as running a command interactively in a container (for example by running `docker run -it <image_name>`).
### caCertificates {#sssec-pkgs-dockerTools-helpers-caCertificates}
This sets up `/etc/ssl/certs/ca-certificates.crt`.
This adds trusted root TLS/SSL certificates from the `cacert` package in multiple locations in an attempt to be compatible with binaries built for multiple Linux distributions.
The locations currently used are:
- `/etc/ssl/certs/ca-bundle.crt`
- `/etc/ssl/certs/ca-certificates.crt`
- `/etc/pki/tls/certs/ca-bundle.crt`
[]{#ssec-pkgs-dockerTools-fakeNss}
### fakeNss {#sssec-pkgs-dockerTools-helpers-fakeNss}
Provides `/etc/passwd` and `/etc/group` that contain root and nobody.
Useful when packaging binaries that insist on using nss to look up
username/groups (like nginx).
This is a re-export of the `fakeNss` package from Nixpkgs.
See [](#sec-fakeNss).
### shadowSetup {#ssec-pkgs-dockerTools-shadowSetup}
This constant string is a helper for setting up the base files for managing users and groups, only if such files don't exist already. It is suitable for being used in a [`buildImage` `runAsRoot`](#ex-dockerTools-buildImage-runAsRoot) script for cases like in the example below:
This is a string containing a script that sets up files needed for [`shadow`](https://github.com/shadow-maint/shadow) to work (using the `shadow` package from Nixpkgs), and alters `PATH` to make all its utilities available in the same script.
It is intended to be used with other dockerTools functions in attributes that expect scripts.
After the script in `shadowSetup` runs, you'll then be able to add more commands that make use of the utilities in `shadow`, such as adding any extra users and/or groups.
See [](#ex-dockerTools-shadowSetup-buildImage) and [](#ex-dockerTools-shadowSetup-buildLayeredImage) to better understand how to use it.
`shadowSetup` achieves a result similar to [`fakeNss`](#sssec-pkgs-dockerTools-helpers-fakeNss), but only sets up a `root` user with different values for the home directory and the shell to use, in addition to setting up files for [PAM](https://en.wikipedia.org/wiki/Linux_PAM) and a {manpage}`login.defs(5)` file.
:::{.caution}
Using both `fakeNss` and `shadowSetup` at the same time will either cause your build to break or produce unexpected results.
Use either `fakeNss` or `shadowSetup` depending on your use case, but avoid using both.
:::
:::{.note}
When used with [`buildLayeredImage`](#ssec-pkgs-dockerTools-buildLayeredImage) or [`streamLayeredImage`](#ssec-pkgs-dockerTools-streamLayeredImage), you will have to set the `enableFakechroot` attribute to `true`, or else the script in `shadowSetup` won't run properly.
See [](#ex-dockerTools-shadowSetup-buildLayeredImage).
:::
### Examples {#ssec-pkgs-dockerTools-helpers-examples}
:::{.example #ex-dockerTools-helpers-buildImage}
# Using `dockerTools`'s environment helpers with `buildImage`
This example adds the [`binSh`](#sssec-pkgs-dockerTools-helpers-binSh) helper to a basic Docker image built with [`dockerTools.buildImage`](#ssec-pkgs-dockerTools-buildImage).
This helper makes it possible to enter a shell inside the container.
This is the `buildImage` equivalent of [](#ex-dockerTools-helpers-buildLayeredImage).
```nix
buildImage {
name = "shadow-basic";
{ dockerTools, hello }:
dockerTools.buildImage {
name = "env-helpers";
tag = "latest";
runAsRoot = ''
#!${pkgs.runtimeShell}
${pkgs.dockerTools.shadowSetup}
groupadd -r redis
useradd -r -g redis redis
mkdir /data
chown redis:redis /data
'';
}
copyToRoot = [
hello
dockerTools.binSh
];
```
Creating base files like `/etc/passwd` or `/etc/login.defs` is necessary for shadow-utils to manipulate users and groups.
After building the image and loading it in Docker, we can create a container based on it and enter a shell inside the container.
This is made possible by `binSh`.
When using `buildLayeredImage`, you can put this in `fakeRootCommands` if you `enableFakechroot`:
```nix
buildLayeredImage {
name = "shadow-layered";
fakeRootCommands = ''
${pkgs.dockerTools.shadowSetup}
'';
enableFakechroot = true;
}
```shell
$ nix-build
(some output removed for clarity)
/nix/store/2p0i3i04cgjlk71hsn7ll4kxaxxiv4qg-docker-image-env-helpers.tar.gz
$ docker load -i /nix/store/2p0i3i04cgjlk71hsn7ll4kxaxxiv4qg-docker-image-env-helpers.tar.gz
(output removed for clarity)
$ docker run --rm -it env-helpers:latest /bin/sh
sh-5.2# help
GNU bash, version 5.2.21(1)-release (x86_64-pc-linux-gnu)
(rest of output removed for clarity)
```
:::
## fakeNss {#ssec-pkgs-dockerTools-fakeNss}
:::{.example #ex-dockerTools-helpers-buildLayeredImage}
# Using `dockerTools`'s environment helpers with `buildLayeredImage`
If your primary goal is providing a basic skeleton for user lookups to work,
and/or a lesser privileged user, adding `pkgs.fakeNss` to
the container image root might be the better choice than a custom script
running `useradd` and friends.
It provides a `/etc/passwd` and `/etc/group`, containing `root` and `nobody`
users and groups.
It also provides a `/etc/nsswitch.conf`, configuring NSS host resolution to
first check `/etc/hosts`, before checking DNS, as the default in the absence of
a config file (`dns [!UNAVAIL=return] files`) is quite unexpected.
You can pair it with `binSh`, which provides `bin/sh` as a symlink
to `bashInteractive` (as `/bin/sh` is configured as a shell).
This example adds the [`binSh`](#sssec-pkgs-dockerTools-helpers-binSh) helper to a basic Docker image built with [`dockerTools.buildLayeredImage`](#ssec-pkgs-dockerTools-buildLayeredImage).
This helper makes it possible to enter a shell inside the container.
This is the `buildLayeredImage` equivalent of [](#ex-dockerTools-helpers-buildImage).
```nix
buildImage {
name = "shadow-basic";
{ dockerTools, hello }:
dockerTools.buildLayeredImage {
name = "env-helpers";
tag = "latest";
copyToRoot = pkgs.buildEnv {
name = "image-root";
paths = [ binSh pkgs.fakeNss ];
pathsToLink = [ "/bin" "/etc" "/var" ];
contents = [
hello
dockerTools.binSh
];
config = {
Cmd = [ "/bin/hello" ];
};
}
```
After building the image and loading it in Docker, we can create a container based on it and enter a shell inside the container.
This is made possible by `binSh`.
```shell
$ nix-build
(some output removed for clarity)
/nix/store/rpf47f4z5b9qr4db4ach9yr4b85hjhxq-env-helpers.tar.gz
$ docker load -i /nix/store/rpf47f4z5b9qr4db4ach9yr4b85hjhxq-env-helpers.tar.gz
(output removed for clarity)
$ docker run --rm -it env-helpers:latest /bin/sh
sh-5.2# help
GNU bash, version 5.2.21(1)-release (x86_64-pc-linux-gnu)
(rest of output removed for clarity)
```
:::
:::{.example #ex-dockerTools-shadowSetup-buildImage}
# Using `dockerTools.shadowSetup` with `dockerTools.buildImage`
This is an example that shows how to use `shadowSetup` with `dockerTools.buildImage`.
Note that the extra script in `runAsRoot` uses `groupadd` and `useradd`, which are binaries provided by the `shadow` package.
These binaries are added to the `PATH` by the `shadowSetup` script, but only for the duration of `runAsRoot`.
```nix
{ dockerTools, hello }:
dockerTools.buildImage {
name = "shadow-basic";
tag = "latest";
copyToRoot = [ hello ];
runAsRoot = ''
${dockerTools.shadowSetup}
groupadd -r hello
useradd -r -g hello hello
mkdir /data
chown hello:hello /data
'';
config = {
Cmd = [ "/bin/hello" ];
WorkingDir = "/data";
};
}
```
:::
:::{.example #ex-dockerTools-shadowSetup-buildLayeredImage}
# Using `dockerTools.shadowSetup` with `dockerTools.buildLayeredImage`
It accomplishes the same thing as [](#ex-dockerTools-shadowSetup-buildImage), but using `buildLayeredImage` instead.
Note that the extra script in `fakeRootCommands` uses `groupadd` and `useradd`, which are binaries provided by the `shadow` package.
These binaries are added to the `PATH` by the `shadowSetup` script, but only for the duration of `fakeRootCommands`.
```nix
{ dockerTools, hello }:
dockerTools.buildLayeredImage {
name = "shadow-basic";
tag = "latest";
contents = [ hello ];
fakeRootCommands = ''
${dockerTools.shadowSetup}
groupadd -r hello
useradd -r -g hello hello
mkdir /data
chown hello:hello /data
'';
enableFakechroot = true;
config = {
Cmd = [ "/bin/hello" ];
WorkingDir = "/data";
};
}
```
:::
## buildNixShellImage {#ssec-pkgs-dockerTools-buildNixShellImage}
Create a Docker image that sets up an environment similar to that of running `nix-shell` on a derivation.

View File

@ -3,6 +3,7 @@
This chapter describes several special build helpers.
```{=include=} sections
special/fakenss.section.md
special/fhs-environments.section.md
special/makesetuphook.section.md
special/mkshell.section.md

View File

@ -0,0 +1,77 @@
# fakeNss {#sec-fakeNss}
Provides `/etc/passwd` and `/etc/group` files that contain `root` and `nobody`, allowing user/group lookups to work in binaries that insist on doing those.
This might be a better choice than a custom script running `useradd` and related utilities if you only need those files to exist with some entries.
`fakeNss` also provides `/etc/nsswitch.conf`, configuring NSS host resolution to first check `/etc/hosts` before checking DNS, since the default in the absence of a config file (`dns [!UNAVAIL=return] files`) is quite unexpected.
It also creates an empty directory at `/var/empty` because it uses that as the home directory for the `root` and `nobody` users.
The `/var/empty` directory can also be used as a `chroot` target to prevent file access in processes that do not need to access files, if your container runs such processes.
The user entries created by `fakeNss` use the `/bin/sh` shell, which is not provided by `fakeNss` because in most cases it won't be used.
If you need that to be available, see [`dockerTools.binSh`](#sssec-pkgs-dockerTools-helpers-binSh) or provide your own.
## Inputs {#sec-fakeNss-inputs}
`fakeNss` is made available in Nixpkgs as a package rather than a function, but it has two attributes that can be overridden and might be useful in particular cases.
For more details on how overriding works, see [](#ex-fakeNss-overriding) and [](#sec-pkg-override).
`extraPasswdLines` (List of Strings; _optional_)
: A list of lines that will be added to `/etc/passwd`.
Useful if extra users need to exist in the output of `fakeNss`.
If `extraPasswdLines` is specified, it will **not** override the `root` and `nobody` entries created by `fakeNss`.
Those entries will always exist.
Lines specified here must follow the format in {manpage}`passwd(5)`.
_Default value:_ `[]`.
`extraGroupLines` (List of Strings; _optional_)
: A list of lines that will be added to `/etc/group`.
Useful if extra groups need to exist in the output of `fakeNss`.
If `extraGroupLines` is specified, it will **not** override the `root` and `nobody` entries created by `fakeNss`.
Those entries will always exist.
Lines specified here must follow the format in {manpage}`group(5)`.
_Default value:_ `[]`.
## Examples {#sec-fakeNss-examples}
:::{.example #ex-fakeNss-dockerTools-buildImage}
# Using `fakeNss` with `dockerTools.buildImage`
This example shows how to use `fakeNss` as-is.
It is useful with functions in `dockerTools` to allow building Docker images that have the `/etc/passwd` and `/etc/group` files.
This example includes the `hello` binary in the image so it can do something besides just have the extra files.
```nix
{ dockerTools, fakeNss, hello }:
dockerTools.buildImage {
name = "image-with-passwd";
tag = "latest";
copyToRoot = [ fakeNss hello ];
config = {
Cmd = [ "/bin/hello" ];
};
}
```
:::
:::{.example #ex-fakeNss-overriding}
# Using `fakeNss` with an override to add extra lines
The following code uses `override` to add extra lines to `/etc/passwd` and `/etc/group` to create another user and group entry.
```nix
{ fakeNss }:
fakeNss.override {
extraPasswdLines = ["newuser:x:9001:9001:new user:/var/empty:/bin/sh"];
extraGroupLines = ["newuser:x:9001:"];
}
```
:::

View File

@ -314,5 +314,8 @@
"systemd-veritysetup@.service(8)": "https://www.freedesktop.org/software/systemd/man/systemd-veritysetup@.service.html",
"systemd-volatile-root(8)": "https://www.freedesktop.org/software/systemd/man/systemd-volatile-root.html",
"systemd-xdg-autostart-generator(8)": "https://www.freedesktop.org/software/systemd/man/systemd-xdg-autostart-generator.html",
"udevadm(8)": "https://www.freedesktop.org/software/systemd/man/udevadm.html"
"udevadm(8)": "https://www.freedesktop.org/software/systemd/man/udevadm.html",
"passwd(5)": "https://man.archlinux.org/man/passwd.5",
"group(5)": "https://man.archlinux.org/man/group.5",
"login.defs(5)": "https://man.archlinux.org/man/login.defs.5"
}

View File

@ -805,6 +805,7 @@ rec {
'';
# This provides /bin/sh, pointing to bashInteractive.
# The use of bashInteractive here is intentional to support cases like `docker run -it <image_name>`, so keep these use cases in mind if making any changes to how this works.
binSh = runCommand "bin-sh" { } ''
mkdir -p $out/bin
ln -s ${bashInteractive}/bin/bash $out/bin/sh