CI/CD for .NET Core
Tags: dotnet, docker, aws
Published: 2022-04-24 10:03:20

This is a guide for setting up CI/CD for a .NET API in the AWS environment using AWS Code Pipeline and AWS ECS.

Here are the steps:

1. Add files for building a docker image and pushing to the container registory
2. Setup Code Pipeline
3. Setup ECS

Two files `Dockerfile` and `buildspec.yml` need to be added to the project.

The TLS 1.0 option is needed if your SQL Server database does not support TLS 1.2. The recommended option is to update your database to the secure protocol.

You may also change sdk and aspnet version to 5.0 if you haven't updated to 6.0 yet.

This Dockerfile uses a multi-stage build to get an optimal image size.

`Dockerfile`

```
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
WORKDIR /app

# Copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore

# Copy everything else and build
COPY . ./
RUN dotnet publish -c Release -o out

# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app

# Enable TLS 1.0
RUN sed -i 's/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/g' /etc/ssl/openssl.cnf
RUN sed -i 's/MinProtocol = TLSv1.2/MinProtocol = TLSv1/g' /etc/ssl/openssl.cnf
RUN sed -i 's/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/g' /usr/lib/ssl/openssl.cnf
RUN sed -i 's/MinProtocol = TLSv1.2/MinProtocol = TLSv1/g' /usr/lib/ssl/openssl.cnf

COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "myapp-whatever.dll"]
```

`buildspec.yml`

```
version: 0.2

phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - aws --version
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
      - REPOSITORY_URI=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - IMAGE_TAG=${COMMIT_HASH:=latest}
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - docker build -t $REPOSITORY_URI:latest .
      - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker images...
      - docker push $REPOSITORY_URI:latest
      - docker push $REPOSITORY_URI:$IMAGE_TAG
      - echo Writing image definitions file...
      - printf '[{"name":"%s","imageUri":"%s"}]' $IMAGE_REPO_NAME $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json
artifacts:
    files: imagedefinitions.json
```


If you don't have a health check endpoint, this is important for containers for the orchestrator to know if your container is ready or if needs to be restarted.

Program.cs
```
builder.Services.AddHealthChecks();
var app = builder.Build();
app.MapHealthChecks("/api/health");
```

Startup.cs
```
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.MapHealthChecks("/api/health");
            });
```

You will need to do this in sequence.

## Create the Elastic Container Repository (ECR)

Take note of your image repo name and URL.

## Create CodePipeline

1. Add the Source stage
2. Add the Build stage
3. Skip the Deployment stage for now.

When you first create the CodePipeline, skip the deployment stage because you will add it only after your first image has been created.

You will need to go to IAM -> Roles -> codebuild-*name*-service-role and add the AmazonEC2ContainerRegistryFullAccess policy to that service role.

After a successful build, then you may proceed to create the ECS Service.

## Create CodeBuild

Choose the following:

Environment Image: Managed Image 
Operating System: Ubuntu 
Runtime: Standard 
Image: aws/codebuild/standard:5.0 
Environment Type: Linux 
Privileged: True

You will need to configure the following environment variables:

```
AWS_DEFAULT_REGION
AWS_ACCOUNT_ID
IMAGE_REPO_NAME
```

## Create ECS Task Definition

Image URI: Copy the repository URL and append `:latest` to it

Task CPU: .25 vCPU
Task memory: .5 GB

## Create ECS Service

1. Go to Clusters
2. Create Service
3. Select the Task Definition that you created in the step before
4. Select VPC, Subnets and Security Group

It will take a while for the service to be created and it will fail because there is no image in the repository yet.

## Add Deploy

Stage Name: Deploy
Action Name: Deploy
Action Provider: Amazon ECS
Input Artifact: BuildArtifact