# CI/CD Pipeline for .NET Lambdas Using GitHub Actions

Serverless applications in .NET are growing in popularity thanks to AWS Lambda’s support for .NET 6, .NET 7, and now .NET 8. But building, testing, and deploying these applications manually can be time-consuming and error-prone. That’s where CI/CD pipelines come in.

In this post, we’ll walk through how to set up a CI/CD pipeline using GitHub Actions to build, test, and deploy .NET AWS Lambda functions automatically.

## Prerequisites

To follow along, you’ll need:

* A .NET Lambda project (for example, created with `dotnet new lambda.EmptyFunction`)
    
* An AWS account with IAM credentials
    
* AWS CLI installed locally (for initial setup)
    
* An S3 bucket to store deployment artifacts (optional)
    
* The AWS CLI and Amazon.Lambda.Tools installed:
    

```bash
dotnet tool install -g Amazon.Lambda.Tools
```

## Project Structure

Let’s assume the following structure:

```bash
MyLambdaFunction/
├── src/
│   └── MyLambdaFunction/
├── tests/
│   └── MyLambdaFunction.Tests/
├── .github/
│   └── workflows/
│       └── deploy.yml
```

---

## Step 1: Create a GitHub Actions Workflow

Create the file `.github/workflows/deploy.yml`:

```yaml
name: CI/CD for .NET Lambda

on:
  push:
    branches:
      - main

jobs:
  build-test-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.x'

      - name: Restore dependencies
        run: dotnet restore

      - name: Build
        run: dotnet build --configuration Release --no-restore

      - name: Run tests
        run: dotnet test --no-build --verbosity normal

      - name: Get Lambda Role ARN from SSM
        id: ssm
        run: |
          ROLE_ARN=$(aws ssm get-parameter --name "/lambda/MyLambdaFunction/RoleArn" --query "Parameter.Value" --output text)
          echo "role_arn=$ROLE_ARN" >> $GITHUB_OUTPUT
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_REGION: us-east-1

      - name: Deploy to AWS Lambda
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_REGION: us-east-1
        run: |
          cd src/MyLambdaFunction
          dotnet lambda deploy-function MyLambdaFunction \
            --region $AWS_REGION \
            --function-role ${{ steps.ssm.outputs.role_arn }}
```

---

## Step 2: Set Up AWS Credentials for GitHub Actions

To securely authenticate your GitHub Actions workflow with AWS, you need to create an IAM user and store its access keys as GitHub secrets.

### Create an IAM User in AWS

1. Sign in to the [AWS Console](https://console.aws.amazon.com/)
    
2. Go to IAM &gt; Users &gt; Add users
    
3. Choose a username (e.g., `github-actions-deploy`)
    
4. Enable **Programmatic access**
    
5. Attach the `AWSLambdaFullAccess` policy or a custom policy with only necessary permissions
    
6. Save the **Access Key ID** and **Secret Access Key**
    

### Add Secrets to GitHub

1. In your GitHub repo, go to **Settings** &gt; **Secrets and variables** &gt; **Actions**
    
2. Add the following secrets:
    
    * `AWS_ACCESS_KEY_ID`
        
    * `AWS_SECRET_ACCESS_KEY`
        

## Step 3: Use SSM Parameters to Avoid Manual Setup

You can avoid hardcoding values like the Lambda role ARN in your workflow by storing them in AWS Systems Manager (SSM) Parameter Store.

### Store Required Values in SSM

Run this command locally to store the role ARN:

```bash
aws ssm put-parameter --name "/lambda/MyLambdaFunction/RoleArn" \
  --type String \
  --value "arn:aws:iam::<account-id>:role/<lambda-execution-role>"
```

Your GitHub Actions workflow can then retrieve this value dynamically before deployment.

## Step 4: Do You Still Need Manual Deployment?

Yes, **unless** you ensure everything required for the deployment is pre-configured. Here's why:

* The `dotnet lambda deploy-function` command creates the Lambda function if it doesn't exist, but requires details like the function role, memory size, etc.
    
* If any required input is missing or the IAM role is misconfigured, the deployment will fail.
    
* GitHub Actions is non-interactive, so any CLI prompts will break the pipeline.
    

### When Manual First-Time Deployment is Needed

You should deploy manually the first time if:

* The Lambda function does not yet exist
    
* You haven't scripted or automated the full configuration
    

```bash
cd src/MyLambdaFunction
dotnet lambda deploy-function MyLambdaFunction
```

This confirms that the function, role, region, and permissions are all set up correctly.

### When You Can Skip Manual Deployment

You can skip it if:

* The Lambda function and IAM role already exist
    
* You’ve stored the function role in SSM and reference it in your workflow
    
* You pass all required CLI arguments directly in the workflow
    

This approach makes your pipeline truly hands-off from the beginning.

## Conclusion

With this setup:

* Every push to the `main` branch triggers your CI/CD pipeline
    
* The pipeline builds, tests, and deploys your Lambda function
    
* SSM parameters allow dynamic configuration without hardcoding
    
* Manual setup can be avoided if resources are pre-provisioned
    

This makes your Lambda deployments consistent, automated, and production-ready.

## References

* [AWS Lambda Tools for .NET CLI](https://github.com/aws/aws-extensions-for-dotnet-cli)
    
* [GitHub Actions Documentation](https://docs.github.com/en/actions)
    
* [AWS CLI Configuration](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html)
    
* [AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html)
    
* [Deploying Lambda with dotnet CLI](https://docs.aws.amazon.com/lambda/latest/dg/csharp-package-cli.html)
