Based on dockerTools.buildImage ADD/COPY equivalent, it looks like the good ole fashioned way to add local files to a Nix-produced docker container was contents
. However, according to this warning, the contents parameter is deprecated:
warning: in docker image: The contents parameter is deprecated. Change to copyToRoot if the contents are designed to be copied to the root filesystem, such as when you use `buildEnv` or similar between contents and your packages. Use copyToRoot = buildEnv { ... }; or similar if you intend to add packages to /bin.
When I try to do what I thought would be the equivalent:
{
pkgs ? import <nixpkgs> {},
pkgsLinux ? import <nixpkgs> {system = "x86_64-linux";},
}:
pkgs.dockerTools.buildImage {
name = "hello-docker";
tag = "latest";
copyToRoot = pkgs.buildEnv {
name = "image-root";
pathsToLink = ["/"];
paths = [pkgs.coreutils ./hello.sh];
};
config = {
Env = ["PATH=/bin/"];
Cmd = [
"${pkgs.bash}/bin/bash"
];
};
}
I get:
error: The store path /nix/store/pd8ny76d3skhk5r7xqg625p3fhla1igp-hello.sh is a file and can't be merged into an environment using pkgs.buildEnv! at /nix/store/2s4zc82jpfvdz3pavxc2vqvm6fq45syv-builder.pl line 122.
I have looked at the buildImage
documentation and what I think is the buildEnv
source code but haven’t been able to figure out what I’m doing wrong. Thanks for your help!
paths
is supposed to be list of directories to merge together. ./hello.sh
is a file, not a directory.
1 Like
I see. This works then:
{
pkgs ? import <nixpkgs> {},
pkgsLinux ? import <nixpkgs> {system = "x86_64-linux";},
}:
pkgs.dockerTools.buildImage {
name = "hello-docker";
tag = "latest";
copyToRoot = pkgs.buildEnv {
name = "image-root";
pathsToLink = ["/hello" "/"];
paths = [pkgs.coreutils ./.]; # ./. not ./hello.sh
};
config = {
Env = ["PATH=/bin/"];
Cmd = [
"${pkgs.bash}/bin/bash"
];
};
}
Although it seems to map everything to /
. How would I map ./.
to e.g. /app
?
pathsToLink
is the list of subdirectories to be merged. So when you put "/"
in there, you’re saying everything should get merged, making "/hello"
redundant. So paths
is the list of paths you want to get files from, and pathsToLink
is the list of subdirectories within those paths that will be merged and linked. e.g.
pkgs.buildEnv {
name = "foobar";
paths = [pkgs.coreutils pkgs.util-linux];
pathsToLink = ["/bin"];
}
Produces a path with just a bin
directory, which contains symlinks to all the files in the bin
directories of coreutils
and util-linux
.
Right that makes sense. I actually meant to remove /hello
. But is there a way to map ~/my-project/hello.sh
to /app/hello.sh
inside the container?
You could just put hello.sh
into ~/my-project/app/hello.sh
, using something like paths = [./. pkgs.coreutils];
and pathsToLink = ["/app" "/bin"];
. But I don’t like using ./.
because that puts your whole project directory into the nix store every time you do a build. You can fix that either by using builtins.filterSource
on ./.
to make sure only the app
subdir is included, or you can just make a derivation that puts the file in the right place like this:
pkgs.writeTextDir "app/hello.sh" (builtins.readFile ./hello.sh)
Asign that to a variable and put that variable in paths
instead of ./.
.
Ok. In my application, I actually have all my source code in a subdirectory so I should be able to just link that subdirectory with pathsToLink
. Thanks!
This is the solution that worked for me:
{
pkgs ? import <nixpkgs> {},
pkgsLinux ? import <nixpkgs> {system = "x86_64-linux";},
}:
pkgs.dockerTools.buildImage {
name = "hello-docker";
tag = "latest";
copyToRoot = pkgs.buildEnv {
name = "image-root";
pathsToLink = ["/app" "/bin"];
paths = [pkgs.coreutils ./.];
};
config = {
Env = ["PATH=/bin/"];
Cmd = [
"${pkgs.bash}/bin/bash"
];
};
}
Here is my directory structure:
.
├── app
│ └── hello.sh
├── big-file-to-ignore
└── hello-docker.nix
I tried adding
let
app = builtins.filterSource (path: type: (baseNameOf path) == "app") ./.;
in
...
copyToRoot = pkgs.buildEnv {
name = "image-root";
pathsToLink = ["/" "/bin"];
paths = [pkgs.coreutils app];
};
...
but /app
was empty inside the container (no hello.sh
). Maybe I am not using filterSource
correctly.
1 Like
builtins.filterSource (path: type: (baseNameOf path) == "app") ./.
This will include the directory, but exclude its children, because the baseNameOf "/app/hello.sh"
is "hello.sh"
, not "app"
.
So how would I include /app/hello.sh
without also including big-file-to-ignore
(and without copying files one-by-one with something like pkgs.writeTextDir
)? I added .dockerignore
but nix seems to ignore it.
This thread is the top result on Google for relevant search terms, so for posterity let me propose a solution that works as of 23.11: filesets. Here is an example docker.nix
file demonstrating one way to solve @ethanabrooks’ problem:
let
pkgs = import <nixpkgs> {};
app = pkgs.lib.fileset.toSource {
root = ./.;
fileset = ./app;
};
in pkgs.dockerTools.buildLayeredImage {
name = "example";
tag = "latest";
contents = [
app
pkgs.tcl
pkgs.toybox
];
config = {
Env = [
"PATH=/bin/"
"SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
];
Cmd = [
"tclsh" "/app/whatever.tcl"
];
};
}
This will place the app
directory in the container at /app
, similar to using the Dockerfile ADD
or COPY
instructions.
Note: I used buildLayeredImage
and contents
here instead of buildImage
and copyToRoot
because I think this pattern is actually what most people looking to get started with Nix and Docker want, but it doesn’t really matter because the fileset
approach has the same result either way.
5 Likes