1

I'm having a few issues try to build a Docker container that runs one Haskell application indefinitely. For starters, I'd like to use a base image that provides a program I need to use from my code. It is based on scratch linux. However, when I build my Haskell program and copy it to that container, I get an error:

standard_init_linux.go:211: exec user process caused "no such file or directory"

Next, I would like to keep my build process, and file structure very simple if possible. I have just one script in Haskell in Main.hs and it has one dependency on process. If it's possible and reasonable to avoid both a stack and a cabal file as well as subdirectories and all the that, it'd be nice if the build directive where just in the Docker or in the Haskell file.

However I have an issue with the build in that the stack ghc line takes several minutes to download ghc and process and build everything and that line reexecutes whenever I make a small code change. This makes development very difficult.

What's a better process for running a simple Haskell script in a Docker image?

Here is my simplified Docker image:

# Pretty standard just using the latest stack-build
FROM fpco/stack-build:lts-15.4 as haskell
# Setup a build dir and copy code to it
WORKDIR /opt/build
COPY Main.hs /opt/build
# This step takes forever and reruns every time I make a code change.
RUN stack ghc --package process -- Main.hs

# Alpine failed here for file not found.
FROM ubuntu:latest
COPY --from=haskell /opt/build/Main /Main
ENTRYPOINT ["/Main"]

A simplified version of the Haskell program.

import System.Process (readProcess)
import Control.Monad (forever)
main = forever $ do
    output <- readProcess "/bin/ls" [] ""
    print output
mmachenry
  • 1,773
  • 3
  • 22
  • 38
  • I don't have experience with Haskell, but I think you have two paths. One: develop your app locally and use Docker only for building & deploying. Two: have multistage Dockerfile with dev, build, prod stages (or whatever you need). Your dev stage would be an environment with everything you need and you'll bind-mount your source code dir into some dir in the container and you would work just as you would locally. – Stefan Golubović Mar 18 '20 at 22:12

1 Answers1

4

That image is intended to be used with Haskell Stack's Docker integration. One very reasonable path is just to use that path to build a binary in a host-system directory, and then use the second half of that Dockerfile to package the binary into a Docker image.

If you look at what gets built, it's a dynamically linked binary that has a non-default dependency. If I change ubuntu to alpine (temporarily) and change ENTRYPOINT to CMD then I can run

$ docker run --rm 101681db8d96 ldd /Main
Error loading shared library libgmp.so.10: No such file or directory (needed by /Main)

This also will not start with the musl libc that's packaged in the Alpine image (it's not obvious why), so you need to install the GNU libc compatibility package as well as the libgmp package.

(Since it's a dynamically linked binary, you also can't run it in a FROM scratch image, unless you're willing to hand-install GNU libc and the other supporting libraries you need.

For the build stage, as the image name suggests, it includes a complete copy of LTS Haskell 15.4, but it takes some poking around in the image to find it.

$ docker run --rm -it fpco/stack-build:lts-15.4 sh

In this shell, you can find the Stack installation in /home/stackage/.stack; pointing the STACK_ROOT environment variable at that directory will make the stack command find it. That avoids the need to download ghc and the rest of the LTS Haskell environment again on rebuild. Once you've done that, the rest of your Dockerfile works pretty much as you've shown.

That leaves us with the final Dockerfile:

FROM fpco/stack-build:lts-15.4 as haskell

# Tell `stack` where to find its content (not in $HOME)
ENV STACK_ROOT /home/stackage/.stack

WORKDIR /opt/build
COPY Main.hs .
RUN stack ghc --package process -- Main.hs

# Switch Ubuntu back to Alpine
FROM alpine:latest

# Add the libraries we need to run the application
RUN apk add libc6-compat gmp

COPY --from=haskell /opt/build/Main /Main
CMD ["/Main"]
David Maze
  • 130,717
  • 29
  • 175
  • 215
  • I have a few questions. One, executing this I get "Error relocating /Main: __strdup: symbol not found", so still not exactly working. I'm thinking maybe I should use -optl-static here since it would free me from having to research/guess which package I need to apk add when I add a new dependency. Also keep dependencies to one point of control on line 8 ("RUN stack ...") and not duplicated on line 14 ("RUN apk add ..."). Also STACK_ROOT seems to have fixed the long-running build. But how and why? – mmachenry Mar 19 '20 at 14:49
  • 1
    The big issue in the long-running build is downloading content like ghc again, but that's already in the base image. Setting `$STACK_ROOT` tells Stack where to find the content that it already has. If you repeat the build it just has to recompile your program and not download everything again. – David Maze Mar 19 '20 at 14:53
  • 1
    If the static build works, using it makes sense (and would let you ultimately run a `FROM scratch` image). This isn't something I've played with in the ghc/Stack land much, but it's a pretty well-trodden path for Go applications. – David Maze Mar 19 '20 at 15:01
  • I see thanks for that explanation. It seems weird that that ENV var wouldn't be defaulted to that, doesn't it? Also, I have been experimenting with optl-static. Adding this to my compile line works "RUN stack ghc --package process -- -optl-static -optl-pthread Main.hs" and allows me to drop the apk add. However, I get a *ton* of warnings that I'd like to avoid. For example: "/usr/lib/x86_64-linux-gnu/libm-2.27.a(slowexp-avx.o)(.note.stapsdt+0x60): warning: relocation refers to discarded section" – mmachenry Mar 19 '20 at 15:03
  • But does this actually work for you? Because I'm still getting "strdup: symbol not found" – mmachenry Mar 19 '20 at 15:30
  • 1
    In experimenting with this, the final Dockerfile here built and ran the program from the question (it printed a listing of `/` in the container in a loop). I didn't try it with a static binary or a `FROM scratch` image. – David Maze Mar 19 '20 at 23:08
  • Very odd. When I use your Dockerfile and my Main.hs in a directory, do "docker build -t foo ." and then "docker run foo", I get "Error relocating /Main: __strdup: symbol not found" – mmachenry Mar 20 '20 at 02:34