This document describes how to build a statically linked binary of Elm 0.19.0 for Linux x64 using docker. The binary is built using Alpine Linux in order to easily link it statically to musl libc. This is how the official Elm 0.19.0 Linux binary was built.
Elm is currently distributed using npm
. For Linux x64 (but this applies to any architecture), this requires to have a single x64 binary that works on all Linux x64 distributions. This is considerably easier to achieve by building a statically linked binary that will only depend on the Linux kernel ABI and System Call Interface but not on userpace libraries (see here for a compatibility survey of a dynamically built executable).
Docker allows to automate and reproduce the build on any system that supports docker without creating some dependencies with the host system (in our case mainly the C libraries needed by elm, and particularly the libc). This lowers the requirements to rebuild elm, improves the builds reliability and allows to manage the whole build procedure in a version control system.
Glibc is not really suitable for static linking as it uses some dynamically loaded name resolution libraries that complicate static linking considerably (see this FAQ and NSS documentation for more information).
Alpine Linux is a very small Linux distribution particularly suitable for Continuous Integration images that uses the musl libc instead of glibc. The musl libc, defined on its homepage as "lightweight, fast, simple, free, and strives to be correct in the sense of standards-conformance and safe", is a nice alternative to glibc, particularly for static linking.
Follow the procedure adapted to your platform.
For Ubuntu x64 (tested on 18.04):
$ sudo apt-get update
$ sudo apt-get install apt-transport-https ca-certificates curl software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
$ sudo apt-get install docker-ce
$ sudo systemctl start docker
Then optionaly (but recommanded):
- add your user to the docker group to avoid using
sudo
to run thedocker
command:
$ sudo usermod -aG docker $USER
This will take effect after you logout/login (recommanded), or you can run su - $USER
for it to take effect without re-logging, but only in the curent shell.
- configure docker to start at boot:
$ sudo systemctl enable docker
Maybe one day the docker build script could be included in elm sources but for now you have to copy/paste it manually.
Create an empty directory named for example elm-docker
(naming is not important) and add a Dockerfile
file in it (naming is important) with the following content:
# We use Alpine 3.7 that includes ghc 8.0.2 as at the time of writing, the last
# Alpine version 3.8 includes ghc 8.4 that is not yet supported by the `language-glsl`
# haskell library used by elm.
FROM alpine:3.7
# Install required packages
RUN apk add --update ghc cabal git musl-dev zlib-dev ncurses-dev ncurses-static
# Checkout elm compiler (using 0.19.0 tag)
WORKDIR /tmp
RUN git clone -b 0.19.0 https://github.com/elm/compiler.git
# Build a statically linked elm binary
WORKDIR /tmp/compiler
RUN cabal update
RUN cabal sandbox init
RUN cabal install --only-dependencies
RUN cabal configure --disable-executable-dynamic --ghc-option=-optl=-static --ghc-option=-optl=-pthread
# For yet unknown reasons, compilation may fail when using several jobs at once
RUN cabal build --jobs=1
Take note of the comment above the git command. You can change some git options there if you want to use another elm tag/commit/branch.
In the directory containing the Dockerfile
, run:
$ docker build -t elm .
The -t elm
option is used to name the docker image "elm"
, which will be useful to refer to it later.
The steps automatically executed are:
- fetch and run the Alpine Linux image inside a container
- install the Alpine packages required to build elm
- build the haskell libraries required to build elm
- build elm
If this goes well, this should end after a few minutes with something like:
Linking dist/build/elm/elm ...
Removing intermediate container ec629eeec5a1
---> c2a967867158
Successfully built c2a967867158
Successfully tagged elm:latest
Your new image should now be listed when running docker images
in addition to the Alpine one used as our basis, for example:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
elm latest c2a967867158 2 hours ago 1.75GB
alpine 3.7 791c3e2ebfcb 5 weeks ago 4.2MB
Note that this image is not optimized for Continuous Integration of software written in elm as it includes all dependencies needed to build elm itself. We could make an image a lot smaller for this other purpose.
You can now retrieve the statically linked elm
binary from the docker image.
As the elm compiler repository has been checked out in the /tmp
image directory and built there, you can copy the elm
binary from the image container to the current directory using:
$ docker run elm cat /tmp/compiler/dist/build/elm/elm > elm
You can then add execution permissions and try the binary:
$ chmod +x ./elm
$ ./elm repl
Note that the elm
binary is not stripped during the build to ease debug. It could be stripped later with strip -s elm
to reduce its size before distributing it (removing symbols from the object file).
To rebuild another version of elm manually, run the image inside a container:
$ docker run -it --rm elm /bin/ash
This opens a shell inside the container in /tmp/compiler
, and you can then run some git commands (to checkout a commit/tag/branch for example) and rebuild elm if you want, for example:
/tmp/compiler# git pull
/tmp/compiler# cabal build
To retrieve the elm binary from a running container (from another shell), first get the container name from the host:
$ docker ps
Example:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7c4680e00158 elm "/bin/ash" About a minute ago Up About a minute laughing_poitras
^^^^^^^^^^^^^^^^
then, still from the host:
$ docker cp CONTAINER_NAME:/tmp/compiler/dist/build/elm/elm .
For example:
$ docker cp laughing_poitras:/tmp/compiler/dist/build/elm/elm .
Important: If you exit the shell inside the container, all your modifications will be lost. Therefore retrieve the files you want before exiting the shell (or learn how to commit your changes into a new image).
0.19.1 version is here.