63

I have an alpine running container which contains some binaries in usr/local/bin

When I ls the content of usr/local/bin I got this output :

/usr/local/bin # ls
dwg2SVG     dwg2dxf     dwgadd      dwgbmp      dwgfilter   dwggrep     dwglayers   dwgread     dwgrewrite  dwgwrite    dxf2dwg     dxfwrite

Which is what I expected. However if I execute one of these binaries by calling it I got a not found error from the shell :

/usr/local/bin # dwg2dxf
sh: dwgread: not found
/usr/local/bin # ./dwg2dxf
sh: ./dwgread: not found

I tested my $PATH which seems to be correct :

/usr/local/bin # echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

How can I make those binaries callable or "foundable" ? Did I miss something in my Dockerfile build ? I suppose that there is something with the ldconfig command in alpine that went wrong but I'm not sure.

EDIT

As suggested in one answer here I executed the file command and here is the output :

/usr/local/bin # file dwg2dxf
dwgread: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=7835d4a42651a5fb7bdfa2bd8a76e40096bacb07, with debug_info, not stripped

Thoses binaries are from the LibreDWG Official repository as well as the first part of my Dockerfile. Here is the complete Dockerfile :

# podman/docker build -t libredwg .
############################
# STEP 1 build package from latest tar.xz
############################

FROM python:3.7.7-buster AS extracting
# libxml2-dev is broken so we need to compile it by our own
ARG LIBXML2VER=2.9.9
RUN apt-get update && \
    apt-get install -y --no-install-recommends autoconf libtool swig texinfo \
            build-essential gcc libxml2 python3-libxml2 libpcre2-dev libpcre2-32-0 curl \
            libperl-dev libxml2-dev && \
    mkdir libxmlInstall && cd libxmlInstall && \
    wget ftp://xmlsoft.org/libxml2/libxml2-$LIBXML2VER.tar.gz && \
    tar xf libxml2-$LIBXML2VER.tar.gz && \
    cd libxml2-$LIBXML2VER/ && \
    ./configure && \
    make && \
    make install && \
    cd /libxmlInstall && \
    rm -rf gg libxml2-$LIBXML2VER.tar.gz libxml2-$LIBXML2VER
WORKDIR /app
RUN tarxz=`curl --silent 'https://ftp.gnu.org/gnu/libredwg/?C=M;O=D' | grep '.tar.xz<' | \
         head -n1|sed -E 's/.*href="([^"]+)".*/\1/'`; \
    echo "latest release $tarxz"; \
    curl --silent --output "$tarxz" https://ftp.gnu.org/gnu/libredwg/$tarxz && \
    mkdir libredwg && \
    tar -C libredwg --xz --strip-components 1 -xf "$tarxz" && \
    rm "$tarxz" && \
    cd libredwg && \
    ./configure --disable-bindings --enable-release && \
    make -j `nproc` && \
    mkdir install && \
    make install DESTDIR="$PWD/install" && \
    make check DOCKER=1 DESTDIR="$PWD/install"

############################
# STEP 2 install into stable-slim
############################

# pull official base image
FROM osgeo/gdal:alpine-normal-latest

# set work directory
WORKDIR /usr/src/app



# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# copy requirements file
COPY ./requirements.txt /usr/src/app/requirements.txt

# install dependencies
RUN set -eux \
    && apk add --no-cache --virtual .build-deps build-base \
        py3-pip libressl-dev libffi-dev gcc musl-dev python3-dev postgresql-dev\
    && pip3 install --upgrade pip setuptools wheel \
    && pip3 install -r /usr/src/app/requirements.txt \
    && rm -rf /root/.cache/pip

