Deploying a .NET Core App to AWS Elastic Beanstalk via Azure DevOps 
Tags: aws, azure, devops
Published: 2020-02-02 18:52:43


---
title: Deploying a .NET Core App to AWS Elastic Beanstalk via Azure DevOps 
published: true
description: In this post I will show you how easy it is to deploy a .NET Core application to AWS Elastic Beanstalk.
tags: aws, azure, devops
canonical_url: https://ikethe.dev/deploying-a-net-core-app-to-aws-elastic-beanstalk-via-azure-devops/
cover_image: https://i1.wp.com/ikethe.dev/wp-content/uploads/2020/01/MVIMG_20191218_084442-scaled.jpg?fit=2560%2C1440&ssl=1
---

Deploying a .NET Core App to AWS Elastic Beanstalk via Azure DevOps





### Introduction

In another life and my two previous roles I evangalized [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/) relentlessly. In one position we had nothing in terms of a tooling outside of a very outdated implementation of [SubVersion](https://subversion.apache.org/). In the other postion, add outdated versions of [Jira ](https://www.atlassian.com/software/jira)and [Jenkins](https://jenkins.io/) to the mix. At least in this instance, Jenkins and SubVersion were somewhat connected, however, Jira was left out in the cold.

Anytime I would demo Azure DevOps I faced the same questions and criticisms.

- **“But it won’t work with \[insert non-Microsoft language\], will it?”**
- **“It’s Microsoft, it will never work outside of their ecosystem!”**
- **“We want to host our applications with Amazon, this won’t work here.”**

Those were the comments on the friendlier side of things. At times, they were just plain unprofessional (to put it nicely). But as [@DonovanBrown](https://twitter.com/DonovanBrown?s=20), “This ain’t your daddy’s Microsoft.”

In this post I will show you how easy it is to deploy a .NET Core application to [AWS Elastic Beanstalk](https://aws.amazon.com/elasticbeanstalk/).

### Setting Up the AWS Environment

To begin, you will need to set up an AWS Elastic Beanstalk environment. Before writing this post, I had never touched AWS. The process is fairly straight forward.

Login to the AWS Management Console and then search for “Elastic Beanstalk”

![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/001-AWS-Management-Console.png?fit=843%2C628&ssl=1)Click the **Create New Application** button towards the upper right-hand corner. The below modal will pop up. Enter a name for your application in the **Application name** field and click the **Create** button.

![](https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/02/002-Create-Web-App.png?fit=843%2C384&ssl=1)This takes us to the Base configuration section. I selected **.NET (Windows/IIS)** for the **Platform**, however you could run .NET Core most of the Linux offerings. For **Application code**, choose **Sample application** as we will deploy our actual code with Azure DevOps. Once you have set the base configuration click the **Create application** button.

![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/02/003-Create-Web-App-Configuration.png?fit=843%2C382&ssl=1)This will kickoff the creation of our Elastic Beanstalk environment.

![](https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/01/004-Create-Web-App-Creating.png?fit=843%2C271&ssl=1)Once the creation is complete it will redirect you to the Elastic Beanstalk dashboard shown below. You can view the application by clicking the **URL** at the top of the page.

![](https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/02/005-Create-Web-App-Created-1.png?fit=843%2C342&ssl=1)Congratulations, you have just created a deault ASP.NET Web App.

![](https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/01/006-Create-Web-App-Created-Default-App.png?fit=843%2C338&ssl=1)### Creating an AWS IAM Account

Next, you will create an account that you can use to deploy your application from Azure DevOps. Click the dropdown menu associated with your **account name** and then select **My Security Credentials**.

![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/023-AWS-My-Security-Credentials-1.png?fit=843%2C231&ssl=1)A warning modal will popup, select **Get Started with IAM Users**.

![](https://i1.wp.com/ikethe.dev/wp-content/uploads/2020/01/024-AWS-My-Security-Credentials-IAM-Warning-1.png?fit=843%2C351&ssl=1)Next, you will create a security group. Enter “AzureDevOps” for the **Group Name**. Then click **Next Step**.

![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/025-AWS-My-Security-Credentials-Create-Group.png?fit=843%2C497&ssl=1)This brings you to the Attach Policy page. Enter “AWSElasticBeanstalk” into the search box to filter through the policies. Then select **AWSElasticBeanstalkFullAccess**. Click **Attach Policy** to continue.

![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/02/026-AWS-My-Security-Credentials-Attach-Policy.png?fit=843%2C440&ssl=1)Review the settings and click **Create Group**.

![](https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/01/027-AWS-My-Security-Credentials-Review.png?fit=843%2C194&ssl=1)This brings you to the Add user screen. Enter a **User name**. I used the name “AzureDevOps” for this example. For **Acces type** select **Programmatic access**. Then click **Next: Permissions**.

![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/02/028-AWS-My-Security-Credentials-Add-User.png?fit=843%2C397&ssl=1)Next we need to set the permissions on the account. We do this by adding the users to groups with policies attached to them. We will place this account in the AzureDevOps group we previously created. Click **Next: Tags**.

![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/02/029-AWS-My-Security-Credentials-Add-User-Set-permissions.png?fit=843%2C390&ssl=1)This will bring us to the Tags page. In this example I did not add any tags. Click **Next: Review**.

![](https://i1.wp.com/ikethe.dev/wp-content/uploads/2020/01/030-AWS-My-Security-Credentials-Add-User-Add-Tags.png?fit=843%2C291&ssl=1)Review your account settings and if they are correct click **Create user**.

![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/031-AWS-My-Security-Credentials-Add-User-Review.png?fit=843%2C470&ssl=1)Congratulations, you have successfully create a new user account. Next copy the **Access key ID** and **Secret access key** as we will be storing them in [Azure Key Vault](https://azure.microsoft.com/en-us/services/key-vault/).

![](https://i1.wp.com/ikethe.dev/wp-content/uploads/2020/01/032-AWS-My-Security-Credentials-Add-User-Success.png?fit=843%2C352&ssl=1)### Creating Azure DevOps Service Connection for AWS

The next step is to create a new [Service Connection](https://docs.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops&tabs=yaml) within Azure DevOps for AWS. This connection will use the account we created in the previous step.

First, go to the Projects Settings by clicking the Settings button towards the bottom of the left hand menu.

![](https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/02/01-Azure-DevOps-Project-Settings.png?w=843&ssl=1)Then scroll down to **Pipelines** and select **Service connections**.

![](https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/02/02-Azure-DevOps-Select-Service-Connections.png?w=843&ssl=1)Next, click **Create service connection**

![](https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/02/021-Azure-DevOps-New-Release-Task-AWS-Elastic-Beanstalk-Deploy-Application-Authenticate-Service-Connection-1.png?fit=843%2C314&ssl=1)Select **AWS** and click **Next**

![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/02/022-Azure-DevOps-New-Release-Task-AWS-Elastic-Beanstalk-Deploy-Application-Authenticate-New-AWS-Service-Connection-1.png?fit=843%2C715&ssl=1)Fill in the **Access Key ID** and **Secret Access Key** from the IAM User you created in AWS.

![](https://i1.wp.com/ikethe.dev/wp-content/uploads/2020/02/033-Azure-DevOps-AWS-Service-Connection-Add-Keys.png?w=843&ssl=1)Enter a **Service connection name** and check **Grant access permission to all pipelines**. Then click **Save.**

![](https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/02/034-Azure-DevOps-AWS-Service-Connection-Add-Name-1.png?w=843&ssl=1)You have now created a new service connection.

![](https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/02/035-Azure-DevOps-AWS-Service-Connection-Created-Service-Connection.png?fit=843%2C216&ssl=1)### Creating the Application

In this post we will deploy a fairly simple .NET Core Application. Create a new .NET Core MVC Application and make the following changes.

In the **Index.chtml file** replace the existing code with the following.

```
@{
    ViewData["Title"] = "Home Page";
}

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p>This demo is running .NET Core on @ViewData["CloudEnv"].</p>
    <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
```


Then in the **HomeController** add an **IConfiguration property** and add it to the constructor.

```
private readonly ILogger<HomeController> _logger;

private readonly IConfiguration _configuration;

public HomeController(ILogger<HomeController> logger, IConfiguration configuration)
{
     _logger = logger;
     _configuration = configuration;
 }
```


Replace the **Index** method of the **HomeController** with the following code.

```<pre class="wp-block-preformatted">public IActionResult Index()
{
     ViewData["CloudEnv"] = _configuration["CloudEnv"];
     return View();
}
```
Add the setting **CloudEnv** to appsettings.json. Not the prefix and postfix “\_\_” in the CloudEnv variable. We are tokenizing this variable so that the value can be replaced with a pipeline variable later on.

```<pre class="wp-block-preformatted">{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "CloudEnv":  "__CloudEnv__"
}
```
Afterwards, add the **CloudEnv** setting to appsettings.Development.json

```<pre class="wp-block-preformatted">{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "CloudEnv": "my local machine"
}
```
That’s it for the application. Now on to the pipeline.

### Creating the Multi-Stage Pipeline

In this example we will create a multi-stage pipeline using YAML. The first stage will build the pipeline and the second will deploy it to AWS.

### Marketplace Extensions

The multi-stage pipeline we are creating requires two [Marketplace Extensions](https://docs.microsoft.com/en-us/azure/devops/marketplace-extensibility/?view=azure-devops). Install the following extensions into your project. Consult [this reference guide](https://docs.microsoft.com/en-us/azure/devops/marketplace/install-extension?view=azure-devops&tabs=browser) if you are having issues installing the extensions.

- [Replace Tokens](https://marketplace.visualstudio.com/items?itemName=qetza.replacetokens)
- [AWS Tools for Microsoft Visual Studio Team Services](https://marketplace.visualstudio.com/items?itemName=AmazonWebServices.aws-vsts-tools)

### Creating the Initial Pipeline

First, we will create the basis for the pipeline.

In Azure DevOps select the **“Rocket Ship”** from the lefthand menu, then select **Pipelines**.

![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/01-Go-to-Pipelines.png?w=843&ssl=1)Next, select **New pipeline**

![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/02-Select-New-Pipeline.png?fit=843%2C81&ssl=1)Then select where your code is located. For this example my code is stored in Azure Repos Git. However, your code could be in GitHub or any other source control tool.

![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/03-Select-Code-Location.png?w=843&ssl=1)Afterwards, select your repository.

![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/04-Select-Repository.png?w=843&ssl=1)Then select **Starter pipeline**. This will create a basic pipeline with a couple of tasks.

![](https://i1.wp.com/ikethe.dev/wp-content/uploads/2020/01/05-Select-Starter-Pipeline.png?w=843&ssl=1)### Modifying the YAML

Before we begin, I would like to warn you that YAML can be very fickle and whitespaces matter. One misaligned space can cause the entire pipeline to fail.

To learn more about YAML pipelines [this reference guide](https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema#pipeline-structure) is an excellent resource.

### Initial Pipeline

The initial portion of our pipeline defines the trigger, which is defined as the master branch of our repository. Anytime master changes, our pipeline will automatically run.

```<pre class="wp-block-preformatted">trigger:
- master
```
### Stage 1 – Build

The first stage of our pipeline downloads any dependencies and then builds the solution. It then creates then publishes the artifact that will be used in the second stage.

```<pre class="wp-block-preformatted">stages:
- stage: 'Build'
  variables:
    solution: '**/*.sln'
    buildPlatform: 'Any CPU'
    buildConfiguration: 'Release'
  displayName: 'Build'
  jobs:
  - job: 'Build'
    displayName: 'Build'
    pool:  
      vmImage: 'windows-latest' 
    steps:
    - task: NuGetToolInstaller@1

    - task: NuGetCommand@2
      inputs:
        restoreSolution: '$(solution)'

    - task: VSBuild@1
      inputs:
        solution: '$(solution)'
        msbuildArgs: '/p:SkipInvalidConfigurations=true /p:DeployOnBuild=true /p:WebPublishMethod=FileSystem /p:publishUrl="$(build.artifactstagingdirectory)\\" /p:DeployDefaultTarget=WebPublish'
        platform: '$(buildPlatform)'
        configuration: '$(buildConfiguration)'

    - task: VSTest@2
      inputs:
        platform: '$(buildPlatform)'
        configuration: '$(buildConfiguration)'

    - task: PublishBuildArtifacts@1
      inputs:
        PathtoPublish: '$(Build.ArtifactStagingDirectory)'
        ArtifactName: 'drop'
        publishLocation: 'Container'
```
**Important**: When deploying a .NET Core App to AWS Elastic Beanstalk you cannot use the default **msbuildArgs**. For some reason Elastic Beanstalk cannot find the web.config file when the artifact is published as a zip file. Please note the **WebPublishMethod=FileSystem** argument.

### Stage 2 – Deploying to AWS

The second stage of the pipeline deploys the artifact from the first stage to AWS.

```<pre class="wp-block-preformatted">  - stage: 'Deploy'
  displayName: 'Deploy'
  jobs:
  - deployment: DeployToAws
    displayName: 'Deploy To AWS Elastic Beanstalk'
    pool:
      vmImage: 'windows-latest'
    variables:
      CloudEnv: 'AWS Elastic Beanstalk'
    environment: 'Production'
    strategy:
      runOnce:
        deploy:
          steps:
          - download: current
            artifact: drop
          - task: replacetokens@3
            displayName: 'Replace Tokens'
            inputs:
              rootDirectory: '$(Pipeline.Workspace)/drop'
              targetFiles: '**/*.json'
              tokenPrefix: '__'
              tokenSuffix: '__'

          - task: BeanstalkDeployApplication@1
            displayName: 'Deploy to Elastic Beanstalk'
            inputs:
              awsCredentials: 'AWS Deployment Service Connection'
              regionName: 'us-east-2'
              applicationName: DotNetCoreDemo
              environmentName: 'Dotnetcoredemo-env'
              applicationType: aspnetCoreWindows
              dotnetPublishPath: '$(Pipeline.Workspace)/drop' 
```
Notice in the deployment job we are setting a job level variable **CloudEnv**. We will use variable in a later task to replace our tokenized appsetting from earlier.

```<pre class="wp-block-preformatted"> - deployment: DeployToAws
     displayName: 'Deploy To AWS Elastic Beanstalk'
     pool:
       vmImage: 'windows-latest'
     variables:
       CloudEnv: 'AWS Elastic Beanstalk' 
```
Let’s break down the tasks in this stage of the pipeline.

The first task, **download**, downloads the artifact we created in the previous build stage.

```<pre class="wp-block-preformatted"> - download: current
             artifact: drop 
```
The next task, **replacetokens@3**, is the first Marketplace Extension we installed earlier. This tasks looks for any string with a prefix and suffix of “\_\_” and attempts to replace them with a variable defined in the pipeline. In this example, “AWS Elastic Beanstalk” replaces the value for CloudEnv in appsettings.json

```<pre class="wp-block-preformatted"> - task: replacetokens@3
            displayName: 'Replace Tokens'
            inputs:
              rootDirectory: '$(Pipeline.Workspace)/drop'
              targetFiles: '**/*.json'
              tokenPrefix: '__'
              tokenSuffix: '__' 
```
**Important**: The variable name must match the string without the prefix and suffix.

The final task, **BeanstalkDeployApplication@1**, is the second Marketplace Extension we installed earlier. This task deploys the artifact to AWS Elastic Beanstalk. Pay close attention to the inputs. The **awsCredentials** argument is the name of the service connection we created earlier. **regionName**, **applicationName**, and **environmentName** should match with the Elastic Beanstalk environment and application we originally created. The last setting, **dotnetPublishPath**, is the location of the artifact we downloaded.

```<pre class="wp-block-preformatted"> - task: BeanstalkDeployApplication@1
            displayName: 'Deploy to Elastic Beanstalk'
            inputs:
              awsCredentials: 'AWS Deployment Service Connection'
              regionName: 'us-east-2'
              applicationName: DotNetCoreDemo
              environmentName: 'Dotnetcoredemo-env'
              applicationType: aspnetCoreWindows
              dotnetPublishPath: '$(Pipeline.Workspace)/drop' 
```
### The Entire Pipeline

Below is the complete multi-stage pipeline.

```<pre class="wp-block-preformatted"> trigger:
- master

stages:
- stage: 'Build'
  variables:
    solution: '**/*.sln'
    buildPlatform: 'Any CPU'
    buildConfiguration: 'Release'
  displayName: 'Build'
  jobs:
  - job: 'Build'
    displayName: 'Build'
    pool:  
      vmImage: 'windows-latest' 
    steps:
    - task: NuGetToolInstaller@1

    - task: NuGetCommand@2
      inputs:
        restoreSolution: '$(solution)'

    - task: VSBuild@1
      inputs:
        solution: '$(solution)'
        msbuildArgs: '/p:SkipInvalidConfigurations=true /p:DeployOnBuild=true /p:WebPublishMethod=FileSystem /p:publishUrl="$(build.artifactstagingdirectory)\\" /p:DeployDefaultTarget=WebPublish'
        platform: '$(buildPlatform)'
        configuration: '$(buildConfiguration)'

    - task: VSTest@2
      inputs:
        platform: '$(buildPlatform)'
        configuration: '$(buildConfiguration)'

    - task: PublishBuildArtifacts@1
      inputs:
        PathtoPublish: '$(Build.ArtifactStagingDirectory)'
        ArtifactName: 'drop'
        publishLocation: 'Container'

- stage: 'Deploy'
  displayName: 'Deploy'
  jobs:
  - deployment: DeployToAws
    displayName: 'Deploy To AWS Elastic Beanstalk'
    pool:
      vmImage: 'windows-latest'
    variables:
      CloudEnv: 'AWS Elastic Beanstalk'
    environment: 'Production'
    strategy:
      runOnce:
        deploy:
          steps:
          - download: current
            artifact: drop
          - task: replacetokens@3
            displayName: 'Replace Tokens'
            inputs:
              rootDirectory: '$(Pipeline.Workspace)/drop'
              targetFiles: '**/*.json'
              tokenPrefix: '__'
              tokenSuffix: '__'

          - task: BeanstalkDeployApplication@1
            displayName: 'Deploy to Elastic Beanstalk'
            inputs:
              awsCredentials: 'AWS Deployment Service Connection'
              regionName: 'us-east-2'
              applicationName: DotNetCoreDemo
              environmentName: 'Dotnetcoredemo-env'
              applicationType: aspnetCoreWindows
              dotnetPublishPath: '$(Pipeline.Workspace)/drop' 
```
At this point we can run the pipeline and it will deploy our application to our AWS Elastic Beanstalk environment.

### Successful Deployment

As you can tell by the below screenshot the deployment was successful and the CloudEnv appsetting was replaced with the pipeline variable.

![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/02/9999-Success-2.png?fit=843%2C226&ssl=1)