Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Static linking with Alpine image vs. dynamic linking with Debian bullseye image #453

Closed
tim-hilt opened this issue Feb 21, 2023 · 8 comments

Comments

@tim-hilt
Copy link

Hi there! I recently came across a strange issue, when trying to build a Go binary with the official golang:1.20 / golang:1.20.0-bullseye image. I wanted to run the image with distroless/static-debian11, but on execution, I only saw the message file not found.

The error can be reproduced with the following Dockerfile (found in this repository: https://github.com/tim-hilt/debug-go-distroless):

# Builder
FROM golang:1.20 AS builder
WORKDIR /go/src/app
COPY . .
RUN go build -ldflags="-w -s"

# Runner
FROM gcr.io/distroless/static-debian11
COPY --from=builder /go/src/app/app /
CMD ["/app"]

full reproduction:

git clone git@github.com:tim-hilt/debug-go-distroless.git
cd debug-go-distroless
docker build -t test . && docker run test

Strangely I could execute the binary if I built the image with golang:1.20-alpine, which is counter-intuitive, since the distroless-image as well as golang:1.20 are based on Debian 11.

I opened up an issue on the distroless-repository (GoogleContainerTools/distroless#1227). One maintainer pointed out, that the binary was statically linked on Alpine, but dynamically linked when built on Debian.

How is this possible?

@tianon
Copy link
Member

tianon commented Feb 22, 2023

If you run ldd on the binary after you build it (in both cases - using the appropriate ldd from the relevant build environment), what's the result?

@tianon
Copy link
Member

tianon commented Feb 22, 2023

My hunch is that in Alpine, it's default to disabling CGO due to having no C compiler, so it generates a more portable executable than it does in Debian where it can default to cgo. The next test would be to add ENV CGO_ENABLED=0 to both builds and see if that changes your result.

@tianon
Copy link
Member

tianon commented Feb 22, 2023

Also, in the future, these sorts of questions/requests would be more appropriately posted to a dedicated support forum, such as the Docker Community Slack, Server Fault, Unix & Linux, or Stack Overflow.

@tim-hilt
Copy link
Author

For golang:1.20:

ldd debug-go-distroless
	linux-vdso.so.1 (0x00007ffd4794a000)
	libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007ff7d8ab5000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007ff7d8a93000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff7d88be000)
	/lib64/ld-linux-x86-64.so.2 (0x00007ff7d8ad5000)

For golang:1.20-alpine:

ldd debug-go-distroless
    /lib/ld-musl-x86_64.so.1: debug-go-distroless: Not a valid dynamic program

I don't really know about linking, but I assume that the binary was statically linked in the case of alpine.

If I use the following Dockerflie:

FROM golang:1.20 AS builder
WORKDIR /go/src/app
ENV CGO_ENABLED=0
COPY . .
RUN go build -ldflags="-w -s"

It links statically either way. Thanks for the hints!

@tim-hilt
Copy link
Author

I'm sorry if this was the wrong place to ask for help. I generally know if I should file a bug or ask on a support-channel. In this case though, I was sure that this is a bug.

It's not intuitive to figure out that go build links differently if a certain executable is found on $PATH. I never saw that in any language before and I've worked with quite a few.

@atc0005
Copy link

atc0005 commented Feb 22, 2023

@tim-hilt See also:

@wader
Copy link

wader commented Feb 23, 2023

@tim-hilt Just a note about ldd. I've seen some versions that don't understand so called "static PIE" binaries which i think alpine's toolchain can produce, those binaries are dynamic but has no external dependencies

@tim-hilt
Copy link
Author

Thanks for the heads up @atc0005 @wader.

In my use-case the binaries that were compiled on alpine work. Making static building explicit via a flag to go build would make muuuch more sense though. I just subscribed to the issue!

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants