# Erlang runtime layers

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/erlang` and contains a full Erlang/OTP compiled
  on Amazon Linux 2023 (`crypto`/`ssl` linked against Lambda's `libcrypto.so.3`).
- The release is assembled with `include_erts: false`; its `bootstrap`
  prepends `/opt/erlang/bin` to `PATH` and, before starting the VM, checks that
  an `erl` exists 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:

```bash
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](../.github/workflows/layers.yml)
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](https://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                          # everything
```

`mix 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](https://github.com/bmalum/mayfly/releases/tag/layers).

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

```bash
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:

1. `layer/latest-otp.sh` resolves the newest patch of each configured major
   (`vars.LAYERS_OTP_MAJORS`, default `27 28`).
2. A matrix job builds each version natively on `ubuntu-24.04` (x86_64) and
   `ubuntu-24.04-arm` (arm64) and smoke-tests `ssl` on a clean AL2023 image.
3. A publish job assumes an IAM role via GitHub OIDC
   (`secrets.LAYERS_ROLE_ARN`, template in `layer/publisher-role.yml`),
   publishes to all regions (`vars.LAYERS_REGIONS`), grants public access and
   attaches `ARNS.md`, `arns.json` and checksums to the `layers` release.
4. With `secrets.WEBSITE_DEPLOY_KEY` set, it merges `arns.json` into the
   website repository's `data/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:

```bash
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.