# Install libredwg binaries
COPY --from=extracting /app/libredwg/install/usr/local/bin/* /usr/local/bin/
COPY --from=extracting /app/libredwg/install/usr/local/include/* /usr/local/include/
COPY --from=extracting /app/libredwg/install/usr/local/lib/* /usr/local/lib/
COPY --from=extracting /app/libredwg/install/usr/local/share/* /usr/local/share/
RUN ldconfig /usr/local/bin/
RUN ldconfig /usr/local/include/
RUN ldconfig /usr/local/lib/
RUN ldconfig /usr/local/share/

# copy project
COPY . /usr/src/app/
jossefaz
  • 3,312
  • 4
  • 17
  • 40
  • What if you try to execute it with absolute path?: `/usr/local/bin/dwg2dxf` Also, is that `$PATH` exported so that `dwg2dxf` sees it too, as the error is `dwgread: not found` like `dwg2dxf` couldn't find `dwgread` for executing. – James Brown Apr 06 '21 at 05:50
  • @jossefaz : I notice two strange things in your question: First you tag it as bash, but the error message you get does not come from bash. The, you don't test the PATH in connection with the command which you executed. To be on the safe side, I would do a `(printenv PATH; ls -l /usr/local/bin/dwg2dxf; dwg2dxf)`. – user1934428 Apr 06 '21 at 07:05
  • @user1934428 : I updated the question tag. For the second point I'll try and edit ;y question with the outpout. But I think I am close to a workaround by using another base image in my dockerfile... – jossefaz Apr 06 '21 at 08:42
  • Can you edit the question to include the Dockerfile directly, not behind a link? Where do the binaries come from? Switching to an Ubuntu base potentially addresses this kind of issue, or it's possible you're missing some shared-library dependencies that need to be installed. – David Maze Apr 06 '21 at 10:46
  • @DavidMaze : thanks for your comment. I've edited my question the entire Dockefile as well as a link to the official repository those binaries came from. Actually I did change the base image an hour ago and it worked. But it's not an ideal solution for me. But since it's working and since it could help other people, I've post this workaround as a response to my own question...If anyone find any improvement to this workaround feel free to post another answer and I could remove mine – jossefaz Apr 06 '21 at 11:32

6 Answers6

146

On Alpine Linux, the not found error is a typical symptom of dynamic link failure. It is indeed a rather confusing error by musl's ldd linker.

Most of the world Linux software is linked against glibc, the GNU libc library (libc provides the standard C library and POSIX API). Most Linux distributions are based on glibc. OTOH, Alpine Linux is based on the musl libc library, which is a minimal implementation and strictly POSIX compliant. Executables built on glibc distributions depend on /lib/x86_64-linux-gnu/libc.so.6, for example, which is not available on Alpine (unless, they are statically linked).

Except for this dependency, it's important to note that while musl attempts to maintain glibc compatibility to some extent, it is far from being fully compatible, and complex software that's built against glibc won't work with musl-libc, so simply symlinking /lib/ld-musl-x86_64.so.1 to the glibc path isn't likely going to work.

Generally, there are several ways for running glibc binaries on Alpine:

  1. Install one the glibc compatibility packages, libc6-compat or gcompat:
# apk add gcompat
apk add libc6-compat

Both packages provide a light weight glibc compatibility layer which may be suitable for running simple glibc applications. libc6-compat implements glibc compatibility APIs and provides symlinks to glibc shared libraries such as libm.so, libpthread.so and libcrypt.so. The gcompat package is based on Adelie Linux gcompat project and does the same but provides a single library libgcompat.so. Both libraries install loader stubs. Depdending on the application, one of them may work while the other won't, so it's good to try both.

  1. Install proper glibc on Alpine, for providing all glibc methods and functionalities. There are glibc builds available for Alpine, which should be installed in the following procedure (example):
# Source: https://github.com/anapsix/docker-alpine-java

ENV GLIBC_REPO=https://github.com/sgerrand/alpine-pkg-glibc
ENV GLIBC_VERSION=2.30-r0

RUN set -ex && \
    apk --update add libstdc++ curl ca-certificates && \
    for pkg in glibc-${GLIBC_VERSION} glibc-bin-${GLIBC_VERSION}; \
        do curl -sSL ${GLIBC_REPO}/releases/download/${GLIBC_VERSION}/${pkg}.apk -o /tmp/${pkg}.apk; done && \
    apk add --allow-untrusted /tmp/*.apk && \
    rm -v /tmp/*.apk && \
    /usr/glibc-compat/sbin/ldconfig /lib /usr/glibc-compat/lib
  1. Use statically linked executables. Static executables don't carry dynamic dependencies and could run on any Linux.

  2. Alternatively, the software may be built from source on Alpine.

For LibreDWG, let's first verify the issue:

/usr/local/bin # ./dwg2dxf
/bin/sh: ./dwg2dxf: not found
/usr/local/bin
/usr/local/bin # ldd ./dwg2dxf
    /lib64/ld-linux-x86-64.so.2 (0x7fd375538000)
    libredwg.so.0 => /usr/local/lib/libredwg.so.0 (0x7fd3744db000)
    libm.so.6 => /lib64/ld-linux-x86-64.so.2 (0x7fd375538000)
    libc.so.6 => /lib64/ld-linux-x86-64.so.2 (0x7fd375538000)
Error relocating /usr/local/lib/libredwg.so.0: __strcat_chk: symbol not found
Error relocating /usr/local/lib/libredwg.so.0: __snprintf_chk: symbol not found
Error relocating /usr/local/lib/libredwg.so.0: __memcpy_chk: symbol not found
Error relocating /usr/local/lib/libredwg.so.0: __stpcpy_chk: symbol not found
Error relocating /usr/local/lib/libredwg.so.0: __strcpy_chk: symbol not found
Error relocating /usr/local/lib/libredwg.so.0: __printf_chk: symbol not found
Error relocating /usr/local/lib/libredwg.so.0: __fprintf_chk: symbol not found
Error relocating /usr/local/lib/libredwg.so.0: __strncat_chk: symbol not found
Error relocating /usr/local/lib/libredwg.so.0: __sprintf_chk: symbol not found
Error relocating ./dwg2dxf: __snprintf_chk: symbol not found
Error relocating ./dwg2dxf: __printf_chk: symbol not found
Error relocating ./dwg2dxf: __fprintf_chk: symbol not found

You can see that dwg2dxf depends on several glibc symbols. Now, let's follow option 2 for installing glibc:

/usr/src/app # cd /usr/local/bin
/usr/local/bin # ls
dwg2SVG     dwg2dxf     dwgadd      dwgbmp      dwgfilter   dwggrep     dwglayers   dwgread     dwgrewrite  dwgwrite    dxf2dwg     dxfwrite
/usr/local/bin # ./dwg2dxf
/bin/sh: ./dwg2dxf: not found
/usr/local/bin # export GLIBC_REPO=https://github.com/sgerrand/alpine-pkg-glibc && \
> export GLIBC_VERSION=2.30-r0 && \
> apk --update add libstdc++ curl ca-certificates && \
> for pkg in glibc-${GLIBC_VERSION} glibc-bin-${GLIBC_VERSION}; \
>    do curl -sSL ${GLIBC_REPO}/releases/download/${GLIBC_VERSION}/${pkg}.apk -o /tmp/${pkg}.apk; done && \
> apk add --allow-untrusted /tmp/*.apk && \
> rm -v /tmp/*.apk && \
> /usr/glibc-compat/sbin/ldconfig /lib /usr/glibc-compat/lib
fetch https://dl-cdn.alpinelinux.org/alpine/v3.13/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.13/community/x86_64/APKINDEX.tar.gz
(1/1) Installing curl (7.74.0-r1)
Executing busybox-1.32.1-r3.trigger
OK: 629 MiB in 126 packages
(1/2) Installing glibc (2.30-r0)
(2/2) Installing glibc-bin (2.30-r0)
Executing glibc-bin-2.30-r0.trigger
/usr/glibc-compat/sbin/ldconfig: /usr/local/lib/libredwg.so.0 is not a symbolic link
/usr/glibc-compat/sbin/ldconfig: /usr/glibc-compat/lib/ld-linux-x86-64.so.2 is not a symbolic link
OK: 640 MiB in 128 packages
removed '/tmp/glibc-2.30-r0.apk'
removed '/tmp/glibc-bin-2.30-r0.apk'
/usr/glibc-compat/sbin/ldconfig: /usr/glibc-compat/lib/ld-linux-x86-64.so.2 is not a symbolic link

/usr/glibc-compat/sbin/ldconfig: /usr/local/lib/libredwg.so.0 is not a symbolic link

Voila:

/usr/local/bin # ./dwg2dxf

Usage: dwg2dxf [-v[N]] [--as rNNNN] [-m|--minimal] [-b|--binary] DWGFILES...
Leonel Sanches da Silva
  • 6,972
  • 9
  • 46
  • 66
valiano
  • 16,433
  • 7
  • 64
  • 79
  • 1
    So in the end, does the "not found" refer to a symbol the linker tried and failed to find? – Charley Apr 07 '21 at 00:36
  • 4
    @Charley actually it is because the linker failed to find the entire glibc shared object, `/lib64/ld-linux-x86-64.so.2`. In the glibc installation procedure, the library is installed in `/usr/glibc-compat/lib/ld-linux-x86-64.so.2`, and then symlinked to the lib64 location. – valiano Apr 07 '21 at 05:17
  • I came across this problem after building a simple Go / Golang program. Using the following options to build the Go application helped: `CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo src/main.go`. Maybe not all of them are necessary. I found this solution here: https://www.callicoder.com/docker-golang-image-container-example/ – JepZ Jul 13 '21 at 18:53
  • 2
    Thank you sir! Number 2 worked in my case :) – graphicbeacon Feb 11 '22 at 16:43
  • Small side question: why use `ENV` and not `ARG`? As in: `ENV GLIBC_REPO=https://github.com/sgerrand/alpine-pkg-glibc ENV GLIBC_VERSION=2.30-r0` I know this is common but I don't get it. – Seub Mar 27 '23 at 14:49
  • 2
    The reason @JepZ comment worked to build his application (for anyone wondering) is because of `GCO_ENABLED=0` and the `-a` flag. These 2 together tell go to build the application statically; therefore it does not depend on any dynamic libraries. – Speeddymon Mar 31 '23 at 20:57
30

Try apk add gcompat (https://pkgs.alpinelinux.org/package/edge/community/x86/gcompat).

gcompat provides both /lib64/ld-linux-x86-64.so.2 and /lib/ld-linux-x86-64.so.2 (https://pkgs.alpinelinux.org/contents?file=ld-linux-x86-64.so.2).

rdesgroppes
  • 988
  • 12
  • 11
3

EDIT :

For a complete solution, please see the @valiano'response.

Here is just a workaround that I've found before reading the @valiano'response

WORKAROUND

I've found a workaround by switching to another base image (Ubuntu based) Here is the new working Dockerfile :

# podman/docker build -t libredwg .
############################
# STEP 1 build package from latest tar.xz
############################

FROM python:3.7.7-buster AS extracting
# libxml2-dev is broken so we need to compile it by our own
ARG LIBXML2VER=2.9.9
RUN apt-get update && \
    apt-get install -y --no-install-recommends autoconf libtool swig texinfo \
            build-essential gcc libxml2 python3-libxml2 libpcre2-dev libpcre2-32-0 curl \
            libperl-dev libxml2-dev && \
    mkdir libxmlInstall && cd libxmlInstall && \
    wget ftp://xmlsoft.org/libxml2/libxml2-$LIBXML2VER.tar.gz && \
    tar xf libxml2-$LIBXML2VER.tar.gz && \
    cd libxml2-$LIBXML2VER/ && \
    ./configure && \
    make && \
    make install && \
    cd /libxmlInstall && \
    rm -rf gg libxml2-$LIBXML2VER.tar.gz libxml2-$LIBXML2VER
WORKDIR /app
RUN tarxz=`curl --silent 'https://ftp.gnu.org/gnu/libredwg/?C=M;O=D' | grep '.tar.xz<' | \
         head -n1|sed -E 's/.*href="([^"]+)".*/\1/'`; \
    echo "latest release $tarxz"; \
    curl --silent --output "$tarxz" https://ftp.gnu.org/gnu/libredwg/$tarxz && \
    mkdir libredwg && \
    tar -C libredwg --xz --strip-components 1 -xf "$tarxz" && \
    rm "$tarxz" && \
    cd libredwg && \
    ./configure --disable-bindings --enable-release && \
    make -j `nproc` && \
    mkdir install && \
    make install DESTDIR="$PWD/install" && \
    make check DOCKER=1 DESTDIR="$PWD/install"

