Can't Run Multi-Stage Build on M1

I’m trying to run this multi-stage image on my MacBook Air M1:

FROM python:3 AS builder

WORKDIR /app
COPY . .

RUN curl -sSL https://install.python-poetry.org | python3 - && \
    PATH="/root/.local/bin:$PATH" && \
    poetry config virtualenvs.in-project true && \
    poetry config virtualenvs.options.always-copy true && \
    poetry install --without dev

FROM gcr.io/distroless/static-debian12

WORKDIR /app
COPY --from=builder /app /app

ENTRYPOINT ["/app/.venv/bin/python", "-m", "my_python_project"]

but when I docker run it, I always see the error:

exec /app/.venv/bin/python: no such file or directory

I used dive to make sure, that the executable is in the location I’m expecting it to. So I thought it might be related to platform issues. Running docker image inspect on the both python:3 and gcr.io/distroless/static-debian12 returned, that both of them are built for linux/arm64; although the python image wasn’t using the v8 variant, which the distroless image does. I’ve tried building and running the image with --platform=linux/arm64/v8, but the issue is still the same.

What has happened here and how can I solve this?

I don’t use poetry, but isn’t python just a symbolic link to something like /usr/local/bin/python?
If it is the case, you need to find a way to make the virtualenv portable or need to have python installed in the last stage as well.

Thanks for the response! That’s what the poetry config virtualenvs.options.always-copy true is for.

Here is the project as context, if you want to try it out:

I copied the content out from the stopped container and copied only app back into a python 3 container. Then I used readelf to see the dependencies:

readelf -d python

Output

Dynamic section at offset 0xfdb8 contains 28 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libpython3.12.so.1.0]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000001d (RUNPATH)            Library runpath: [$ORIGIN/../lib]
 0x000000000000000c (INIT)               0x788
 0x000000000000000d (FINI)               0x964
 0x0000000000000019 (INIT_ARRAY)         0x1fda8
 0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)
 0x000000000000001a (FINI_ARRAY)         0x1fdb0
 0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
 0x000000006ffffef5 (GNU_HASH)           0x298
 0x0000000000000005 (STRTAB)             0x4e0
 0x0000000000000006 (SYMTAB)             0x2e8
 0x000000000000000a (STRSZ)              276 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000003 (PLTGOT)             0x1ffe8
 0x0000000000000002 (PLTRELSZ)           120 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x710
 0x0000000000000007 (RELA)               0x650
 0x0000000000000008 (RELASZ)             192 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000006ffffffb (FLAGS_1)            Flags: PIE
 0x000000006ffffffe (VERNEED)            0x620
 0x000000006fffffff (VERNEEDNUM)         1
 0x000000006ffffff0 (VERSYM)             0x5f4
 0x000000006ffffff9 (RELACOUNT)          4
 0x0000000000000000 (NULL)               0x0

libpython3.12.so.1.0 and libc.so.6 are indeed in the python:3 image but not in the final image. So I guess even if you copy the binaries, the shared objects are still required.

Here is where I found in the Python image:

/usr/lib/aarch64-linux-gnu/libc.so.6
/usr/local/lib/libpython3.12.so.1.0

Then I tried to copy these to the distroless image, but didn’t help, so I wanted to check a normal distro image and replaced the original FROM instruction with

FROM debian:12

Then I tried to run the container from the image again and got this:

/app/.venv/bin/python: error while loading shared libraries: libpython3.12.so.1.0: cannot open shared object file: No such file or directory

So indeed this is the problem. Try to solve it with the debian:12 image without using apt. When it works, switch back to the distroless. Or if you use apt, do it interactively and use docker diff to see the changes in the container. Or… if you already use Dive, you could probably see the new layers as well.