AWS Cloud Development Kit can now manage building Lambda functions (such as ones written in Go) within sandboxed Docker containers - that resemble the Lambda execution environment - during CDK synth and deploy! Best of all, you can configure the build process quite easily using BundlingOptions interface within the CDK's Function construct.

Background

In my team at Alma Media we develop “polyglot serverless monorepos”: The project infrastructure is defined with AWS Cloud Development Kit in TypeScript and the project can contain multiple services which in turn can contain multiple Lambda functions that are written in either Go, TypeScript or Python depending on the use case. Go is our preferred language, TypeScript is aimed for Lambda@Edge (and for frontend projects) and Python is great for CloudFormation custom resources & working with data. We like Go, because it performs well in Lambda environment, has a relatively “small” syntax, it's opinionated and since you can easily output a single binary out of it (vs. NodeJS Lambda functions where you have to use tools like Webpack or Parcel to optimize the deployment package).

So far, we have built these Lambda functions by utilizing docker-lambda with our own Makefile-based setup. It has worked “okay” - at least considering we've built multiple production setups with it - but it has had its problems, so I've wished to have something else, more native to CDK. I know, I know, you can build Lambda functions defined in CDK with SAM CLI, but even that is a bit challenging at times. I even had started building cdk-docker-lambda CDK construct myself that would manage this, but I started to feel that there must be another way (than to reinvent the wheel myself). By the way, the illustration used in this blog post was actually logo I originally made for that project.

Reason why we like to build all our Lambda functions inside Docker containers is that there are minimal dependencies required for your host machine, CI-server AND if you happen to use native dependencies, it's best practise to build them against an environment that resembles the real Lambda execution environment.

CDK has had support for bundling NodeJS & Python Lambda function code via experimental NodejsFunction and PythonFunction constructs, but since we mainly write our Lambda functions with Go, we were still out of luck. This led me to ask Google DuckDuckGo if there happens to be anything similar in development for Go; No luck for GoFunction (at least yet), but then I found this Github issue and lo & behold, CDK Lambda module's Function construct can take BundlingOptions into its code-property!!! 🤩

Using CDK BundlingOptions

Support for bundling Lambda function assets was released in CDK v1.45.0 during June 2020, but I had totally missed it at the time and given that I couldn't find that much information about it besides the CDK issues and BundlingOptions interface documentation, I suspect quite many CDK users don't know about this new feature.

For Lambda functions written in Go, you must be using CDK v1.57.0 (released on August 7th 2020) or newer, since previous CDK had an issue with missing Docker image for Go.

How it works, is that you define your Lambda function as you have done before, but for the lambda.Code.fromAsset function you give extra options that define how the function code is built and with what Docker image:

new lambda.Function(this, "MyFunc", {
runtime: lambda.Runtime.GO_1_X,
handler: "bin/main",
code: lambda.Code.fromAsset("path/to/my-func", {
bundling: {
// Speficy Docker image:
image: lambda.Runtime.GO_1_X.bundlingDockerImage,
// The build command to be executed within the Docker container:
command: [
'bash', '-c', [
`cd /asset-input`,
`go build -o bin/main`,
`cp /asset-input/bin/main /asset-output/bin`,
].join(' && ')
],
user: 'root',
},
// Glob(s) to exclude from deployment package:
// In most cases with Go, only the binary is needed (unless you depend on external files)
exclude: ["!bin"],
}),
});

Now once you run cdk synth or cdk deploy, CDK will build the functions for you inside a Docker containers! And the best part: This is not just for Go, you can use the BundlingOptions with any runtime!

If you have multiple Lambda functions and they all have the same build steps, remember CDK is just code, so you can define the BundlingOptions as an object that you can then import & reference in each of your Lambda function definitions!

Having the tooling to build your Lambda functions within CDK makes complete sense, since I guess one could say that one aim of CDK is that you can deploy both your infrastructure and application logic at one go because by using serverless and AWS managed services, the line between infrastructure and application code is already quite blurry, so it makes sense.

Local development?

Having the possibility to build Lambda functions with just CDK (inside Docker container) is an awesome feature, since it means not having to use many different tools: The toolchain still leaves one having to think about how to solve local testing and invocation, which I think is essential for developing Lambda functions by allowing you to specify different kind of event payloads as json-files and test/debug the function logic against them.

It would be cool if the same construct would provide a mechanism for testing and locally invoking the Lambda functions. I could imagine something like this:

// ⚠️ IMPORTANT NOTE ⚠️
// This is NOT a working code example!
// Just something I imagined and think would be cool to have in CDK:
new lambda.Function(this, "MyFunc", {
runtime: lambda.Runtime.GO_1_X,
handler: "bin/main",
code: lambda.Code.fromAsset("path/to/my-func", {
// Removed the "building" config shown above for brevity
testing: {
// Speficy Docker image:
image: lambda.Runtime.GO_1_X.bundlingDockerImage, // Could probably use the same from "building" if none specified? Maybe also support providing own Dockerfile?
// The test command to be executed within the Docker container:
command: [
'bash', '-c', [
`cd /asset-input`,
`go test`,
].join(' && ')
],
user: 'root',
},
}),
});

And I could also imagine something like:

  • cdk lambda test to run tests for all functions
  • cdk lambda test MyFunc to run tests for specific function
  • cdk lambda invoke MyFunc --event some-payload.json to build and then locally invoke the function code

But I am doubtful something like this will be implemented (at least in the near future), so maybe I'll still keep working on the cdk-docker-lambda as a temporary solution and maybe consider contributing something (at least ideas) after I've done my learning with it.

I have created an issue about this on CDK Github repo. [Update on August 10th] The issue has now been moved under the AWS CDK RFCs-repository which is more suitable for tracking changes and proposals that need deeper consideration and you can find it here.

Thoughts

I'm just getting started with building Lambda functions with CDK, but it sure looks promising so far! I'll probably write a separate blog post about my experiences once I've started using this feature in real projects and figured out the local development flow.