How to Deploy a .NET App on AWS Elastic Beanstalk using Terraform (Windows Server)

Somebody who likes to code
Search for a command to run...

Somebody who likes to code
No comments yet. Be the first to comment.
Google processes tens of thousands of code changes every day across a codebase of hundreds of millions of lines. The secret isn't magic tooling — it's a documented, principled approach to code review

Amazon Simple Notification Service (SNS) looks straightforward on the surface — publish a message, deliver it to subscribers — but the gap between a topic that "works in dev" and one that is productio

Amazon Simple Queue Service (SQS) is deceptively simple to get started with, but surprisingly easy to misconfigure in ways that cause silent message loss, runaway costs, duplicate processing storms, o

HTTP API is the right default choice for the majority of .NET serverless workloads. At 70% less cost than REST API, lower latency, and native JWT authorization, it covers most production requirements

AWS Lambda has become a cornerstone of modern serverless architectures, but writing a function that "just works" is very different from writing one that is performant, cost-effective, secure, and read

A couple of weeks ago, we discussed how to deploy a .NET application on AWS Elastic Beanstalk using Terraform, but on a Linux server. After reviewing the official documentation, it appears somewhat outdated when it comes to using Windows Server as a platform. As a result, we aim to modify our initial project to accommodate this support.
So, download or clone this repository. Open the variables.tf file and append the following content at the end:
variable "platform" {
type = string
}
Open the main.tf file and replace its content with:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "4.22.0"
}
}
}
provider "aws"{
region = "us-east-2"
}
data "aws_iam_policy_document" "assume_service_role" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["elasticbeanstalk.amazonaws.com"]
}
actions = ["sts:AssumeRole"]
condition {
test = "StringEquals"
variable = "sts:ExternalId"
values = [
"elasticbeanstalk"
]
}
}
}
resource "aws_iam_role" "service_role" {
name = "aws-elasticbeanstalk-service-role"
assume_role_policy = data.aws_iam_policy_document.assume_service_role.json
}
resource "aws_iam_role_policy_attachment" "AWSElasticBeanstalkEnhancedHealth-attach" {
role = aws_iam_role.service_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSElasticBeanstalkEnhancedHealth"
}
resource "aws_iam_role_policy_attachment" "AWSElasticBeanstalkManagedUpdatesCustomerRolePolicy-attach" {
role = aws_iam_role.service_role.name
policy_arn = "arn:aws:iam::aws:policy/AWSElasticBeanstalkManagedUpdatesCustomerRolePolicy"
}
data "aws_iam_policy_document" "assume_role" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
actions = ["sts:AssumeRole"]
}
}
resource "aws_iam_role" "role" {
name = "aws-elasticbeanstalk-ec2-role"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
resource "aws_iam_role_policy_attachment" "CloudWatchFullAccess-attach" {
role = aws_iam_role.role.name
policy_arn = "arn:aws:iam::aws:policy/CloudWatchFullAccess"
}
resource "aws_iam_role_policy_attachment" "AWSElasticBeanstalkWebTier-attach" {
role = aws_iam_role.role.name
policy_arn = "arn:aws:iam::aws:policy/AWSElasticBeanstalkWebTier"
}
resource "aws_iam_role_policy_attachment" "AWSElasticBeanstalkWorkerTier-attach" {
role = aws_iam_role.role.name
policy_arn = "arn:aws:iam::aws:policy/AWSElasticBeanstalkWorkerTier"
}
resource "aws_iam_role_policy_attachment" "AWSElasticBeanstalkMulticontainerDocker-attach" {
role = aws_iam_role.role.name
policy_arn = "arn:aws:iam::aws:policy/AWSElasticBeanstalkMulticontainerDocker"
}
resource "aws_iam_instance_profile" "instance_profile" {
name = "aws-elasticbeanstalk-ec2-role"
role = aws_iam_role.role.name
}
resource "aws_s3_bucket" "bucket" {
bucket = var.bucket
}
resource "aws_s3_object" "bucket_object" {
bucket = aws_s3_bucket.bucket.id
key = "app-${uuid()}.zip"
source = "app.zip"
}
resource "aws_elastic_beanstalk_application" "application" {
name = var.application
}
resource "aws_elastic_beanstalk_environment" "environment" {
name = "${var.application}-env"
application = aws_elastic_beanstalk_application.application.name
solution_stack_name = var.platform
tier = "WebServer"
setting {
namespace = "aws:ec2:vpc"
name = "VPCId"
value = var.vpc_id
}
setting {
namespace = "aws:ec2:vpc"
name = "Subnets"
value = var.ec2_subnets
}
setting {
namespace = "aws:ec2:vpc"
name = "ELBScheme"
value = "internet facing"
}
setting {
namespace = "aws:ec2:vpc"
name = "ELBSubnets"
value = var.elb_subnets
}
setting {
namespace = "aws:autoscaling:launchconfiguration"
name = "IamInstanceProfile"
value = aws_iam_role.role.name
}
setting {
namespace = "aws:autoscaling:launchconfiguration"
name = "InstanceType"
value = var.instance_type
}
setting {
namespace = "aws:autoscaling:launchconfiguration"
name = "EC2KeyName"
value = var.keypair
}
setting {
namespace = "aws:elasticbeanstalk:environment"
name = "LoadBalancerType"
value = "application"
}
setting {
namespace = "aws:elasticbeanstalk:environment"
name = "ServiceRole"
value = aws_iam_role.service_role.name
}
setting {
namespace = "aws:elasticbeanstalk:environment:process:default"
name = "HealthCheckPath"
value = var.health_check_path
}
}
resource "aws_elastic_beanstalk_application_version" "version" {
bucket = aws_s3_bucket.bucket.id
key = aws_s3_object.bucket_object.id
application = aws_elastic_beanstalk_application.application.name
name = "${var.application}-app-${uuid()}"
}
With this new variable, we are parameterizing the platform that will be used. It's time to add the deployment manifest file. Create a file named aws-windows-deployment-manifest.json at the solution level with the following content:
{
"manifestVersion": 1,
"deployments": {
"aspNetCoreWeb": [
{
"name": "dotnet-api",
"parameters": {
"appBundle": "site.zip",
"iisPath": "/",
"iisWebSite": "Default Web Site"
}
}
]
}
}
Run the following commands to create the bundle to deploy:
mkdir terraform/publish
mkdir terraform/bundle
dotnet publish ./src/WeatherApi.csproj --output "terraform/publish" --configuration "Release" --framework "net6.0" /p:GenerateRuntimeConfigurationFiles=true --runtime win-x64 --no-self-contained
Compress-Archive -Path terraform/publish/* -DestinationPath terraform/bundle/site.zip
copy .\aws-windows-deployment-manifest.json .\terraform\bundle
Compress-Archive -Path terraform/bundle/* -DestinationPath terraform/app.zip
Tip: To deploy .NET versions such as .NET 5 or .NET 7, you can create a self-contained artifact using a command like:
dotnet publish ./src/WeatherApi5/WeatherApi5.csproj --output "terraform/publish" --configuration "Release" --framework "net5.0" /p:GenerateRuntimeConfigurationFiles=true --runtime win-x64 --self-contained /p:PublishTrimmed=false
Run the following commands to create the environment, application, and application version:
cd terraform
terraform init
terraform plan -out app.tfplan -var="health_check_path=/swagger/index.html" -var="bucket=app-tf-001" -var="keypair=<MY_KEY_PAIR>" -var="instance_type=t2.medium" -var="application=app-tf-001" -var="vpc_id=<MY_VPC>" -var="ec2_subnets=<MY_SUBNETS>" -var="elb_subnets=<MY_SUBNETS>" -var="platform=64bit Windows Server 2019 v2.11.3 running IIS 10.0"
terraform apply 'app.tfplan'
And finally, the deployment itself of our application version:
aws --region us-east-2 elasticbeanstalk update-environment --environment-name <OUTPUT_ENV_NAME> --version-label <OUTPUT_APP_VERSION>
And that's it, the same application we deployed to a Linux server now running in a Windows Server under AWS Elastic Beanstalk. The code and scripts are available here. Thanks, and happy coding.