Mayfly: Bringing Elixir to AWS Lambda

A lightweight AWS Lambda Custom Runtime that brings the full power of Elixir to serverless functions.

I’m excited to announce Mayfly - a custom AWS Lambda runtime designed specifically for Elixir. Mayfly makes it simple to run Elixir code serverless without compromise.

🌐 Visit elixir-aws-lambda.dev for complete documentation, examples, and guides.

Why Mayfly?

  • Zero Boilerplate: Focus on your business logic, not Lambda implementation details
  • Native Elixir: Use standard {:ok, result} / {:error, reason} patterns
  • OTP Support: Run GenServers and full OTP applications in Lambda
  • Proper Error Handling: Get meaningful Elixir stacktraces in Lambda responses

Quick Start

# 1. Add to dependencies
def deps do
  [{:mayfly, github: "bmalum/mayfly"}]
end

# 2. Create handler
defmodule MyFunction do
  def handle(event) do
    {:ok, %{message: "Hello from Elixir!", event: event}}
  end
end

# 3. Build and deploy
$ mix lambda.build --zip

API Gateway Example

defmodule MyApp.ApiHandler do
  def handle(event) do
    case {event["httpMethod"], event["path"]} do
      {"GET", "/users"} ->
        {:ok, %{
          statusCode: 200,
          body: Jason.encode!(%{users: fetch_users()})
        }}
      
      {"POST", "/users"} ->
        body = Jason.decode!(event["body"])
        {:ok, %{
          statusCode: 201,
          body: Jason.encode!(create_user(body))
        }}
      
      _ ->
        {:ok, %{statusCode: 404, body: Jason.encode!(%{error: "Not found"})}}
    end
  end
end

Custom Docker Images

For production deployments or when you need specific system dependencies, you can build custom Docker images:

FROM public.ecr.aws/lambda/provided:al2023

# Install Erlang/OTP and Elixir
RUN dnf install -y tar gzip && \
    curl -fSL https://github.com/erlang/otp/releases/download/OTP-26.2.1/otp_src_26.2.1.tar.gz -o otp.tar.gz && \
    tar -xzf otp.tar.gz && cd otp_src_26.2.1 && \
    ./configure && make && make install && \
    cd .. && rm -rf otp_src_26.2.1 otp.tar.gz

RUN curl -fSL https://github.com/elixir-lang/elixir/releases/download/v1.16.0/elixir-otp-26.zip -o elixir.zip && \
    unzip elixir.zip -d /usr/local && rm elixir.zip

# Copy your Lambda function
COPY _build/lambda/rel/my_function /var/task
COPY bootstrap /var/runtime/bootstrap

CMD ["Elixir.MyFunction.handle"]

Build and deploy:

# Build release
MIX_ENV=lambda mix release

# Build Docker image
docker build -t my-elixir-lambda .

# Push to ECR and deploy
aws ecr get-login-password | docker login --username AWS --password-stdin $ECR_REGISTRY
docker tag my-elixir-lambda:latest $ECR_REGISTRY/my-elixir-lambda:latest
docker push $ECR_REGISTRY/my-elixir-lambda:latest

This approach gives you full control over the runtime environment and allows you to include native dependencies or custom configurations.

Build Options

# Local build
mix lambda.build --zip

# Docker build (cross-platform)
mix lambda.build --docker --zip

# Custom output directory
mix lambda.build --zip --outdir ./deploy

Event Source Support

Mayfly works with all AWS event sources:

S3 Events:

def handle(%{"Records" => records}) do
  Enum.each(records, fn record ->
    bucket = get_in(record, ["s3", "bucket", "name"])
    key = get_in(record, ["s3", "object", "key"])
    process_s3_object(bucket, key)
  end)
  {:ok, %{processed: length(records)}}
end

EventBridge:

def handle(%{"detail-type" => type, "detail" => detail}) do
  case type do
    "Order Placed" -> process_order(detail)
    "User Registered" -> process_registration(detail)
    _ -> {:ok, %{status: "ignored"}}
  end
end

Documentation

Complete documentation at elixir-aws-lambda.dev :

Try It Out

git clone https://github.com/bmalum/mayfly.git
cd mayfly
mix docs

Why “Mayfly”?

Mayflies are ephemeral creatures with short lifespans - some live just hours. Like Lambda functions, they’re lightweight, purpose-built, and designed for brief but important tasks.


Links:

Tags: #elixir #lambda #aws #serverless #otp