Good way of installing specific Cabal and GHC inside a Docker container?

I have written a Haskell program that runs with a very specific version of Cabal and GHC. What is the best way to distribute it within a Docker container?

I am trying the following, but the build process seems to freeze at the step “Installing GHC (this may take a while)”

# Use the official Alpine Linux image
FROM alpine:latest

# Install necessary dependencies
RUN apk update && apk add --no-cache \
    bash curl git make m4 gcc g++ ocaml \
    ocaml-compiler-libs opam findutils \
    linux-headers gmp-dev \
    binutils-gold libc-dev libffi-dev musl-dev ncurses-dev perl tar xz

# download and install ghcup
RUN curl -L https://downloads.haskell.org/~ghcup/x86_64-linux-ghcup -o /usr/bin/ghcup && \
    chmod +x /usr/bin/ghcup

ENV PATH="$HOME/.cabal/bin:$HOME/.ghcup/bin:$PATH"

RUN ghcup install cabal 3.10.2.1 && \
    ghcup install ghc 9.4.8 && \
    ghcup set 9.4.8
2 Likes

This Dockerfile builds without issue for me. The GHC installation step takes some time; perhaps it works for you as well if you give it more time.

$ docker buildx build -f Dockerfile .
[+] Building 124.9s (8/8) FINISHED                                              docker:default
 => [internal] load build definition from Dockerfile                                      0.0s
 => => transferring dockerfile: 644B                                                      0.0s
 => WARN: UndefinedVar: Usage of undefined variable '$HOME' (line 15)                     0.0s
 => [internal] load metadata for docker.io/library/alpine:latest                          0.0s
 => [internal] load .dockerignore                                                         0.0s
 => => transferring context: 2B                                                           0.0s
 => [1/4] FROM docker.io/library/alpine:latest                                            0.0s
 => [2/4] RUN apk update && apk add --no-cache     bash curl git make m4 gcc g++ ocaml   24.5s
 => [3/4] RUN curl -L https://downloads.haskell.org/~ghcup/x86_64-linux-ghcup -o /usr/bi  2.5s
 => [4/4] RUN ghcup install cabal 3.10.2.1 &&     ghcup install ghc 9.4.8 &&     ghcup   93.7s
 => exporting to image                                                                    4.2s
 => => exporting layers                                                                   4.2s
 => => writing image sha256:5938b1bfe833439201af140accc3d902b4cd3d828c4dea4cfebe7722da1c  0.0s

 1 warning found (use --debug to expand):
 - UndefinedVar: Usage of undefined variable '$HOME' (line 15)

If your goal is to distribute the specific versions of GHC and Cabal, then this should be fine. Given that you are using Alpine, you may want to check out benz0li/ghc-musl for inspiration. You may also want to check out the Dockerfile in one of my projects, which does some things to reduce the size of the image.

If your goal is to distribute your program, then you may want to configure a multi-stage build and copy only your program and its dependencies into the final image.

I don’t really understand the purpose of the ghc-musl images.

Both GHC and GHCup support alpine linux.

If you’re worried about space, ghcup gc --help has some tricks.

1 Like

I don’t really understand the purpose of the ghc-musl images.

I think that ghc-musl images are convenient for use in software that integrates with Docker. For example, VS Code Dev Containers and Stack’s Docker integration are easy to use with Docker containers.

@benz0li can probably explain the purpose better, however.

Both GHC and GHCup support alpine linux.

Thank you very much for your work on GHCup. I enjoy using it in general, and the working Alpine builds are great!

If you’re worried about space, ghcup gc --help has some tricks.

Indeed. The Dockerfile that I linked to does the following:

  • Disables apk cache
  • Removes unneeded *_p.a and *.p_hi files
  • Uses ghcup gc
  • Removes ghcup, as any further GHCup usage warrants building a new image
1 Like

It appears that ghc-musl builds GHC from source: latest.Dockerfile · main · Olivier Benz / GHC musl · GitLab

