Skip to content

Latest commit

 

History

16 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AWS.Lambda.Runtime

A dependency-free AWS Lambda custom runtime for Elixir.

AWS.Lambda.Runtime implements the Lambda Runtime API directly on top of :inets/:httpc and the OTP-bundled JSON module — no HTTP client, no JSON library, and no other dependency beyond what ships with Erlang/OTP itself. It also wires up the mix release steps needed to package a function for the provided.al2023 runtime.

Installation

Add aws_lambda_runtime to your list of dependencies in mix.exs:

def deps do
  [
    {:aws_lambda_runtime, "~> 0.1.0"}
  ]
end

Usage

The steps below take you from a blank mix new project to a function ready to be packaged for Lambda.

1. Create the function app

mix new hello_function
cd hello_function

2. Add the dependency and wire up releases

Add aws_lambda_runtime to deps/0 in mix.exs:

defp deps do
  [
    {:aws_lambda_runtime, "~> 0.1.0"}
  ]
end

Fetch it:

mix deps.get

Then, inside project/0, declare the release, and add a private releases/0 function that wires up the steps :aws_lambda_runtime needs to turn a plain release into a Lambda-ready package:

def project do
  [
    app: :hello_function,
    version: "0.1.0",
    elixir: "~> 1.18",
    start_permanent: Mix.env() == :prod,
    deps: deps(),
    releases: releases()
  ]
end
defp releases do
  [
    lambda: [
      include_erts: false,
      include_executables_for: [:unix],
      strip_beams: true,
      quiet: true,
      steps: [
        :assemble,
        &AWS.Lambda.Runtime.Release.copy_bootstrap/1,
        &AWS.Lambda.Runtime.Release.copy_release_files/1
      ]
    ]
  ]
end

include_erts: false because ERTS comes from the Lambda layer, see below. copy_bootstrap/1 and copy_release_files/1 run after :assemble and copy the bootstrap entrypoint and the vm.args/env.sh files Lambda's provided.al2023 runtime expects — running them as steps (rather than at project/0 evaluation time) guarantees :aws_lambda_runtime is already compiled and loaded.

3. Write a handler

A handler is any module that implements the AWS.Lambda.Runtime.Handler behaviour — a handle_event/2-shaped function receiving the decoded event and an AWS.Lambda.Runtime.Context struct:

defmodule HelloFunction do
  alias AWS.Lambda.Runtime.Context
  alias AWS.Lambda.Runtime.Handler

  @behaviour Handler

  @impl Handler
  def handler(event, context) do
    {:ok,
     %{
       message: "hello from Elixir on #{:erlang.system_info(:otp_release)}",
       received: event,
       request_id: context.request_id,
       remaining_ms: Context.remaining_time_ms(context)
     }}
  end
end

Point the function's Handler setting at it, e.g. HelloFunction or HelloFunction.handler — see the AWS.Lambda.Runtime.Handler moduledoc for every accepted format.

4. Configure the handler

Set the :aws_lambda_runtime, :handler config to the module from step 3, in config/config.exs:

import Config

config :aws_lambda_runtime, handler: HelloFunction

5. Build and deploy

mix aws_lambda.build 29.0.5 1.20.3 --output .

builds the release inside the prebuilt lambda-layer-elixir Docker image — for the given Erlang and Elixir versions — and downloads the resulting function.zip to the current directory, ready to be uploaded as a Lambda function package. Building inside the image guarantees the release matches the target provided.al2023 runtime regardless of your local OS/architecture. See mix help aws_lambda.build for the full list of options (--release, --platform, --dep, ...).

6. Push to AWS

Create the function once, referencing the OTP layer published above:

PLATFORM=arm64 OTP_VERSION=29.0.5; \
aws lambda create-function \
  --function-name elixir-hello \
  --runtime provided.al2023 \
  --architectures "$PLATFORM" \
  --role arn:aws:iam::000000000000:role/lambda-elixir \
  --handler bootstrap \
  --zip-file fileb://function.zip \
  --layers "arn:aws:lambda:eu-west-3:226873539218:layer:erlang-otp-${OTP_VERSION}"

Replace the role ARN, layer ARN/region and account ID with your own. On subsequent deploys, update the function code instead of recreating it:

aws lambda update-function-code --publish --function-name elixir-hello \
  --zip-file fileb://function.zip \
  && aws lambda wait function-updated --function-name elixir-hello

Performance

Rough numbers for the HelloFunction example above, deployed with 256MB of memory:

  • Cold start: ~1.5-2s
  • Execution time: ~2ms
  • Memory used: ~180MB
  • Cost: ~$0.0000068667 per call in us-east-1

The AWS Layer for OTP

Because releases are built with include_erts: false, the function package only ships Elixir and your own code — the Erlang/OTP runtime itself must come from a Lambda layer mounted at /opt/otp, matching the OTP version the release was built against.

Prebuilt layer zips are published as GitHub release assets (see docker/erlang for how they're built). Available releases:

Download the one matching your OTP version and architecture, and publish it as a layer version:

OTP_VERSION=29.0.5 PLATFORM=amd64 AWS_PROFILE=default AWS_REGION=us-east-1; \
curl -fsSL -o layer.zip "https://github.com/GRoguelon/aws_lambda_runtime/releases/download/erlang-${OTP_VERSION}/lambda-layer-erlang-${OTP_VERSION}-${PLATFORM}.zip" \
  && aws lambda publish-layer-version \
       --layer-name "erlang-otp-${OTP_VERSION}" \
       --zip-file fileb://layer.zip \
       --compatible-architectures "$PLATFORM" \
       --compatible-runtimes provided.al2023 \
  && rm layer.zip

PLATFORM is amd64 or arm64 (matching Lambda's own architecture names) and must correspond to the architecture your function is built/deployed for. OTP_VERSION must match the version your release was compiled against.

Trademarks

"AWS", "Amazon Web Services", and "AWS Lambda" are trademarks of Amazon.com, Inc. or its affiliates. This project is an independent, community-maintained library and is not affiliated with, endorsed by, or sponsored by Amazon.com, Inc. or its affiliates.

About

A dependency-free AWS Lambda custom runtime for Elixir, built on :inets/:httpc and the OTP-bundled JSON module, meant to run on a Lambda layer providing OTP.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages