How to Package .NET Lambda Functions Using AWS CDK

How to Package .NET Lambda Functions Using AWS CDK

Two ways to build, publish, and package .NET Lambda Functions using AWS CDK

If you don't use tools regularly, you never get really good at them. This is especially true for me with the AWS CDK. I rarely have the chance or need to work with it and have even less motivation to dive deep into it.

That said, my CDK stacks are usually small, typically consisting of just one (.NET) Lambda function and a few other constructs.

How to compile and package a .NET Lambda function—or better, a .NET Lambda function project—when executing a stack is well described in this blog post from AWS, so I will not go into the details here.

Briefly summarized, when applying the stack:

  • A Docker container is started using a specified AWS Lambda .NET runtime image.

  • The project directory is mounted.

  • An archive of the build is created via dotnet lambda package.

Finally, the function archive is uploaded to AWS Lambda.

Here is a sample code for a function construct that uses a Docker build as the code source.

var lambdaBundlingOptions = new BundlingOptions
            {
                Image = Runtime.DOTNET_6.BundlingImage,
                User = "root",
                OutputType = BundlingOutput.ARCHIVED,
                Command = new string[]
                {
                    "/bin/sh",
                    "-c",
                    " dotnet tool install -g Amazon.Lambda.Tools" +
                    " && dotnet build" +
                    " && dotnet lambda package --output-package /asset-output/function.zip"
                }
            };

var myFunction = new Function(this, "MyFunctionId", new FunctionProps
            {
                Runtime = Runtime.DOTNET_6,
                FunctionName = "my-function",
                Handler = "MyFunction::MyFunction.Function::FunctionHandler",
                Code = Code.FromAsset("src/MyLambdaFunction/src/MyLambdaFunction", new AssetOptions
                {
                    Bundling = lambdaBundlingOptions
                })
            });

This works well, but it takes some time, at least on my development workstation (maybe I need an upgrade). Recently, I had to manage not just one or two Lambda functions but a set of five, and whenever I ran a cdk deploy, it felt like I was going on vacation.

I understand why this is the recommended approach for building and packaging .NET Lambda functions. You don't need additional tools, especially if you have to build stacks with multiple functions using different programming languages.

In my case, I only use .NET. My CDK stack is written in C#, and so are my functions. I already have the necessary tools, including the dotnet lambda tools, on my machine. So, I started looking for an alternative way to package a Lambda function locally without using a Docker container to speed everything up.

Fortunately, besides Code.FromAsset, which allows you to specify a ZIP archive directly or use a Docker build and package as described above, the CDK also lets you create the function asset from a custom command using Code.FromCustomCommand.

FromCustomCommand takes three parameters:

  • output - the path to the output of the custom command. This can be a directory, which is then bundled into an archive, or a ZIP archive directly.

  • command - a string array with the command elements that produce the output.

  • options - additional options to configure the spawned process executing the command.

My modified function construct looks like this:

var myFunction = new Function(this, "MyFunctionId", new FunctionProps
            {
                Runtime = Runtime.DOTNET_6,
                FunctionName = "my-function",
                Handler = "MyFunction::MyFunction.Function::FunctionHandler",
                Code = Code.FromCustomCommand("src/MyFunction/dist/function.zip",
                    new string[]
                    {
                        "dotnet-lambda package -pl src/MyFunction/src/MyFunction -o src/MyFunction/dist/function.zip"
                    },
                    new CustomCommandOptions
                    {
                        CommandOptions = new Dictionary<string, object> {{"shell", true }}
                    })
            });

This modification runs dotnet-lambda package in the project directory (where the .csproj file is located). It builds, publishes, and packages the Lambda function into the output directory, ready for uploading.

💡
The CustomCommandOptions "shell": true took me while to find. If not specified cdk deploy will error out.

cdk deploy runs much faster now with the local build and package. Additionally, the cdk-out directory is no longer cluttered with generated Docker-based builds.

Thank you for reading. I hope you enjoyed it and that I've explained the important parts clearly. If not, please let me know 😊 Your feedback is greatly appreciated.

Follow me on LinkedIn to receive notifications whenever I publish something new.