And it applies patches: patches/9.8.2.patch · main · Olivier Benz / GHC musl · GitLab

But these patches are not explained. Where do they come from?

Not from alpine it seems: ghc « community - aports - Alpine packages build scripts

I deleted the containers/images which were lingering on my machine and tried rebuilding. I am getting the following error this time:

➜  alexee git:(main) ✗ sudo docker build --progress=plain -t alexee .
#0 building with "desktop-linux" instance using docker driver

#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 2.18kB done
#1 DONE 0.0s

#2 [internal] load metadata for docker.io/library/alpine:latest
#2 DONE 0.0s

#3 [internal] load .dockerignore
#3 transferring context: 2B done
#3 DONE 0.0s

#4 [1/4] FROM docker.io/library/alpine:latest
#4 DONE 0.0s

#5 [2/4] RUN apk update && apk add --no-cache     bash curl git make m4 gcc g++ ocaml     ocaml-compiler-libs opam findutils     linux-headers gmp-dev     binutils-gold libc-dev libffi-dev musl-dev ncurses-dev perl tar xz
#5 CACHED

#6 [3/4] RUN curl -L https://downloads.haskell.org/~ghcup/x86_64-linux-ghcup -o /usr/bin/ghcup &&     chmod +x /usr/bin/ghcup
#6 CACHED

#7 [4/4] RUN ghcup install cabal 3.10.2.1 &&     ghcup install ghc 9.4.8 &&     ghcup set 9.4.8
#7 0.840 [ Info  ] downloading: https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-0.0.8.yaml as file /root/.ghcup/cache/ghcup-0.0.8.yaml
#7 0.907   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
#7 0.907                                  Dload  Upload   Total   Spent    Left  Speed
100  454k  100  454k    0     0  2639k      0 --:--:-- --:--:-- --:--:-- 2644k
#7 1.232 [ Info  ] downloading: https://downloads.haskell.org/~ghcup/unofficial-bindists/cabal/3.10.2.1/cabal-install-3.10.2.1-x86_64-linux-alpine319.tar.xz as file /root/.ghcup/tmp/ghcup-363b9854eb378800/cabal-install-3.10.2.1-x86_64-linux-alpine319.tar.xz
#7 1.237   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
#7 1.237                                  Dload  Upload   Total   Spent    Left  Speed
100 7543k  100 7543k    0     0  22.3M      0 --:--:-- --:--:-- --:--:-- 22.3M
#7 1.575 [ Info  ] verifying digest of: cabal-install-3.10.2.1-x86_64-linux-alpine319.tar.xz
#7 1.629 [ Info  ] Unpacking: cabal-install-3.10.2.1-x86_64-linux-alpine319.tar.xz to /root/.ghcup/tmp/ghcup-469a8ee054f91903
#7 2.207 [ Info  ] Installing cabal
#7 2.254 [ Info  ] Cabal installation successful
#7 2.254 [ Info  ] cabal run is currently partially broken on Windows, please see https://github.com/haskell/cabal/issues/9334
#7 2.416 [ Warn  ] New cabal version available. If you want to install this latest version, run 'ghcup install cabal 3.12.1.0'
#7 2.423 [ Info  ] downloading: https://downloads.haskell.org/~ghc/9.4.8/ghc-9.4.8-x86_64-alpine3_12-linux.tar.xz as file /root/.ghcup/tmp/ghcup-f378398765729087/ghc-9.4.8-x86_64-alpine3_12-linux.tar.xz
#7 2.429   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
#7 2.429                                  Dload  Upload   Total   Spent    Left  Speed
100  202M  100  202M    0     0  26.6M      0  0:00:07  0:00:07 --:--:-- 28.4M
#7 10.01 [ Info  ] verifying digest of: ghc-9.4.8-x86_64-alpine3_12-linux.tar.xz
#7 10.98 [ Info  ] Unpacking: ghc-9.4.8-x86_64-alpine3_12-linux.tar.xz to /root/.ghcup/tmp/ghcup-7f0c7cf4e3a8c8bf
#7 28.46 [ Info  ] Installing GHC (this may take a while)