############################
# STEP 2 install into stable-slim
############################

# pull official base image
FROM osgeo/gdal:ubuntu-small-latest

# set work directory
WORKDIR /usr/src/app



# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# copy requirements file
COPY ./requirements.txt /usr/src/app/requirements.txt

# install dependencies
RUN set -eux \
    && apt-get update && apt-get install -y --no-install-recommends build-essential \
        libc6 python3-pip libffi-dev musl-dev gcc python3-dev postgresql-server-dev-all\
    && pip3 install --upgrade pip setuptools wheel \
    && pip3 install -r /usr/src/app/requirements.txt \
    && rm -rf /root/.cache/pip

# Install libredwg binaries
COPY --from=extracting /app/libredwg/install/usr/local/bin/* /usr/local/bin/
COPY --from=extracting /app/libredwg/install/usr/local/include/* /usr/local/include/
COPY --from=extracting /app/libredwg/install/usr/local/lib/* /usr/local/lib/
COPY --from=extracting /app/libredwg/install/usr/local/share/* /usr/local/share/
RUN ldconfig

# copy project
COPY . /usr/src/app/

Basically I've just changed the

FROM osgeo/gdal:alpine-normal-latest

To

FROM osgeo/gdal:ubuntu-small-latest

I've updated the dependencies installation as well (switching from apk add alpine PM to apt-get)

This is not an ideal solution for me since using an alpine based image generate more lightweight container but it's working so I post it as a possible solution.

The @valiano'response is the optimal solution. Please refer to it if you care about lightweigth images.

jossefaz
  • 3,312
  • 4
  • 17
  • 40
1

I solved it like this:

rm /usr/glibc-compat/lib/ld-linux-x86-64.so.2
ln -s /usr/glibc-compat/sbin/ldconfig /usr/glibc-compat/lib/ld-linux-x86-64.so.2

EDIT: explanation: both /usr/glibc-compat/sbin/ldconfig and /usr/glibc-compat/lib/ld-linux-x86-64.so.2 were normal files in the docker container, so I tried one of them.

First I removed /usr/glibc-compat/sbin/ldconfig and made it a symlink. But I got an error, don't remember which. Next I tried to remove ld-linux-x86-64.so.2 and make it a symlink. That worked.

Koroslak
  • 643
  • 1
  • 6
  • 12
0

It may not be in a binary format you can use on the system in question. Check your architecture and the file format (for example using the file command).

Edit: Does /lib64/ld-linux-x86-64.so.2 exist? Can you run it?

Further edit: The general idea here is that a dynamically linked binary can be thought of as a script with an interpreter. See this LWN article for additional details to understand what may be going on here. If your binaries are for the wrong platform, you will need new binaries or you will need to run them on the proper platform.

Another thing you can check is whether the output of file for this binary differs from the output of file for binaries that are working correctly (e.g. /bin/ls).

Charley
  • 578
  • 3
  • 13
  • Hi and thank you for your answer. I added the output of the `file` command to my question. I am not a OS master, but I cannot see in the output anything that could be not compatible with the alpine image that I use...Maybe you can see any compatibility problem here ? – jossefaz Apr 06 '21 at 05:27
  • You right...the interpreter `/lib64/ld-linux-x86-64.so.2` is `not found` too. How can I add it ? – jossefaz Apr 06 '21 at 05:30
  • I found this thread https://github.com/gliderlabs/docker-alpine/issues/219 ... I am trying different things...If you have any idea please let me know – jossefaz Apr 06 '21 at 05:32
  • Wherever you got those binaries from, you need to recompile them for your current architecture, or compile them statically so that they no longer depend on external files. – tripleee Apr 06 '21 at 06:34
  • @Charley : If the binary format does not fit, i.e. the executable is not loadable or has a bogus magic number, I think you would get a different error message, not just that the executable is not found. – user1934428 Apr 06 '21 at 07:07
-3

Now we can use docker compose to replace docker-compose

For Alpine Linux, install by

apk add docker-cli-compose

If you still prefer docker-compose, you can save a file under /usr/local/bin/docker-compose with a+x:

#!/bin/sh
docker compose $@

Then you still can run the old scripts which use docker-compose.

silencej
  • 221
  • 2
  • 13