Auto Deploy to AWS App Runner using AWS CDK and Azure DevOps
Tags: aws, azure, cdk, devops
Published: 2023-03-12 09:22:14

## What's the update?

### Azure DevOps Flow Updates

I've updated the flow and added a step to run the `cdk deploy` command.

![DevOps Flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oj9mwbt7tcojsw6elyhc.png)

I separate the build & deploy into two templates. I include the templates in my pipelines.

- The pipelines (azure-pipelines.yml)

```yml
trigger:
- main

pr:
- main

variables:
- group: 'AWS'

pool:
  vmImage: ubuntu-22.04

parameters:
  - name: awsCredentials
    displayName: AWS Credentials
    type: string
    default: 'AWS-Dev-AssumeRole'
  - name: region
    displayName: AWS Region
    type: string
    default: 'ap-southeast-1'

steps:
- template: build.yml
  parameters:
    awsCredentials: ${{ parameters.awsCredentials }}
    region: ${{ parameters.region }}
- ${{ if and(ne(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) }}:
  - template: deploy.yml
    parameters:
      awsCredentials: ${{ parameters.awsCredentials }}
      region: ${{ parameters.region }}
```

- build.yml

```yml
parameters:
  - name: awsCredentials
    displayName: AWS Credentials
    type: string
    default: 'AWS-Dev-AssumeRole'
  - name: region
    displayName: AWS Region
    type: string
    default: 'ap-southeast-1'

steps:
- script: npm install -g aws-cdk
  displayName: 'Install AWS CDK'
- script: cd SimplePasswordManagerService.Infra && cdk synth
  displayName: 'CDK Synth'
- task: Docker@2
  displayName: 'Build Docker Image'
  inputs:
    command: 'build'
    Dockerfile: 'Dockerfile'
    repository: 'spms'
    tags: '$(Build.BuildId)'
```

- deploy.yml

```yml
parameters:
  - name: awsCredentials
    displayName: AWS Credentials
    type: string
    default: "AWS-Dev-AssumeRole"
  - name: region
    displayName: AWS Region
    type: string
    default: "ap-southeast-1"

steps:
  - task: ECRPushImage@1
    displayName: "Push Image to ECR"
    inputs:
      awsCredentials: "${{ parameters.awsCredentials }}"
      regionName: "${{ parameters.region }}"
      sourceImageName: "spms"
      sourceImageTag: "$(Build.BuildId)"
      repositoryName: "spms"
      pushTag: "$(Build.BuildId)"
  - script: cd SimplePasswordManagerService.Infra && cdk deploy SimplePasswordManagerServiceInfraStack --parameters "imageTag=$(Build.BuildId)"
    displayName: CDK Deploy
    env:
      AWS_ACCESS_KEY_ID: $(AWS_ACCESS_KEY_ID)
      AWS_SECRET_ACCESS_KEY: $(AWS_SECRET_ACCESS_KEY)
      AWS_DEFAULT_REGION: ${{ parameters.region }}
```

### CDK Structures!

I move the ECR provisioner to SpmsRepo class (different Stack). I add some setup to the previous stack:

1. Read created ECR.
2. Read created Secrets from Secret Manager.
3. Read the parameter of `imageTag` and provision App Runner Service.

**Note**: I delete the previous stack. You might not do that in your production! I'm still experimenting, so I'm fine with losing the data.

The pipeline will provide the `imagesTag`. Anyway, currently, I consider this pipeline as a Development environment. It will automatically deploy the application to the App Runner. I'm going to add the release pipeline for the next post.

You can look at my cdk codes.

```csharp
using Amazon.CDK;
using Amazon.CDK.AWS.AppRunner.Alpha;
using Amazon.CDK.AWS.ECR;
using Constructs;
using System.Collections.Generic;

namespace SimplePasswordManagerService.Infra {
  public class SimplePasswordManagerServiceInfraStack : Stack {
    internal SimplePasswordManagerServiceInfraStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props) {
      // 0.0 ECR
      var repository = Repository.FromRepositoryName(this, "spms-ecr", "spms");

      // 1.0 AppRunner
      var appRunnerSecret = Amazon.CDK.AWS.SecretsManager.Secret.FromSecretNameV2(this, "apprunner-secret", "dev/AppRunner/spms");

      var imageTag = new CfnParameter(this, "imageTag", new CfnParameterProps {
        Type = "String",
        Description = "Target tag"
      });

      var appRunner = new Service(this, "spms-apprunner", new ServiceProps {
        Source = Source.FromEcr(new EcrProps {
          Repository = repository,
          ImageConfiguration = new ImageConfiguration {
            Port = 80,
            EnvironmentSecrets = new Dictionary<string, Secret> {
              {"Authentication__Microsoft__ClientId", Secret.FromSecretsManager(appRunnerSecret, "Authentication__Microsoft__ClientId")},
              {"Authentication__Microsoft__ClientSecret", Secret.FromSecretsManager(appRunnerSecret, "Authentication__Microsoft__ClientSecret")},
              {"ConnectionStrings__mongo", Secret.FromSecretsManager(appRunnerSecret, "ConnectionStrings__mongo")},
            },
            EnvironmentVariables = new Dictionary<string, string> {
              {"ASPNETCORE_FORWARDEDHEADERS_ENABLED", "true" }
            }
          },
          TagOrDigest = imageTag.ValueAsString
        }),
      });

      new CfnOutput(this, "output-spms-apprunner-url", new CfnOutputProps {
        Value = appRunner.ServiceUrl
      });
    }
  }
}
```

## Repositories

My Application is open-source. Feel free to see the pipelines and the CDK codes! Feel free to give some feedback.

{% github bervProject/SimplePasswordManagerService %}

![Deployed App Runner](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qt8c2qhz2lewgoqooqbt.png)

## Thank you

Thank you for reading! Have a great day.

![Have a great day gif](https://media.giphy.com/media/dzMu8oCKwBbpUFMBNs/giphy.gif)