#7 29.86 
#7 29.86 
#7 29.86 
#7 29.86 
#7 29.86 
#7 51.57 [ Error ] [GHCup-00841] Process "gmake" with arguments ["DESTDIR=/root/.ghcup/tmp/ghcup-b8f43f0690cb0035",
#7 51.57 [ ...   ]                                 "install"] failed with exit code 2.
#7 51.57 [ Error ] Also check the logs in /root/.ghcup/logs
#7 ERROR: process "/bin/sh -c ghcup install cabal 3.10.2.1 &&     ghcup install ghc 9.4.8 &&     ghcup set 9.4.8" did not complete successfully: exit code: 3
------
 > [4/4] RUN ghcup install cabal 3.10.2.1 &&     ghcup install ghc 9.4.8 &&     ghcup set 9.4.8:
28.46 

29.86 
29.86 
29.86 
29.86 
29.86 
51.57 [ Error ] [GHCup-00841] Process "gmake" with arguments ["DESTDIR=/root/.ghcup/tmp/ghcup-b8f43f0690cb0035",
51.57 [ ...   ]                                 "install"] failed with exit code 2.
51.57 [ Error ] Also check the logs in /root/.ghcup/logs
------
Dockerfile:17
--------------------
  16 |     
  17 | >>> RUN ghcup install cabal 3.10.2.1 && \
  18 | >>>     ghcup install ghc 9.4.8 && \
  19 | >>>     ghcup set 9.4.8
  20 |     
--------------------
ERROR: failed to solve: process "/bin/sh -c ghcup install cabal 3.10.2.1 &&     ghcup install ghc 9.4.8 &&     ghcup set 9.4.8" did not complete successfully: exit code: 3

View build details: docker-desktop://dashboard/build/desktop-linux/desktop-linux/tsdhj1ikzwp4k995pqm42up61

These are the contents of ghc-make.log

278b3e61038f:~/.ghcup/logs# tail ghc-make.log 
# The rts package doesn't actually supply haddocks, so we stop advertising them
# altogether.
((echo "system-cxx-std-lib" | grep rts) && (cat '/root/.ghcup/tmp/ghcup-89fc5029c36a1a48/root/.ghcup/ghc/9.4.8/lib/ghc-9.4.8/lib/package.conf.d/system-cxx-std-lib-1.0.conf.copy' | sed 's|haddock-.*||' > '/root/.ghcup/tmp/ghcup-89fc5029c36a1a48/root/.ghcup/ghc/9.4.8/lib/ghc-9.4.8/lib/package.conf.d/system-cxx-std-lib-1.0.conf.copy.copy')) || (cat '/root/.ghcup/tmp/ghcup-89fc5029c36a1a48/root/.ghcup/ghc/9.4.8/lib/ghc-9.4.8/lib/package.conf.d/system-cxx-std-lib-1.0.conf.copy' > '/root/.ghcup/tmp/ghcup-89fc5029c36a1a48/root/.ghcup/ghc/9.4.8/lib/ghc-9.4.8/lib/package.conf.d/system-cxx-std-lib-1.0.conf.copy.copy')
# We finally replace the original file.
mv '/root/.ghcup/tmp/ghcup-89fc5029c36a1a48/root/.ghcup/ghc/9.4.8/lib/ghc-9.4.8/lib/package.conf.d/system-cxx-std-lib-1.0.conf.copy.copy' '/root/.ghcup/tmp/ghcup-89fc5029c36a1a48/root/.ghcup/ghc/9.4.8/lib/ghc-9.4.8/lib/package.conf.d/system-cxx-std-lib-1.0.conf'
# Fix the mode, in case umask is set
chmod 644 '/root/.ghcup/tmp/ghcup-89fc5029c36a1a48/root/.ghcup/ghc/9.4.8/lib/ghc-9.4.8/lib/package.conf.d/system-cxx-std-lib-1.0.conf'
'/root/.ghcup/tmp/ghcup-89fc5029c36a1a48/root/.ghcup/ghc/9.4.8/lib/ghc-9.4.8/bin/ghc-pkg' --global-package-db "/root/.ghcup/tmp/ghcup-89fc5029c36a1a48/root/.ghcup/ghc/9.4.8/lib/ghc-9.4.8/lib/package.conf.d" recache
rosetta error: failed to open elf at /lib/ld-musl-x86_64.so.1
 gmake: *** [Makefile:232: update_package_db] Trace/breakpoint trap
