A dotnet new template for an AWS Lambda function in .NET 10, built with a streamlined Clean Architecture and shipped as a container image. All the layers you need, none of the ceremony you don't.
It's one of five Lean Mean Clean Apps templates. They share the same Application and Infrastructure layers and differ only in how they face the world. Same genes, different haircuts.
The guides, the reference and the reasoning behind every opinion live at leanmeancleanapps.github.io/lambda. This is the version for people who skim. We see you.
You need the .NET 10 SDK. Docker is optional, but it's how you run the function locally.
# Install the template
dotnet new install CleanLambdaFunction.Template
# Create a solution
dotnet new cleanlambdafunction --name Acme.Reviews --authorName "Your Name"
cd Acme.Reviews| Option | What it does |
|---|---|
--name |
Names the solution and its root namespace |
--authorName |
Puts your name on the assembly instead of ours |
That's three layers, four test projects, a Dockerfile and a CI pipeline. Go and tell your team you've been working on it all week.
The image ships with the Lambda runtime emulator, so it answers exactly like Lambda would. No AWS account needed until you're ready.
# Build the image and start it with sample data
docker compose up -d --build
# Ask it for a page of movies
curl -X POST http://localhost:9000/2015-03-31/functions/function/invocations \
-d '{ "query": "global", "limit": 2 }'{
"items": [
{
"id": "30fca414-247a-779e-105a-d81aa2506f58",
"title": "Assimilated global infrastructure",
"reviewCount": 1,
"averageStars": 1
},
{
"id": "c27d6600-79a6-9346-2595-a52b7f07dc3e",
"title": "Automated global alliance",
"reviewCount": 3,
"averageStars": 3.3
}
],
"totalCount": 2
}Yes, the sample movies are named by a corporate buzzword generator, and the critics are harsh.
Every function returns one page at a time, with a totalCount and a nextOffset when there's more to come. Send the nextOffset back as offset to get the next page. Movies and authors come with their review count and average stars, worked out by the database rather than in a loop somewhere.
The image runs GetMovies by default. The same image also has GetAuthors and GetReviews, and you pick one by passing its handler string when the container starts. Lambda Annotations writes the class behind each one, so you only write the method.
# Free up port 9000 and build the image
docker compose down
docker build . --target run --tag acme-reviews:local
# Start the reviews function instead of movies
docker run --rm -p 9000:8080 \
-e Database__SeedSampleData=true \
acme-reviews:local \
"Presentation::Acme.Reviews.Presentation.LambdaFunction.Handler_GetReviews_Generated::GetReviews"
# Reviews take a movie or an author instead of a search term
curl -X POST http://localhost:9000/2015-03-31/functions/function/invocations \
-d '{ "movieId": "30fca414-247a-779e-105a-d81aa2506f58", "limit": 5 }'The database is a SQLite file in /tmp, the only place Lambda lets you write, and the function stops any work that would outrun the invocation's remaining time. Lambda bills by the millisecond, so it knows when to call it a day.
When you're ready for the real thing, fill in your profile, region and stack name in src/Presentation/aws-lambda-tools-defaults.json and deploy it to AWS. Every other way to run it, from plain docker run to stamping a version into the image, is in docs/RUN.md.
The function is invoked directly, so AWS IAM decides who gets to call it. Want bearer tokens over HTTP? Put it behind an API Gateway HTTP API with a JWT authorizer. Either way, the bouncer stands outside the door, not inside your code.
Three layers, loosely based on Clean Architecture. Fewer layers than an onion, more than a sandwich.
- Presentation is the Lambda handler. It sends each request through Mediator and uses the source-generated Lambda serializer, so cold starts don't pay for reflection.
- Application owns the rules. Commands and queries are validated by FluentValidation and mapped by Mapperly.
- Infrastructure owns the data, with Entity Framework Core on SQLite. Everything in it is
internal, so Presentation can wire it up but can't move in.
There's a little CQRS in how commands and queries are shaped, but reads and writes share one database underneath. CQRS-curious, not CQRS-committed.
And around the edges:
- Tests use xUnit, Shouldly and NSubstitute, with coverage from Coverlet. Each test gets its own throwaway in-memory database, so no test can spoil another one's data.
- Sample data comes from Bogus, and only when
Database:SeedSampleDatais on. A deployed function never starts with fake reviews. - Build rules live in
Directory.Build.props. Code style and the recommended analyzers run on every build, warnings fail it, and package versions sit in one place with Central Package Management. - The pipeline builds, checks formatting, tests with a 95% coverage bar, makes sure the packed template still works, runs CodeQL, reviews new dependencies and their licences, and scans the container and Dockerfile with Trivy. Then it sums the lot up in one comment on your pull request, so you know exactly who to blame. It needs no accounts, tokens or secrets, so your copy works the moment you push it. Trust is earned.
- Dependabot bundles the month's dependency updates into one pull request on the 15th.
The architecture page has the long version, with full credit to Jason Taylor's template and Uncle Bob for the ideas.
A new version lands on the 1st of each month, as long as something changed, with notes for every pull request in the GitHub release. No changes, no release. We're not here to pad the download stats. Each release also carries an SPDX bill of materials listing every package the function ships with, for anyone whose security team asks nicely (or not so nicely).
dotnet new update| Template | Presentation |
|---|---|
| Clean Minimal API | ASP.NET Core Minimal APIs |
| Clean Model Context Protocol | Model Context Protocol server |
| Clean GraphQL API | GraphQL with Hot Chocolate |
| Clean Web API | ASP.NET Core controllers |
| Clean Lambda Function | AWS Lambda, you are here |
Bug reports, ideas and pull requests are all welcome, and typo fixes doubly so. Start with the contributing guide and the code of conduct. Security problems go through the security policy, not an issue.
Built by Stephen Walsh in spare time, with strong coffee and a healthy disregard for pomp and ceremony. Say hello on GitHub, LinkedIn, Medium or at stphnwlsh.github.io.
If this template saves you some time, sponsoring the work keeps all five templates current and the coffee flowing, which is the real dependency here.
MIT. Take it, change it, ship it. Just don't blame us for your cold starts.
