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.
# 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"- Features
- Requirements
- Installation
- Usage
- Documentation
- Configuration
- Error Handling
- Deployment
- Performance
- Troubleshooting
- Roadmap
- Community & Contributing
- License
- 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
- Elixir ~> 1.15
- AWS Account with Lambda access
- Mix
Add Mayfly to your dependencies in mix.exs:
def deps do
[
{:mayfly, github: "bmalum/mayfly"}
]
endRun mix deps.get to fetch the dependency.
-
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 end
-
Build your Lambda package
mix lambda.build --zip
-
Deploy 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
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
enddefmodule 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
enddefmodule 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
endFull 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 and view the documentation on your machine:
mix docs
open doc/index.html_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
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 --dockerYou 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.
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 end
-
Raise 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.
-
Build the Lambda package:
mix lambda.build --zip
-
Create 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
- Increase memory allocation (which also increases CPU power)
- Minimize dependencies in your application
- Consider provisioned concurrency for critical functions
- 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
- 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
- Set
LOGLEVELenvironment variable todebugfor verbose logging - Review CloudWatch logs for detailed error information
- Test locally before deployment when possible
- 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.)
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)
Mayfly is licensed under the MIT License. See the LICENSE file for details.