1 Like

I see that it is a rosetta error, which is particular to your macOS architecture. (I built on an amd64 Linux system.)

The following search query may provide some clues: “rosetta” error: failed to open elf at /lib/ld-musl-x86_64.so.1

Here is a relevant Docker issue: Support for running x86-64 binaries with Rosetta 2

The multi-arch (linux/amd64, linux/arm64/v8) quay.io/benz0li/ghc-musl images were created as a replacement for the utdemir/ghc-musl images (linux/amd64 only) to build the statically linked Linux amd64 and arm64 binary releases of Pandoc.
:information_source: Back in 2021 there were no Linux arm64 bindists of GHC available.

Long story short: Dev Containers added to Stack repository - #6 by benz0li.

P.S: These images are also used to build the statically linked Linux amd64 and arm64 binary releases of Stack. See also

2 Likes

Correct. Furthermore:

  1. Multi‑arch: linux/amd64, linux/arm64/v8
  2. Built using Hadrian[^1], from source, without docs
  3. Built using the LLVM backend
    • flavour: perf+llvm+split_sections

[^1]: GHC versions ≥ 9.2.8.

I added a comment to the Dockerfiles now: Add comment regarding patch (eb4a21a6) · Commits · Olivier Benz / GHC musl · GitLab

No.

2 Likes

The GHC musl repository also includes Dev Container configurations providing the following tools:

:information_source: See the Version Matrix for the available combinations of GHC, Cabal (the tool), Stack and HLS.

Thanks. I think it’s important to always document the motivation and sources of downstream patches. Most distros do that.

It may be useful to add something of that to the README and also point out that alpine bindists are now officially produced as well.

1 Like

:+1:

It is mentioned here: README.md · eb4a21a677d56c160eee6573402ec0412d46f819 · Olivier Benz / GHC musl · GitLab

The reasons I still maintain GHC musl:

My reason for asking is that I tried to use the GHC project’s own GHC 9.8.2 for Alpine Linux 3.18/AArch64 in an Alpine Linux 3.19.1 VM on macOS/AArch64 to build a statically-linked Stack, but it fell over.

I was hoping that the official Alpine Linux[/AArch64] releases would work correctly – and make my project obsolete.

As long as this is not the case I will continue to maintain GitHub - benz0li/ghc-musl: Unofficial binary distributions of GHC on Alpine Linux. Multi-arch (linux/amd64, linux/arm64/v8) GHC musl docker images. Please submit Pull Requests to the GitLab repository. Mirror of.

Alternatives to a self-hosted runner to build statically-linked Stack for Linux/AArch64? · Issue #6531 · commercialhaskell/stack · GitHub

(Migrate repository to haskell org? · Issue #6252 · commercialhaskell/stack · GitHub)


The Docker “Official Image” for haskell does not provide ‘Alpine Linux’-based images (including LLVM).

Yes, GHCup already uses --disable-ld-override on alpine: ghcup-hs/lib/GHCup/GHC.hs at d8410edde2c5914eeb96f1840238956cb7d8d7c2 · haskell/ghcup-hs · GitHub

We went so far to consider that option for all platforms: Disable GHCs aggressive selection of `ld.gold` · Issue #1032 · haskell/ghcup-hs · GitHub