Mayfly can ship your function without an Erlang runtime (mayfly: [layer: true])
and take ERTS from a Lambda layer instead. The zip then holds only BEAM files
(1–5 MB), builds on any operating system in seconds, and needs no Docker.
How it works
- The layer unpacks to
/opt/erlangand contains a full Erlang/OTP compiled on Amazon Linux 2023 (crypto/ssllinked against Lambda'slibcrypto.so.3). - The release is assembled with
include_erts: false; itsbootstrapprepends/opt/erlang/bintoPATHand, before starting the VM, checks that anerlexists and that the layer's ERTS version equals the one the release was built with (releases/start_erl.data). Mismatches fail fast with a clear message instead of an obscure boot error. - Elixir itself (
elixir,logger,eex, …) is part of your release, so any Elixir version that supports the layer's OTP major works.
The OTP version must match exactly (Mix releases without ERTS require the same ERTS version on the target). Pin your toolchain to the layer's OTP:
mise use erlang@27.3.4.18 elixir@1.18.4-otp-27 # or asdf / .tool-versions
Naming
| Layer name | Meaning |
|---|---|
mayfly-erlang-27-3-4-18-arm64 | pinned OTP 27.3.4.18 build; new versions only for rebuilds of the same OTP |
mayfly-erlang-27-arm64 | alias; its newest version is always the latest published 27.x patch |
Layer versions are immutable, so pin the full ARN (with version number) in
your infrastructure and bump it deliberately. The layer description contains
the OTP version and the first 16 hex digits of the zip's SHA-256; the full
checksums are attached to the GitHub release layers.
Public layers
Public layers are published by the Layers workflow for the latest patch release of each supported OTP major, both architectures, in these regions:
eu-central-1 eu-west-1 eu-west-2 eu-west-3 eu-north-1 us-east-1 us-east-2 us-west-1 us-west-2 ca-central-1 sa-east-1 ap-southeast-1 ap-southeast-2 ap-northeast-1 ap-northeast-2 ap-south-1
Browse them at elixir-aws-lambda.dev/layers (every OTP version × region × architecture, with copy buttons). The same data is available as static JSON:
GET https://elixir-aws-lambda.dev/layers/27/arm64/eu-central-1.json # newest 27.x
GET https://elixir-aws-lambda.dev/layers/27.3.4.18/arm64/eu-central-1.json # pinned
GET https://elixir-aws-lambda.dev/layers/index.json # everythingmix lambda.doctor queries the resolver for your local OTP, --arch and
--region and prints the matching ARN. Checksums (.sha256) and the raw
ARNS.md/arns.json are attached to the
layers release.
Missing a region or OTP version? Open an issue, or run the workflow on your fork with your own account.
Publishing to your own account
layer/build.sh --otp "$(layer/latest-otp.sh 27)" --arch arm64 # docker or finch
layer/publish.sh --region eu-central-1 --region eu-west-1 # prints the ARN table
layer/publish.sh --org o-abc123 # share with your AWS Organization
layer/publish.sh --public # share with everyone
layer/publish.sh --skip-existing is idempotent and safe to re-run. Building
the non-native architecture locally runs under QEMU and is slow; the GitHub
workflow uses native runners for both.
Automation
.github/workflows/layers.yml runs weekly and on demand:
layer/latest-otp.shresolves the newest patch of each configured major (vars.LAYERS_OTP_MAJORS, default27 28).- A matrix job builds each version natively on
ubuntu-24.04(x86_64) andubuntu-24.04-arm(arm64) and smoke-testssslon a clean AL2023 image. - A publish job assumes an IAM role via GitHub OIDC
(
secrets.LAYERS_ROLE_ARN, template inlayer/publisher-role.yml), publishes to all regions (vars.LAYERS_REGIONS), grants public access and attachesARNS.md,arns.jsonand checksums to thelayersrelease. - With
secrets.WEBSITE_DEPLOY_KEYset, it mergesarns.jsoninto the website repository'sdata/layers.json; Cloudflare Pages rebuilds and the catalog and resolver at/layers/reflect the new versions within minutes.
Set it up once in the publishing account:
aws cloudformation deploy --stack-name mayfly-layers-publisher \
--template-file layer/publisher-role.yml --capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides GitHubRepo=<owner>/<repo>
# -> Outputs.RoleArn becomes the GitHub secret LAYERS_ROLE_ARN
Security notes
- A layer is code that runs inside your function. Pin ARNs by version, verify the checksum against the release assets if you need to, or publish your own.
- The publishing role can only touch layers named
mayfly-erlang-*in its account and is only assumable from the configured repository and ref. - Layers should live in a long-lived account you control, not in a personal sandbox: functions referencing a deleted layer can no longer be created or updated.