Installing a Haskell build stack can be daunting, so building and/or shipping programs with docker seems like a good way to shield users from this complexity. I’d like to learn more about how people use these (or whether everybody is using nix instead).
The background is that we are currently working on making official pandoc images a reality. We opted for Alpine images in order to keep image size low.
Any tips on how to combine docker, Haskell, and its build tools effectively would be most welcome.
People think nix is not easy to learn, which might be true, but it is actually quite simple to use it for Haskell projects. If your project has a cabal file, you only need a file called default.nix containing:
Then nix-shell --run 'cabal new-build' to build the project or nix-shell --run 'cabal new-run' to run it. The nix-shell command will put you in an environment containing the right GHC version and with the package dependencies.
EDIT: Here is the current default.nix for building pandoc on Linux and Mac via nix.
At work i use a build-container with a dockerfile like:
FROM ubuntu
RUN apt-get update
RUN apt-get install -y g++ gcc libc6-dev libffi-dev libgmp-dev make xz-utils zlib1g-dev git gnupg curl
libexpat-dev locales && apt-get clean && rm -rf /var/lib/apt/lists/*
RUN locale-gen de_DE.UTF-8 && update-locale
RUN curl -sSL https://get.haskellstack.org/ | sh
ENV resolver=lts-13.12
ENV LC_ALL de_DE.utf8
RUN mkdir -p /usr/src/build
RUN cd /usr/src/build && /usr/local/bin/stack setup --resolver ${resolver}
#preinstall some needed packages so they get cached
RUN cd /usr/src/build && /usr/local/bin/stack install aeson --resolver ${resolver} --work-dir=.stack-work
RUN cd /usr/src/build && /usr/local/bin/stack install async --resolver ${resolver} --work-dir=.stack-work
[...]
# copy project & build
COPY . /usr/src/build/
RUN cd /usr/src/build && /usr/local/bin/stack install --work-dir=.stack-work
and then copy the binary from containername:~/.local/bin to .
Then i build an image just containing the binary & all deps for that
FROM ubuntu
RUN apt-get update && apt-get install -y netbase libc6-dev libffi-dev libgmp-dev xz-utils zlib1g-dev gnupg curl libexpat1 locales && apt-get clean && rm -rf /var/lib/apt/lists/*
RUN locale-gen de_DE.UTF-8 && update-locale
RUN mkdir -p /app
ENV LC_ALL de_DE.utf8
COPY $BINARYNAME /app
COPY data /app/data
WORKDIR /app
ENTRYPOINT ["/app/$BINARY"]
this yields an ~150mb-image. ~50mb is base-ubuntu, ~50mb got added via apt as dependencies & ~50mb is the binary that was produced.
The build-image gets quite big (~4GB), but that is just for building. You could achieve more with using alpine as base (but some deps are not in their repos), strip the binary, optimize for size, etc.
Nix can create dockerimage, I don’t know how it do it but when I look into the result it seem not based on any linux distribution.
For example, try building following default.nix (via nix-build)
# create docker images using nix-build
# load with `docker load < result`
# run with `docker -it` for interactive with vt
{ pkgs ? import <nixpkgs> {} }:
with pkgs;
dockerTools.buildImage {
name = "mydocker";
tag = "latest";
contents = [
pandoc
];
}
I’ve recently added support to optionally split the executables in a separate output. In Nix, they are compiled statically by default, while library is compiled dynamically. This should get the docker images to a few 10 MBs.
Unfortunately, it seems like Paths libddir reference stays in the Nix closure, so it’s not much help here until we figure out why splitting sections didn’t get rid of it.
I am experimenting with stack to the point that it is somewhat usable but still can’t get the stack update integrated in the image. When I run stack new test it will always pull the update.
Help/suggestions are always welcome, the first goal is ability to build hello world project in mounted volume.
$ docker run -v "$PWD/data":/data haskell_stack stack build