Getting Started with AWS Lambda in F#


I recently set out to create my first AWS Lambda function. Of the supported languages, I chose my favorite: F#. In this post, I’ll walk through the process of building a Lambda function in F# and deploying it to AWS.

## AWS Alphabet Soup
Many of Amazon’s web services have [famously inscrutable][aws-plain-english] names. After fumbling my way through the process of creating a function in the web UI ([AWS Console]), I retraced my steps in the command line to produce denser, more easily repeatable instructions:

The first step is to install [AWS CLI], which is available as `awscli` via Homebrew and pip.

Next, we’ll need user credentials, which brings us to our first acronym: IAM, or _Identity and Access Management_. Visit the [IAM Console], create a non-root user if you don’t already have one, and then create an access key for it. Provide them to AWS CLI with `aws configure` ([documentation][aws-cli-configuration]). This should be the last time we need the web UI.

Before we continue, I’ll offer one tip upfront: When you’re referencing a local file, AWS CLI requires you to [prefix it]( with `file://`. Weird.

### Role and Policy
Time for the next two pieces of jargon: Our Lambda function will need a [role] with an associated [policy].

The role creation subcommand (`aws iam create-role`) unfortunately requires you to provide a magic JSON blob in a file on disk. Avoid the hassle with a one-liner like this:

echo '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":""},"Action":"sts:AssumeRole"}]}' | aws iam create-role --role-name my-lambda-role --assume-role-policy-document file:///dev/stdin

(I acquired that JSON by creating a role in the UI and then fetching it with `aws iam get-role`.)

From the output, find the line that looks like this:
`”Arn”: “arn:aws:iam::676570827806:role/my-lambda-role”,`

Time for another acronym! An [ARN] is an _Amazon Resource Name_, which is just an ID that you can unambiguously reference. Save it for later.

Next up, we’ll associate an existing [managed policy] with the role we created like this:

aws iam attach-role-policy --role-name my-lambda-role --policy-arn arn:aws:iam::aws:policy/AWSLambdaExecute

I chose `AWSLambdaExecute` arbitrarily for this toy project; check out the other available policies in the web portal to govern your function’s access to your various other AWS resources.

## Creating the Function Project
With the AWS prerequisites wrapped up, it’s time for some code.

### Tooling
I haven’t tried Lambda’s other [supported languages], but for .NET languages, Amazon has created tooling that integrates nicely with the language’s ecosystem.

  1. First, install the latest [.NET Core SDK]( if you don’t already have it.
  2. Next, install the Lambda project templates:
    `dotnet new -i Amazon.Lambda.Templates`
  3. Finally, install the Lambda tools:
    `dotnet tool install -g Amazon.Lambda.Tools`.

### New Project
Scaffold a basic Lambda project with `dotnet new lambda.EmptyFunction -lang f#`. There are a handful of other Lambda templates available; see them with `dotnet new -l`.

The template doesn’t include a solution file. Create one with `dotnet new sln`, then associate the generated projects with `dotnet sln add **/*.fsproj`.

Take a look at the generated code (I recommend [VS Code][vs-code-fsharp] or [JetBrains Rider]). At the time of writing, the generated function uppercases an input string:

    member __.FunctionHandler (input: string) (_: ILambdaContext) =
        match input with
        | null -> String.Empty
        | _ -> input.ToUpper()

I think this is a [bug][template-issue]. To work nicely with the provided tooling, change the parameter to a [record type]:

type Input = { foo : string }

type Function() =
    member __.FunctionHandler (input: Input) (_: ILambdaContext) =
        match with
        | null -> String.Empty
        | s -> s.ToUpper()

## Deployment and Testing
Now we’re ready to deploy it! Thanks to the tools we installed above, deployment is as simple as `dotnet lambda deploy-function my-lambda`. It may ask for a region (e.g. `us-east-2`) and a profile ARN (from above).

Once it’s deployed, you can invoke your Lambda with test input like this:

jrr@jrrmbp ~/r/g/g/s/my-lambda (master) [255]> dotnet lambda invoke-function my-lambda --payload '{"foo":"jkl"}'
Amazon Lambda Tools for .NET Core applications (3.0.1)
Project Home:,
Log Tail:
START RequestId: 8305ef96-bef1-11e8-9704-9957a282268a Version: $LATEST
END RequestId: 8305ef96-bef1-11e8-9704-9957a282268a
REPORT RequestId: 8305ef96-bef1-11e8-9704-9957a282268a	Duration: 601.54 ms	Billed Duration: 700 ms 	Memory Size: 256 MB	Max Memory Used: 61 MB

Billed for 700ms! Not bad.

You can also send test data to it from the [web interface][Lambda Console], as well as view logs, etc.

## Impressions
After getting past the one-time [administrivia] in AWS, I was impressed by the developer tooling on offer. I appreciated having F# templates, something Microsoft’s own Azure Functions [lack](https:/azure-functions-f-sharp/). The `dotnet lambda` global tool was good to see, especially since global tools have only existed for a [few months](

I haven’t done anything meaningful with Lambda yet, such as wiring up input [triggers]( or producing output side effects. But so far, I like what I see!

## Further Reading

  • [What’s the AWS IoT Suite Like? – A Dozen Lego Kits Mixed Together](https:/aws-iot-component-overview/)
  • [Amazon Lambda Auto-Deployment For Your Alexa Skill Using AWS CLI](https:/alexa-aws-deployment/)
  • [Managing AWS CloudFront Security Group with AWS Lambda](https:/aws-cloudfront-security-group-lambda/)

[aws cli]:
[aws console]:
[IAM Console]:
[managed policy]:
[jetbrains rider]:
[supported languages]:
[record type]:
[Lambda Console]:

  • Eff Sharper says:

    Interesting–is Atomic Object using F# in any of its professional services or was this a personal fun project?

    • John Ruble John Ruble says:

      Yes 😉

      This post was from a personal fun project, but we also use F# on client projects.

  • Comments are closed.