🪰 Mayfly
View Source
A lightweight AWS Lambda Custom Runtime for Elixir
  Why Mayfly?
Mayfly lets you leverage the full power of Elixir in AWS Lambda without compromise. Run your Elixir code in a serverless environment with:
- Zero Boilerplate - Focus on your business logic, not Lambda implementation details
- Native Elixir Experience - Use the same coding patterns and libraries you love
- Optimized Performance - Designed specifically for Elixir's strengths in AWS Lambda
- Proper Error Handling - Get meaningful stack traces and error reports
Unlike generic custom runtimes, Mayfly is purpose-built for Elixir, bringing the language's reliability and expressiveness to serverless functions.
Quick Start
# 1. Add Mayfly to your dependencies
# In mix.exs
def deps do
[
{:mayfly, github: "bmalum/mayfly"}
]
end
# 2. Create your handler function
defmodule MyFunction do
def handle(event) do
{:ok, %{message: "Hello from Elixir!", event: event}}
end
end
# 3. Build and deploy
# Terminal
$ mix lambda.build --zip
# Upload lambda.zip to AWS Lambda and set handler to "Elixir.MyFunction.handle"Table of Contents
- Features
- Requirements
- Installation
- Usage
- Documentation
- Configuration
- Error Handling
- Deployment
- Performance
- Troubleshooting
- Roadmap
- Community & Contributing
- License
Features
- Full Elixir Support: Run any Elixir code in AWS Lambda, including GenServers and OTP applications
- Simple API: Clean, idiomatic interface for Lambda functions with minimal boilerplate
- Robust Error Handling: Get meaningful error reports with proper Elixir stacktraces
- Build Tooling: Mix tasks for packaging and deployment with Docker and local build support
- Flexible Integration: Works seamlessly with API Gateway, S3, EventBridge and other AWS services
Requirements
- Elixir ~> 1.15
- AWS Account with Lambda access
- Mix
Installation
Add Mayfly to your dependencies in mix.exs:
def deps do
[
{:mayfly, github: "bmalum/mayfly"}
]
endRun mix deps.get to fetch the dependency.
Usage
Creating a Lambda Function
Define your handler function
Create a module with a function that accepts a map and returns
{:ok, result}or{:error, reason}:defmodule MyLambda do def handle(payload) do # Process the payload {:ok, %{message: "Hello from Elixir!", received: payload}} end endBuild your Lambda package
mix lambda.build --zipDeploy to AWS Lambda
- Create a new Lambda function in the AWS Console
- Select "Custom runtime" as the runtime
- Upload the generated
lambda.zipfile - Set the handler environment variable to
Elixir.MyLambda.handle
Advanced Examples
API Gateway Integration
When integrating with API Gateway, structure your response like this:
defmodule Api.Handler do
def process(event) do
{:ok, %{
statusCode: 200,
headers: %{
"Content-Type" => "application/json"
},
body: Jason.encode!(%{
message: "Hello from Elixir!",
path: event["path"],
method: event["httpMethod"]
})
}}
end
endError Handling
defmodule Example.WithError do
def handle(%{"should_fail" => true}) do
{:error, "Requested failure"}
end
def handle(%{"raise_error" => true}) do
raise "Demonstrating error handling"
end
def handle(payload) do
{:ok, %{status: "success", payload: payload}}
end
endHandling Binary Data
defmodule ImageGenerator do
def generate(payload) do
# Generate image data
image_data = create_image(payload)
{:ok, %{
isBase64Encoded: true,
body: Base.encode64(image_data),
headers: %{"Content-Type" => "image/png"}
}}
end
defp create_image(payload) do
# Implementation details...
end
endDocumentation
Online Documentation
Full documentation with guides and API reference is available at elixir-aws-lambda.dev/docs
Quick links:
- Getting Started Guide - Step-by-step tutorial for your first Lambda function
- Deployment Guide - Advanced deployment scenarios and best practices
- API Reference - Complete module and function documentation
Generate Documentation Locally
Generate and view the documentation on your machine:
mix docs
open doc/index.html
Configuration
Environment Variables
_HANDLER: Required - The Elixir function to call, in the formatElixir.Module.functionAWS_LAMBDA_RUNTIME_API: Automatically set by AWS LambdaLOGLEVEL: Optional - Set todebugfor more verbose logging
Build Options
The lambda.build mix task supports the following options:
--zip: Create a ZIP file for deployment--outdir: Specify the output directory (default: current directory)--docker: Build using Docker (useful for cross-platform compatibility)
Example:
mix lambda.build --zip --docker
Custom Docker Build Environment
You can provide your own lambda.Dockerfile in your project root to customize the build environment. This is useful when:
- Your dependencies require specific system libraries
- You need a different Erlang/Elixir version
- You want to add native dependencies for NIFs
Create a lambda.Dockerfile in your project root:
FROM amazonlinux:2023
# Add your custom system dependencies
RUN yum install -y imagemagick-devel libxml2-devel
# Install Erlang/Elixir (customize versions as needed)
RUN yum install -y wget tar gcc make && \
wget https://github.com/erlang/otp/releases/download/OTP-27.2/otp_src_27.2.tar.gz && \
tar -zxf otp_src_27.2.tar.gz && \
cd otp_src_27.2 && \
./configure --without-javac && \
make -j$(nproc) && make install && \
cd / && rm -rf otp_src_27.2*
ENV MIX_ENV=lambda
WORKDIR /mnt/code
RUN mix local.rebar --force && mix local.hex --forceIf no lambda.Dockerfile is found in your project, Mayfly will use the default one from the library.
Error Handling
Mayfly provides standardized error handling for Lambda functions:
Return
{:error, reason}: For expected errorsdef handle(payload) do case validate(payload) do :ok -> {:ok, process(payload)} {:error, reason} -> {:error, reason} end endRaise an exception: For unexpected errors
def handle(payload) do # This will be caught and formatted properly result = payload["a"] + payload["b"] {:ok, %{sum: result}} end
Errors are formatted according to the AWS Lambda error response format with proper stacktraces.
Deployment
Detailed Deployment Steps
Build the Lambda package:
mix lambda.build --zipCreate a new Lambda function:
- Open the AWS Lambda Console
- Click "Create function"
- Choose "Author from scratch"
- Name your function
- Select "Custom runtime" for Runtime
- Create or select an execution role
- Click "Create function"
Upload the deployment package:
- In the Function code section, click "Upload from"
- Select ".zip file"
- Upload the generated
lambda.zipfile - Click "Save"
Configure the handler:
- In the Runtime settings section, click "Edit"
- Set the Handler to
Elixir.YourModule.function_name - Click "Save"
Test your function:
- Click "Test"
- Configure a test event
- Click "Test" to invoke your function
Performance
Optimizing Cold Starts
- Increase memory allocation (which also increases CPU power)
- Minimize dependencies in your application
- Consider provisioned concurrency for critical functions
Memory and Timeout Configuration
- Start with at least 512MB of memory for reasonable performance
- Adjust timeout based on your function's processing needs
- Monitor execution times to fine-tune these settings
Troubleshooting
Common Issues
- Handler Not Found: Ensure the
_HANDLERenvironment variable is correctly set toElixir.Module.function - Timeout Errors: Check if your function exceeds the Lambda timeout limit
- Memory Issues: Increase the Lambda memory allocation if needed
- Cold Start Performance: Consider increasing the memory allocation to improve cold start times
Debugging Tips
- Set
LOGLEVELenvironment variable todebugfor verbose logging - Review CloudWatch logs for detailed error information
- Test locally before deployment when possible
Roadmap
- [ ] Build with Docker Image for x86/arm64
- [ ] Build Locally
- [ ] Create ZIP File
- [ ] CDK Sample
- [ ] HexDocs and Hex.pm publishing
- [ ] GitHub Actions CI/CD templates
- [ ] Performance benchmarks and optimizations
- [ ] Framework integrations (Phoenix, etc.)
Community & Contributing
We welcome contributions of all kinds! Here's how you can help:
- Bug Reports: Open an issue describing the bug and how to reproduce it
- Feature Requests: Open an issue describing the desired feature
- Code Contributions: Submit a pull request with your changes
- Documentation: Help improve or translate documentation
- Examples: Share how you're using Mayfly in your projects
Before contributing, please review our:
- Code of Conduct (link)
- Contributing Guidelines (link)
License
Mayfly is licensed under the MIT License. See the LICENSE file for details.