BEAM docker release GitHub action - a GitHub action to build a docker container from a BEAM (Elixir/Erlang) release

What is it?

The BEAM docker release GitHub action builds a tiny from scratch container as part of your GitHub workflow. With a minimal attack surface, of tens rather than hundreds of megabytes. We are building containers not virtual machines here!

It supports:

Hello World! Simple examples for both Elixir and Erlang can be found at:

Only the release and required runtime shared libraries are present. There is no shell, or any executable other than those required to run the BEAM.

Some real examples:

The parameters used in the BEAM docker release GitHub action can be found here. Some of the ideas are described further in this article with the dependency copying script now written as an escript to support both Erlang/OTP and Elixir. This article looks at using Elixir with the BEAM docker release action.

What doesn’t work right now?

  • Multi-platform. linux/arm64 crashes and burns with a qemu uncaught signal. It should work, and I would really like it to work! I would appreciate any collaboration into why. Hoping that it is something simple that I’ve done wrong. linux/arm/v7 works fine…
  • Other BEAM languages should be reasonably simple to support providing they use relx, with some updates to the dependency copying script.

Regards,
Peter.

9 Likes

This looks awesome, thanks for sharing!

I think that this comes from the fact that qemu doesn’t handle dual mapping which the erlang JIT relies on. You can try exporting the env var ERL_AFLAGS="+JPperf true" during the build (enabling perf has a side effect of disabling the dual mapping), on master you could use +JMsingle true instead (see https://github.com/erlang/otp/pull/6340)

4 Likes

Thank you @asabil! That did it!

I’ve updated the build platform default to linux/amd64 and linux/arm64, so that a multi-platform build is created. I’ve updated the HelloWorlds on Elixir and Erlang with multi-platform builds.

I’ve run the HelloWorld container on an arm64 Raspberry PI. It would be great to hear whether it works on Apple Silicon too.

3 Likes

this is very inconvenient for debugging. Do I understand you correctly that main idea of this inconvenience is reducing amount of things that can be attacked?

1 Like

very strange. Our flussonic is being built for arm64 with cross-compile mechanism and docker buildx for creating multiarch image.

What do you use qemu for?

Are you building with OTP25?

OTP25 on a JIT supported platform you’ll need +JPperf true to use a cross compile docker build with QEMU.

For example with erlang:25 as the build image:

docker build --platform linux/arm64 \
                    --progress plain \
                    --tag hwe \
                    --build-arg BUILD_IMAGE=erlang:25 \
                    --build-arg GITHUB_REPOSITORY=shortishly/hwe \
                    --build-arg BUILD_COMMAND="make app rel" .
...
#9 [build 5/6] RUN make app rel
#9 sha256:bd13b2e954c569265bb333ec5cd7edd9a16b0254be86eaa9fbfd55231c6507b2
#9 1.195 qemu: uncaught target signal 11 (Segmentation fault) - core dumped
#9 1.207 Segmentation fault
#9 1.377  DEP    relx (main)

Confusingly (at least to me at the time), linux/arm/v7 will work, but that is because it isn’t supported by the JIT in OTP25.

Whereas with erlang:24 no segmentation faults (but no JIT!):

docker build --platform linux/arm64 \
                    --progress plain \
                    --tag hwe \
                    --build-arg BUILD_IMAGE=erlang:24 \
                    --build-arg GITHUB_REPOSITORY=shortishly/hwe \
                    --build-arg BUILD_COMMAND="make app rel" .
#13 exporting to image
#13 sha256:e8c613e07b0b7ff33893b694f7759a10d42e180f2b4dc349fb57dc6b71dcab00
#13 exporting layers 0.1s done
#13 writing image sha256:a789234df44084f937d80832e615071ed198631a35c04747955159cff4376de5
#13 writing image sha256:a789234df44084f937d80832e615071ed198631a35c04747955159cff4376de5 done
#13 naming to docker.io/library/hwe done
#13 DONE 0.1s
1 Like

What sort of debug do you need?

  • You can use nsenter to run any OS command within the context of the container. They don’t have to be in the container to run them.

  • If you want to run an interactive command line on the running BEAM, then I would look at running a ssh daemon within your application. Or embedding (shameless plug!) something like shelly with your OTP application. That is usually my goto, usually with recon also installed.

Obviously, you also need to instrument your application so that you know what “good” (normal) looks like.

If you are on AWS then SSM integrated with IAM might also make sense. Other cloud providers probably have something similar to SSM.

One man’s inconvenience, is another’s convenience! I generally get the phone call at 3am when log4shell is discovered asking whether “Can you be sure it isn’'t present in the container?”. My motivation is:

  • “right size” containers that can run on low memory/bandwidth/footprint systems
  • no unnecessary dependencies outside of the application (patching, security, bloat… etc).
  • good sleep at night :slight_smile:
5 Likes

No, my team haven’t migrated to OTP25 yet =(

Will check it, thanks

I added the +JPperf true flag to build the Elixir image for the arm64 architecture, and the image size will increase significantly. I deleted the jit-*.dump and perf-*.map files in the /tmp path to reduce the size. But this phenomenon seems to only exist when building Elixir, and does not happen when building Elixir apps.