/nix-build-pack-docker

Nix to Docker buildpack

Primary LanguageMakefile

Nix to Docker buildpack

For creating Docker containers from scratch using Nix package manager.

Please, read Domen's introduction to Nix package manager, for more information about Nix.

Try it

$ git clone https://github.com/datakurre/nix-build-pack-docker
$ cd nix-build-pack-docker
$ make

Then use docker ps (and docker-machine ip default on mac) to figure out, where the example Pyramid service is running.

Step by step

At first, create a generic Nix-builder image and name it nix-builder:

$ docker build -t nix-builder -f nix-builder.docker --rm=true --force-rm=true --no-cache=true .

The builder is made of simple single-user Nix installation on top of some trusted Linux distribution, like debian:

FROM debian:jessie
RUN apt-get update && apt-get install -y curl bzip2 adduser graphviz
RUN adduser --disabled-password --gecos '' user
RUN mkdir -m 0755 /nix && chown user /nix
USER user
ENV USER user
WORKDIR /home/user
RUN curl https://nixos.org/nix/install | sh
VOLUME /nix
COPY nix-builder.sh /home/user/
ENTRYPOINT ["/home/user/nix-builder.sh"]

The image contains a mount point at /nix to support shared persistent Nix-store as a data container for any amount of builder containers.

The entrypoint is a simple script to build a Nix expression and

  • create dependency graph for the build results
  • add /tmp, because that's usually required in images
  • move build results into container root (into /bin etc)
#!/bin/bash
source ~/.nix-profile/etc/profile.d/nix.sh
mkdir tmp
nix-channel --update
nix-build $1
if [ -h result/etc ]; then echo Error: Build resulted /etc as symlink && exit 1; fi
nix-store -q result --graph | sed 's/#ff0000/#ffffff/' | dot -Nstyle=bold -Tpng > $1.png
tar cvz --transform="s|^result/||" tmp `nix-store -qR result` result/* > $1.tar.gz

These build conventions work for me, but the script should be trivial enough to customize.

Note: If the Nix expression results ./result/etc-directory as a symlink (happens when only a single buildInput has ./etc) an error is raised and nothing is created (Docker creates an implict /etc, which cannot be populated from a tarball with /etc as symlink).

Once the builder is build, a data container to persist Nix-store between builds (and allow parallel builds with shared store) is created with:

$ docker create --name nix-store nix-builder

Now you can run the builder for your expression with:

$ docker run --rm --volumes-from=nix-store -v $PWD:/mnt nix-builder /mnt/pyramid.nix

The example pyramid.nix expression simply defines a Python environment with pyramid-package:

with import <nixpkgs> {};

python.buildEnv.override {
  extraLibs = [ pkgs.pythonPackages.pyramid ];
  ignoreCollisions = true;
}

The builder creates a tarball, which could be used in ./Dockerfile to populate an image from scratch:

FROM scratch
ADD pyramid.nix.tar.gz /
EXPOSE 8080
ENTRYPOINT ["/bin/python"]

with a normal docker build command:

$ docker build -t pyramid --rm=true --force-rm=true --no-cache=true .

Finally, the resulting Docker image can be used to Run containers as usual:

$ docker run --rm -v $PWD:/mnt -w /mnt -P pyramid hello_world.py