When building an Elixir application docker image on an arm64 platform, the process crashes with a variety of errors:
- Segmentation fault
- Error code 139
- Error code 143
- Memory request of some enormous size
The issue is related to the interaction between the Erlang JIT compiler introduced in OTP25, and QEMU virtualization. In short, QEMU does not detect a scenario where code gets replaced by JIT.
The Dockerfile should include this line before any mix operations:
ENV ERL_FLAGS="+JPperf true"This has the effect of disabling the double-memory mapping of the JIT-generated code, which works around
the QEMU's behavior and fixes the crash. This also includes the Linux perf support by providing the dump and map symbols
in separate files under tmp/ directory: jit-*.dump and perf-*.map. The whole tmp/ dir can be omitted when creating
the image by including it in .dockerignore file, so it does not affect the resulting image size.
Dockerfile
...
FROM elixir:1.14.1-alpine as builder
ARG MIX_ENV
ENV MIX_ENV=$MIX_ENV
# Sets the ERL_FLAGS conditionally when the DOCKER_DEFAULT_PLATFORM is present.
ARG DOCKER_DEFAULT_PLATFORM
ENV ERL_FLAGS=${DOCKER_DEFAULT_PLATFORM:+'+JPperf true'}
RUN apk add --no-cache \
gcc \
git \
make \
musl-dev
RUN mix local.rebar --force && \
mix local.hex --force
...
docker-compose.yml
...
backend:
image: "backend:${BUILD_VERSION}"
build:
context: .
args:
MIX_ENV: prod
# Substituting with null in case of missing variable to silence the warning
# in the on-demand scenario. Remove the susbtitution in a static setup.
DOCKER_DEFAULT_PLATFORM: ${DOCKER_DEFAULT_PLATFORM-}
...
The DOCKER_DEFAULT_PLATFORM variable can be specified inline or exported in the shell. This way the image
can be built as needed for running locally on arm64, or for deployment to a linux/amd64.
DOCKER_DEFAULT_PLATFORM="linux/amd64" docker compose build backend