Why use static builds?
Usually, a program is compiled to use shared objects - precompiled blobs of code that are maintained elsewhere. This is incredibly useful sometimes. Plenty of tools are dynamically linked (that is, they call out to shared objects) with OpenSSL. This enables a program to use all of the functionality contained in the linked object while being independently updated when needed.
Security updates for OpenSSL will be transparently applied to all of the software which is dynamically linked to it, so it can be simple to apply patches across a system using dynamic linking. But what if your program requires a library that is not already installed on a system? This is one scenario where static linking might be useful. If your program needs to be deployed in places where bundling other libraries is not an option, you could compile your tool statically. When it is run, everything needed for execution will already be available.
This is a common situation in penetration testing, where you wish to remain stealthy and installing extra packages so your custom tool can run on a target would reveal your presence. Or you might not have administrative privileges, so you could not install packages even if you wanted to.
Why use Golang?
Any compiled language could be statically linked, but some languages make this easier than others. Among the easiest is Golang where builds are static by default.
However, Go programs can use inline C through the cgo
package. If you use this feature, your build might not be static anymore. With the Dockerfile below, you
can compile just about any Go program statically even with cgo
. This is an adaptation and simplified version of this blog post, designed to make the process as straightforward
as possible.
The Dockerfile
Here is our Dockerfile:
FROM golang:alpine
RUN apk --no-cache add musl linux-headers git gcc build-base
CMD ["/bin/sh"]
To use it, we only need to run two commands. Assuming your Go source code is in the same directory as the Dockerfile, and you are currently in that directory:
user@host:~$ docker build -t builder .
user@host:~$ docker run --rm -v "$PWD":/usr/src/myapp -w /usr/src/myapp builder \
go get -d -v ./... && \
go build --ldflags '-w -s -linkmode external -extldflags "-static"' -asmflags -trimpath -v
Now you will have myapp
in the current directory. This is a totally statically compiled executable, ready for deployment. You can even run it inside a
Docker container with no other content. Here is an example Dockerfile:
FROM scratch
COPY myapp /myapp
CMD ["/myapp"]
Which can be built and launched:
user@host:~$ docker build -t myapp .
user@host:~$ docker run myapp
So there you have it. Totally static builds using Golang, compatible with cgo
and deployed in an instant.