statically-linked binaries (docker, ldc)

Suppose you'd like to run some D software on a CentOS 6 box. CentOS 6 has been past EOL for a long while, so you'll find that none of dlang.org's compilers can run out of the box on it, and moreover the binaries you build on some other newer system will fail to start on the CentOS 6 box after spewing GLIBC version errors. Fun. There are ways to deal with this (like finding a suitably old version of dmd to start with) but it's still not pleasant. A reasonable alternative is building a completely static binary with Alpine Linux and the LDC compiler that's available for it.

If you have an Alpine Linux server set up, great: go set up a build environment it. For that matter static compilation isn't a unique feature of Alpine Linux and you may already be able to do it just by adding a --static to your build. In any case you can use Docker to spin up an Alpine Linux environment just long enough to build your D program for you.

Get the example files from static-binaries.

Step 1: get docker

Getting docker running at all can be frustrating, but there are at least online guides for it. This docker-ce guide worked for me. Whether you use docker-ce or some other product, you need to get it to the point that sudo docker run hello-world prints out "Hello from Docker!" and some tutorial text.

Step 2: set up a Docker image for static D builds

This requires a Dockerfile of just two lines:

FROM alpine
RUN apk add ldc dub alpine-sdk openssl-dev zlib-dev llvm-libunwind-static

With that in the current directory, you can create an image 'dstatic', version 1.0, with

sudo docker build -t dstatic:1.0 .

(this is 'make docker' with the example files)

Step 3: static hello world

With a 'hi.d' containing a D hello-world program, you can use this image to compile it into a statically linked executable with this long command:

sudo docker run --rm -it -v $(pwd):/workspace -w /workspace dstatic:1.0 ldc2 -O -release --static hi.d

(this is 'make hi' with the example files)

Some key points of that:

  1. -v $(pwd):/workspace -- this bind-mounts the current directory as /workspace in the image, making the current directory available in the image and carrying any changes to /workspace in the image back to the current directory.
  2. -w /workspace -- this sets the working directory of the following command to /workspace
  3. --rm -- this removes the container as soon as the build is done, so that your next build with dstatic can start from a clean slate.
  4. --static -- this flag, to LDC, makes it actually produce a statically-linked binary, rather than one that's dynamically linked with Alpine Linux's musl libc.

After that command runs, a binary named 'hi' will be in the current directory:

$ file hi
hi: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, with debug_info, not stripped
$ ldd hi
        not a dynamic executable
$ ./hi
Hello, world!

If you're not following along that closely and got an error at this point like "Error: failed to locate cc", you installed ldc but not other Alpine Linux packages that are required or it to actually be able to build a binary. Check the RUN apk command above, or add at least 'gcc musl-dev' on top of ldc.

Step 4: static dub build

dub's available in the image, and a hack to get it to build a static executable is just to run it with DFLAGS set, like so:

sudo docker run --rm -it -v $(pwd):/workspace -w /workspace dstatic:1.0 sh -c 'DFLAGS="-O -release --static" dub build -y getr; find ~/.dub -name getr -type f -executable -exec cp -pv {} . \;'

And... there's obviously a lot more going on here. This is just two commands, one to run dub with a DFLAGS with --static, and another to grab the executable (buried under a path like ~/.dub/packages/getr-0.1.2/getr/getr) and place it in the working directory of the command - i.e., /workspace, i.e., the directory that you ran this docker command in.

After that command runs, a binary named 'getr' will be in the current directory, also not a dynamic executable, but this time built with dub.