{"id":85736,"date":"2019-12-02T16:28:31","date_gmt":"2019-12-02T16:28:31","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=85736"},"modified":"2026-03-09T11:13:54","modified_gmt":"2026-03-09T11:13:54","slug":"asp-net-core-with-gitops-deploying-infrastructure-as-code","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/devops\/tools\/asp-net-core-with-gitops-deploying-infrastructure-as-code\/","title":{"rendered":"Infrastructure as Code with AWS CloudFormation"},"content":{"rendered":"\n<p>Infrastructure as Code (IaC) lets you define cloud infrastructure &#8211; servers, networks, and security configurations &#8211; in version-controlled template files instead of configuring resources manually. This tutorial uses AWS CloudFormation to deploy an EC2 instance running a Docker container, configure Auto Scaling Groups for horizontal scaling, and set up security groups and IAM roles, all through YAML configuration files. By the end, you will have a reproducible infrastructure deployment that you can version, share, and redeploy across environments.<\/p>\n\n\n<p><strong>The series so far:<\/strong><\/p>\n<ul>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/containerization\/asp-net-core-with-gitops-dockerizing-an-api-on-aws-ec2\">ASP.NET Core with GitOps: Dockerizing an API on AWS EC2<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/devops\/asp-net-core-with-gitops-deploying-infrastructure-as-code\/\">ASP.NET Core with GitOps: Deploying Infrastructure as Code<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/devops\/asp-net-core-with-gitops-orchestrating-your-containers-with-kubernetes\/\">ASP.NET Core with GitOps: Orchestrating Your Containers with Kubernetes<\/a><\/li>\n<\/ul>\n\n\n\n\n<p>The first article of the series explained how to create a Docker image of your ASP.NET Core WebApi and deploy it to an existing server. This ensures that you can deploy your application to any server, without having to worry about the software that is installed on it; but you still had to create the server, configure it to accept traffic from port 80, connect to it through SSH and run the Docker image. To follow along with this article, make sure that you have created the Docker image and pushed it to Docker Hub.<\/p>\n\n\n\n<p>By the end of this second article, you will learn how to create your EC2 instance through code, with all the necessary configuration, and deploy the API as part of the process. As explained in the first part of the series, this reduces the risk of your application working in one environment, but failing in another, since you ensure that you always have the same configuration in all environments.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-understanding-infrastructure-as-code-and-cloudformation\">Understanding Infrastructure as Code and CloudFormation<\/h2>\n\n\n\n<p>Infrastructure as Code (or IaC) is a way to manage your servers, networks, and other elements of your cloud infrastructure, by writing code instead of manually configuring them.<\/p>\n\n\n\n<p>Most IaC tools offer a way to define your infrastructure by writing in a language like JSON or YAML, in which you can create reusable templates that you can deploy in the cloud. There are many IaC providers out there \u2013 Ansible, Azure Automation, Google Cloud Deployment Manager, AWS CloudFormation, etc. Since this series follows all the concepts using AWS, the IaC deployment process will be performed using CloudFormation. However, even on AWS, you can choose other providers, such as Ansible or Chef.<\/p>\n\n\n\n<p>As you will see in this tutorial, <a href=\"https:\/\/aws.amazon.com\/cloudformation\/\">CloudFormation<\/a> works with something called stacks \u2013 simply put, collections of resources that you can manage as a single unit (you can read more about it <a href=\"https:\/\/docs.aws.amazon.com\/AWSCloudFormation\/latest\/UserGuide\/stacks.html\">here<\/a>). CloudFormation currently supports two file types of configuration files: JSON and YAML. The documentation always shows examples in both languages, but the rest of this series will use YAML, as it contains less noise than JSON, making it easier to read and understand.<\/p>\n\n\n\n<p>Each resource has a name, a type, and a list of properties \u2013 you can configure resources the same way you would do from the AWS Console. After creating a stack, managing the resources inside it is as simple as updating the file that defines your infrastructure and then performing an <em>update<\/em> command \u2013 this will create any resources that you added in the meantime, update the existing one if necessary, or delete those resources that you eliminated from your code.<\/p>\n\n\n\n<p>Cleaning up is also easy, as deleting the stack will remove all the resources that were part of it, making sure you are not susceptible to any additional costs by forgetting to delete a resource.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-what-about-elastic-beanstalk\">What about Elastic Beanstalk?<\/h2>\n\n\n\n<p>If you are familiar with AWS, you might have heard about Elastic Beanstalk (EB). If not, you can find an introduction to it <a href=\"https:\/\/www.red-gate.com\/simple-talk\/cloud\/cloud-development\/introduction-to-aws-elastic-beanstalk\/\">here<\/a>. If you compare its features with what you will learn in this tutorial, it might seem that writing Infrastructure as Code is redundant, since there is a service to do it for you out of the box.<\/p>\n\n\n\n<p>Depending on your purposes, EB might be the way to go for you \u2013 it is easier to setup, it offers the scaling capabilities that are going to be touched in this series, and it doesn\u2019t require low-level management of resources.<\/p>\n\n\n\n<p>The reason to use CloudFormation instead of Elastic Beanstalk is being able to have your infrastructure in one place, as a single source of truth, and having the possibility of deploying that anywhere, while also being sure that it will have the intended result.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-prerequisites\">Prerequisites<\/h2>\n\n\n\n<p>Before moving on with CloudFormation, you will need to install the <em>AWS CLI<\/em> on your machine. This will give you access to working with your resources from the terminal, instead of needing to use the AWS Console in the browser. You can find installation instructions for your OS <a href=\"https:\/\/docs.aws.amazon.com\/cli\/latest\/userguide\/cli-chap-install.html\">here<\/a>. Note that if you install AWS CLI version 2, you will need to change all the commands in the article from aws to aws2.<\/p>\n\n\n\n<p>After successfully installing the CLI, you need to set up an IAM user that will be used to work with your resources.<\/p>\n\n\n\n<p>First of all, open the <em>AWS Console<\/em> and search for <em>IAM<\/em> in the <em>Find services<\/em> box.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"900\" height=\"379\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/12\/word-image.png\" alt=\"\" class=\"wp-image-85737\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Then, select <em>Users<\/em> from the left menu and click the <em>Add user<\/em> button.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"843\" height=\"238\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/12\/word-image-1.png\" alt=\"\" class=\"wp-image-85738\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Pick a name, like <em>console-user<\/em>, for the new user, and check the <em>Programmatic access<\/em> box. This allows the user to perform actions through the AWS CLI, among other tools from AWS, but it cannot be used to login to the Console. Then, click the <em>Next: Permissions<\/em> button.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"988\" height=\"633\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/12\/word-image-2.png\" alt=\"\" class=\"wp-image-85739\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>This page allows you to select which actions the user will be able to perform and what resources it can access; it is a good security measure in case someone gains access to your user. Select the <em>Attach existing policies directly<\/em> and select the <em>AdministratorAccess<\/em> policy; keep in mind that you should not be doing this in a production scenario, but AWS policies can be tough to work with, so this works for testing purposes. Click the <em>Next<\/em> buttons and then <em>Create user<\/em>.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"992\" height=\"673\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/12\/word-image-3.png\" alt=\"\" class=\"wp-image-85740\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>You should now see your newly created user, an access key ID, and a secret access key. In your terminal, run the <code>aws configure<\/code> command, then paste the required values. For the region, you can use the one that is closest to you from <a href=\"https:\/\/docs.aws.amazon.com\/AmazonRDS\/latest\/UserGuide\/Concepts.RegionsAndAvailabilityZones.html\">this list<\/a>, such as <em>eu-central-1<\/em>.<\/p>\n\n\n\n<p>Make sure that you have also pushed a Docker image to Docker Hub following the instructions in the <a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/containerization\/asp-net-core-with-gitops-dockerizing-an-api-on-aws-ec2\/\">first article<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-deploying-a-single-instance\">Deploying a Single Instance<\/h2>\n\n\n\n<p>The first step of deploying your Infrastructure as Code is to find out which resources you need to accomplish your goal. The goal, in this case, is to create an EC2 instance and deploy the previously created Docker image on it.<\/p>\n\n\n\n<p>Once you are aware of what you want to create, the easiest way to gather information is to check the <a href=\"https:\/\/docs.aws.amazon.com\/AWSCloudFormation\/latest\/UserGuide\/aws-properties-ec2-instance.html\">CloudFormation documentation<\/a> for that resource. Going through the list of properties for the EC2 instance, you can see that none of them is required.<\/p>\n\n\n\n<p>To create the same instance from the previous tutorial, you only need to provide the instance\u2019s image ID. To find the current image ID, go to the EC2 Dashboard and click Launch Instance. Scroll down the list of images until you see the free Ubuntu server. Copy the image ID and save it because you will need to supply it in several scripts.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1821\" height=\"344\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/12\/word-image-4.png\" alt=\"\" class=\"wp-image-85741\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Click Select to see the sizes. In this case, note <em>t2.micro,<\/em> which is the free tier. You will be creating your new instance from code, so you can cancel out of the steps after collecting this information.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1238\" height=\"247\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/12\/word-image-5.png\" alt=\"\" class=\"wp-image-85742\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>To write the code to deploy this instance, create a new file called <em>infrastructure.yaml<\/em>. This is all the code you need, considering you want to deploy an EC2 instance similar to the one from the previous tutorial. Be sure to replace the <code>ImageID<\/code> property with the image ID you found on the site. Note that you will need to do this each time you copy in a new version of the MainInstance section.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Description: Creating an EC2 instance.\nResources:\n  MainInstance:\n    Type: AWS::EC2::Instance\n    Properties:\n      ImageId: ami-0ac05733838eabc06\n      InstanceType: t2.micro<\/pre>\n\n\n\n<p>After saving the file and modifying the file path if necessary, you can run the following command to deploy your instance to the cloud:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">aws cloudformation create-stack --stack-name dotnet-docker --template-body file:\/\/infrastructure\/infrastructure.yaml<\/pre>\n\n\n\n<p>This will return a <code>StackId<\/code>, so you know the operation has started \u2013 it does not mean it was successful, though. The output will look similar to this:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1313\" height=\"128\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/12\/word-image-6.png\" alt=\"\" class=\"wp-image-85743\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>To check on your deployment status, go to the AWS Console and search for <em>CloudFormation<\/em> in the <em>Find services<\/em> box.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"892\" height=\"374\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/12\/word-image-7.png\" alt=\"\" class=\"wp-image-85744\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>As the stack only contains an EC2 instance, the deployment will complete quite quickly, so you should already see the <em>CREATE_COMPLETE<\/em> status for your stack.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"613\" height=\"201\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/12\/word-image-8.png\" alt=\"\" class=\"wp-image-85745\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>You can click on the stack name and choose the <em>Resources<\/em> tab to view all the resources inside the stack.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"990\" height=\"365\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/12\/word-image-9.png\" alt=\"\" class=\"wp-image-85746\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Before pulling and running the Docker image, there is one more thing that needs to be taken care of: allowing the instance to receive (only) HTTP traffic, but to send any type of request. Since you did not provide any security group for the instance, the default one is used \u2013 any traffic, to and from any port is allowed; however, this does not provide the level of security and control that you might want for your application.<\/p>\n\n\n\n<p>In the same <em>infrastructure.yaml <\/em>file, you can define a new resource, <em>SecurityGroup<\/em>, and then reference it to the instance. However, that is not the only resource that needs to be created, and this is one of the situations where trial and error will lead you to the result. To set the inbound\/outbound traffic rules, you need a security group.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  MainSecurityGroup:\n    Type: AWS::EC2::SecurityGroup\n    Properties:\n      GroupDescription: Security group for the API instances.\n      VpcId: !Ref VPC\n      SecurityGroupIngress:\n        - IpProtocol: tcp\n          FromPort: 80\n          ToPort: 80\n          CidrIp: 0.0.0.0\/0\n      SecurityGroupEgress:\n        - IpProtocol: tcp\n          FromPort: 0\n          ToPort: 65535\n          CidrIp: 0.0.0.0\/0<\/pre>\n\n\n\n<p>The security group must link to a <a href=\"https:\/\/docs.aws.amazon.com\/AWSCloudFormation\/latest\/UserGuide\/aws-resource-ec2-vpc.html\">VPC<\/a> to define outbound rules.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  VPC:\n    Type: AWS::EC2::VPC\n    Properties:\n      CidrBlock: 192.168.0.0\/16\n      EnableDnsSupport: true\n      EnableDnsHostnames: true<\/pre>\n\n\n\n<p>To have your components accessible from the internet, you need an <a href=\"https:\/\/docs.aws.amazon.com\/vpc\/latest\/userguide\/VPC_Internet_Gateway.html\">Internet Gateway<\/a> attached to your VPC.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  InternetGateway:\n    Type: AWS::EC2::InternetGateway\n  InternetGatewayAttachment:\n    Type: AWS::EC2::VPCGatewayAttachment\n    Properties:\n      InternetGatewayId: !Ref InternetGateway\n      VpcId: !Ref VPC<\/pre>\n\n\n\n<p>Furthermore, you need a way to specify that all the traffic should go to the Internet Gateway you created; for this, you need to create a <a href=\"https:\/\/docs.aws.amazon.com\/vpc\/latest\/userguide\/VPC_Route_Tables.html\">route table<\/a> and a route.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">RouteTable:\n    Type: AWS::EC2::RouteTable\n    Properties:\n      VpcId: !Ref VPC\n  DefaultRoute:\n    Type: AWS::EC2::Route\n    DependsOn: InternetGatewayAttachment\n    Properties:\n      RouteTableId: !Ref RouteTable\n      DestinationCidrBlock: 0.0.0.0\/0\n      GatewayId: !Ref InternetGateway<\/pre>\n\n\n\n<p>The EC2 is part of the default VPC if not otherwise specified, so the instance and the security group will be part of different VPCs, which means you will not be able to link them; to solve this, you need to create a subnet as part of the new VPC and place the EC2 instance inside that.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  Subnet:\n    Type: AWS::EC2::Subnet\n    Properties:\n      VpcId: !Ref VPC\n      AvailabilityZone: !Select [ 0, !GetAZs '' ]\n      CidrBlock: 192.168.0.0\/16\n      MapPublicIpOnLaunch: true<\/pre>\n\n\n\n<p>Finally, the subnet should be associated with the route table created earlier.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SubnetRouteTableAssociation:\n    Type: AWS::EC2::SubnetRouteTableAssociation\n    Properties:\n      SubnetId: !Ref Subnet\n      RouteTableId: !Ref RouteTable<\/pre>\n\n\n\n<p>Once everything is added to the file, you can perform an <em>update-stack <\/em>command to deploy the newly created resources.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">aws cloudformation update-stack --stack-name dotnet-docker --template-body file:\/\/infrastructure.yaml<\/pre>\n\n\n\n<p>Finally, with the infrastructure ready, you can add the commands to install Docker on the machine, login to Docker Hub, pull the image and run it. You do this by adding a property of the EC2 instance, called <code>UserData<\/code><em>.<\/em><\/p>\n\n\n\n<p>Since these commands contain your password for Docker Hub, which you would not want to be committed to a Git repository, you can use parameters and specify them when creating or updating the stack. This is what the instance code looks like after adding the commands (be sure to replace your image ID).<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  MainInstance:\n    Type: AWS::EC2::Instance\n    Properties:\n      ImageId: ami-0ac05733838eabc06\n      InstanceType: t2.micro\n      SecurityGroupIds: \n        - !GetAtt \"MainSecurityGroup.GroupId\"\n      SubnetId: !Ref Subnet\n      UserData:\n        Fn::Base64:\n            !Sub |\n                  #!\/bin\/bash\n                  apt-get update -y\n                  apt-get install docker.io -y\n                  docker login -username ${DockerUsername} -password ${DockerPassword}\n                  docker pull docker.io\/${DockerUsername}\/dotnet-api\n                  docker run -d -p 80:80 ${DockerUsername}\/dotnet-api<\/pre>\n\n\n\n<p>Add the parameters to a special section between <em>Description<\/em> and <em>Resources<\/em>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Parameters:\n  DockerUsername:\n    Description: The Docker Hub username.\n    Type: String\n  DockerPassword:\n    Description: The Docker Hub password.\n    Type: String<\/pre>\n\n\n\n<p>If you would like to view the whole file, you can do so <a href=\"https:\/\/github.com\/Mirch\/docker-dotnet-api\/blob\/infrastructure_as_code\/infrastructure\/infrastructure.yaml\">here<\/a>.<\/p>\n\n\n\n<p>To update the stack, you can run the <code>update-stack<\/code> command with the <em>parameters <\/em>tag; if you deleted the stack, you can run the <code>create-stack<\/code> command instead:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">aws cloudformation update-stack --stack-name dotnet-docker --template-body <a href=\"file:\/\/infrastructure.yaml\">file:\/\/infrastructure.yaml<\/a> --parameters ParameterKey=DockerUsername,ParameterValue=yourusername ParameterKey=DockerPassword,ParameterValue=yourpassword<\/pre>\n\n\n\n<p>If you go to the EC2 dashboard on the AWS console, you should see a new instance being created. Copy the public DNS and append <code>\/api\/test<\/code> to it. You should see the same JSON as in the previous tutorial. Note that if you have problems with the test, see the \u201cDebugging your instances\u201d section later in the article.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"457\" height=\"85\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/12\/word-image-10.png\" alt=\"\" class=\"wp-image-85747\"\/><\/figure>\n\n\n\n<p><strong>Read also:<\/strong> <a href=\"https:\/\/www.red-gate.com\/simple-talk\/devops\/containers-and-virtualization\/deploying-a-dockerized-application-to-the-kubernetes-cluster-using-jenkins\/\" target=\"_blank\" rel=\"noreferrer noopener\">Deploy Docker apps to Kubernetes using Jenkins<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-debugging-your-instance\">Debugging Your Instance<\/h2>\n\n\n\n<p>What happens after you deploy your instances, and something does not work as intended \u2013 such as the test endpoint not returning anything? The easiest way to figure out what went wrong is to connect to the instance via SSH.<\/p>\n\n\n\n<p>First of all, allow connections to your instances via port 22, by adding another ingress rule to the security group.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  MainSecurityGroup:\n    Type: AWS::EC2::SecurityGroup\n    Properties:\n      GroupDescription: Security group for the API instances.\n      VpcId: !Ref VPC\n      SecurityGroupIngress:\n        - IpProtocol: tcp\n          FromPort: 80\n          ToPort: 80\n          CidrIp: 0.0.0.0\/0\n        - IpProtocol: tcp\n          FromPort: 22\n          ToPort: 22\n          CidrIp: 0.0.0.0\/0\n      SecurityGroupEgress:\n        - IpProtocol: tcp\n          FromPort: 0\n          ToPort: 65535\n          CidrIp: 0.0.0.0\/0<\/pre>\n\n\n\n<p>Next, in the Instance properties, you have to specify a key-pair name. You can use the one you created during the previous tutorial which you can see by scrolling down to the Network &amp; Security section of the ECW menu.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  MainInstance:\n    Type: AWS::EC2::Instance\n    Properties:\n      KeyName: dotnet-docker-keypair\n      ImageId: ami-0ac05733838eabc06\n      InstanceType: t2.micro\n      SecurityGroupIds: \n        - !GetAtt \"MainSecurityGroup.GroupId\"\n      SubnetId: !Ref Subnet\n      UserData:\n        Fn::Base64:\n            !Sub |\n                  #!\/bin\/bash\n                  apt-get update -y\n                  apt-get install docker.io -y\n                  docker login -username ${DockerUsername} -password ${DockerPassword}\n                  docker pull docker.io\/${DockerUsername}\/dotnet-api\n                  docker run -d -p 80:80 ${DockerUsername}\/dotnet-api<\/pre>\n\n\n\n<p>The <em>update-stack<\/em> command will not achieve the intended result here, as the instance is already running. Instead, delete your stack first, by running this code.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">aws cloudformation delete-stack --stack-name dotnet-docker<\/pre>\n\n\n\n<p>Then create it again with the new configuration.<\/p>\n\n\n\n<p>Afterwards, you can follow the connection technique that you used for the instance created manually in the first article, by the SSH command. Make sure that the pem file is in a secured location.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">ssh -i \"dotnet-docker-keypair.pem\" ubuntu@{public-dns}<\/pre>\n\n\n\n<p>Once you are connected, you can check the logs for any issues that might have appeared while launching the instance, by running the following command.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">cat \/var\/log\/cloud-init-output.log<\/pre>\n\n\n\n<p>Don\u2019t forget that this method is something to be used for testing and debugging purposes only \u2013 remove such configurations when deploying production environments.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-deploying-multiple-instances\">Deploying Multiple Instances<\/h2>\n\n\n\n<p>If you remember the series introduction, one of the essential aspects was being able to deploy multiple, identical servers that run the same API. This way, if one of them fails, or if there is an update for the API, the customers are still able to use the service by being redirected to the working instances.<\/p>\n\n\n\n<p>The goal of working with multiple instances is to make it seem like the users are only interacting with one server. For this purpose, a load balancer will be placed in front of the instances \u2013 this will receive the traffic and decide where to send it, ensuring a lower response time for the clients.<\/p>\n\n\n\n<p>To achieve the deployment of multiple instances, there are some new resources that need to be added to the infrastructure:<\/p>\n\n\n\n<p>Additional subnets for different availability zones in your region; this ensures that if one availability zone fails, your API is still up and running; the <code>Subnet<\/code> and the <code>SubnetRouteTableAssociation<\/code> sections created previously should be deleted.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  Subnet1:\n    Type: AWS::EC2::Subnet\n    Properties:\n      VpcId: !Ref VPC\n      AvailabilityZone: !Select [ 0, !GetAZs '' ]\n      CidrBlock: 192.168.0.0\/24\n      MapPublicIpOnLaunch: true\n  Subnet2:\n    Type: AWS::EC2::Subnet\n    Properties:\n      VpcId: !Ref VPC\n      AvailabilityZone: !Select [ 1, !GetAZs '' ]\n      CidrBlock: 192.168.1.0\/24\n      MapPublicIpOnLaunch: true\n  Subnet1RouteTableAssociation:\n    Type: AWS::EC2::SubnetRouteTableAssociation\n    Properties:\n      SubnetId: !Ref Subnet1\n      RouteTableId: !Ref RouteTable\n  Subnet2RouteTableAssociation:\n    Type: AWS::EC2::SubnetRouteTableAssociation\n    Properties:\n      SubnetId: !Ref Subnet2\n      RouteTableId: !Ref RouteTable<\/pre>\n\n\n\n<p>A <a href=\"https:\/\/docs.aws.amazon.com\/autoscaling\/ec2\/userguide\/LaunchConfiguration.html\">Launch Configuration<\/a>, that represents the template for any instance that is going to be created; since you are not creating the instances manually anymore, this will specify the image id, the size and the user data for your servers; as part of this step, you must also delete the EC2 instance code (<code>MainInstance<\/code> section). This section contains the Image ID, so be sure to replace it.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  LaunchConfiguration:\n    Type: AWS::AutoScaling::LaunchConfiguration\n    Properties:\n      UserData:\n        Fn::Base64: !Sub |\n          #!\/bin\/bash\n          apt-get update -y\n          apt-get install docker.io -y\n          docker login -u ${DockerUsername} -p ${DockerPassword}\n          docker pull docker.io\/${DockerUsername}\/dotnet-api\n          docker run -d -p 80:80 ${DockerUsername}\/dotnet-api\n      ImageId: ami-0ac05733838eabc06\n      SecurityGroups:\n      - Ref: MainSecurityGroup\n      InstanceType: t2.micro<\/pre>\n\n\n\n<p>An <a href=\"https:\/\/docs.aws.amazon.com\/autoscaling\/ec2\/userguide\/AutoScalingGroup.html\">Auto Scaling Group<\/a> to control how many instances are created, when to create them and when to stop them.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  AutoScalingGroup:\n    Type: AWS::AutoScaling::AutoScalingGroup\n    Properties:\n      VPCZoneIdentifier:\n         - !Ref Subnet1\n         - !Ref Subnet2\n      LaunchConfigurationName:\n        Ref: LaunchConfiguration\n      DesiredCapacity: 3\n      MinSize: 2\n      MaxSize: 4\n      TargetGroupARNs:\n      - Ref: TargetGroup<\/pre>\n\n\n\n<p>A <a href=\"https:\/\/docs.aws.amazon.com\/autoscaling\/ec2\/userguide\/autoscaling-load-balancer.html\">Load Balancer<\/a> that will control the traffic for the instances inside the Auto Scaling Group.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  LoadBalancer:\n    Type: AWS::ElasticLoadBalancingV2::LoadBalancer\n    Properties:\n      Subnets:\n      - !Ref Subnet1\n      - !Ref Subnet2\n      SecurityGroups:\n      - Ref: MainSecurityGroup<\/pre>\n\n\n\n<p>A <a href=\"https:\/\/docs.aws.amazon.com\/elasticloadbalancing\/latest\/application\/load-balancer-listeners.html\">Load Balancer Listener<\/a> and a rule for it, as well as a <a href=\"https:\/\/docs.aws.amazon.com\/elasticloadbalancing\/latest\/application\/load-balancer-target-groups.html\">Target Group<\/a>, to specify how to check the health of each instance and make sure they are still running. Here is the Listener Rule section:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  ListenerRule:\n    Type: AWS::ElasticLoadBalancingV2::ListenerRule\n    Properties:\n      Actions:\n      - Type: forward\n        TargetGroupArn: !Ref TargetGroup\n      Conditions:\n      - Field: path-pattern\n        Values: [\/]\n      ListenerArn: !Ref Listener\n      Priority: 1<\/pre>\n\n\n\n<p>Add the Listener.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  Listener:\n    Type: AWS::ElasticLoadBalancingV2::Listener\n    Properties:\n      DefaultActions:\n      - Type: forward\n        TargetGroupArn:\n          Ref: TargetGroup\n      LoadBalancerArn:\n        Ref: LoadBalancer\n      Port: '80'\n      Protocol: HTTP<\/pre>\n\n\n\n<p>Also add the TargetGroup section.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  TargetGroup:\n    Type: AWS::ElasticLoadBalancingV2::TargetGroup\n    Properties:\n      HealthCheckIntervalSeconds: 10\n      HealthCheckPath: \/\n      HealthCheckProtocol: HTTP\n      HealthCheckTimeoutSeconds: 8\n      HealthyThresholdCount: 2\n      Port: 80\n      Protocol: HTTP\n      UnhealthyThresholdCount: 5\n      VpcId: !Ref VPC<\/pre>\n\n\n\n<p>You can view the file containing all the resources <a href=\"https:\/\/github.com\/Mirch\/docker-dotnet-api\/blob\/infrastructure_as_code_2\/infrastructure\/infrastructure.yaml\">here<\/a>.<\/p>\n\n\n\n<p>From the CloudFormation Stacks list, delete the original stack. You should now run the same command from earlier with <code>create-stack<\/code> and the username and password parameters. Once it finishes (and it might take longer than the previous versions, since there are many new resources added), go to the EC2 dashboard on AWS Console to inspect the resources. You should see three instances.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"697\" height=\"132\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/12\/word-image-11.png\" alt=\"\" class=\"wp-image-85748\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>You can go to any of them, copy the public DNS and append <code>\/api\/test<\/code> to it, and receive the expected JSON message from earlier. This ensures that all the instances work as expected.<\/p>\n\n\n\n<p>While on the instances page, if you scroll down in the left menu and select <em>Load Balancers<\/em>, you should see one resource.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"650\" height=\"164\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/12\/word-image-12.png\" alt=\"\" class=\"wp-image-85749\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Below, you should see a property called <em>DNS Name<\/em>, which is the URI for your load balancer. You can copy it, append <code>\/api\/test<\/code> and receive the same JSON message as from the instances. You are done!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-debugging-your-instances\">Debugging Your Instances<\/h2>\n\n\n\n<p>Enabling SSH connections to your instances is essentially the same process as the one described earlier, for the single instance \u2013 however, the key-pair will be added to the launch configuration and propagated to all the EC2 instances.<\/p>\n\n\n\n<p>The first step is adding port 22 as an ingress rule for the security group.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  MainSecurityGroup:\n    Type: AWS::EC2::SecurityGroup\n    Properties:\n      GroupDescription: Security group for the API instances.\n      VpcId: !Ref VPC\n      SecurityGroupIngress:\n        - IpProtocol: tcp\n          FromPort: 80\n          ToPort: 80\n          CidrIp: 0.0.0.0\/0\n        - IpProtocol: tcp\n          FromPort: 22\n          ToPort: 22\n          CidrIp: 0.0.0.0\/0\n      SecurityGroupEgress:\n        - IpProtocol: tcp\n          FromPort: 0\n          ToPort: 65535\n          CidrIp: 0.0.0.0\/0<\/pre>\n\n\n\n<p>Next, in the Launch Configuration properties, specify the key-pair name.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  LaunchConfiguration:\n    Type: AWS::AutoScaling::LaunchConfiguration\n    Properties:\n      KeyName: dotnet-docker-keypair\n      UserData:\n        Fn::Base64: !Sub |\n          #!\/bin\/bash\n          apt-get update -y\n          apt-get install docker.io -y\n          docker login -u ${DockerUsername} -p ${DockerPassword}\n          docker pull docker.io\/${DockerUsername}\/dotnet-api\n          docker run -d -p 80:80 ${DockerUsername}\/dotnet-api\n      ImageId: ami-0ac05733838eabc06\n      SecurityGroups:\n      - Ref: MainSecurityGroup\n      InstanceType: t2.micro<\/pre>\n\n\n\n<p>Again, don\u2019t forget to remove these settings before deploying your instances to production!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-cleaning-up\">Cleaning Up<\/h2>\n\n\n\n<p>If you are following this just for testing or learning purposes, you should avoid keeping the resources alive for too long, as it can create additional costs or reach the free tier limits; since all the resources are part of a stack, deleting them is as simple as deleting the stack. You can do it from the console, by running.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">aws cloudformation delete-stack --stack-name dotnet-docker<\/pre>\n\n\n\n<p>You can also remove the stack from the CloudFormation dashboard in the AWS Console, by selecting the stack and clicking the <em>Delete<\/em> button.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"869\" height=\"237\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/12\/word-image-13.png\" alt=\"\" class=\"wp-image-85750\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-what-is-next\">What is Next?<\/h2>\n\n\n\n<p>It might seem that the goal of the tutorials is more or less achieved: you can pack the code from your Git repository in a Docker image and deploy it to multiple instances, ensuring the high availability of your application. But you are still handling most of this process manually.<\/p>\n\n\n\n<p>Going forward, you will learn how to use Kubernetes to orchestrate your Docker containers, and how to automate the deployment process.<\/p>\n\n\n\n<p><strong>Read also:<br><\/strong><a href=\"https:\/\/www.red-gate.com\/simple-talk\/devops\/containers-and-virtualization\/asp-net-core-with-gitops-dockerizing-an-api-on-aws-ec2\/\" target=\"_blank\" rel=\"noreferrer noopener\">Part 1: Dockerizing an API on AWS EC2<\/a><br><a href=\"https:\/\/www.red-gate.com\/simple-talk\/devops\/containers-and-virtualization\/asp-net-core-with-gitops-orchestrating-your-containers-with-kubernetes\/\" target=\"_blank\" rel=\"noreferrer noopener\">Part 3: Orchestrating Containers with Kubernetes<\/a><br><a href=\"https:\/\/www.red-gate.com\/simple-talk\/devops\/securing-the-devops-pipeline-part-2-hardening-kubernetes-and-cloud-security\/\" target=\"_blank\" rel=\"noreferrer noopener\">Securing your DevOps pipeline and cloud infrastructure<\/a><\/p>\n\n\n\n<section id=\"my-first-block-block_b527b398b52f7e88fa8ef2c9a32a9f4a\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Simple Talk is brought to you by Redgate Software<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Take control of your databases with the trusted Database DevOps solutions provider. Automate with confidence, scale securely, and unlock growth through AI.                                    <\/div>\n            <\/div>\n                            <a href=\"https:\/\/www.red-gate.com\/solutions\/overview\/\" class=\"btn btn--secondary btn--lg\">Discover how Redgate can help you<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs: ASP.NET Core with GitOps: Deploying Infrastructure as Code<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. What is the difference between CloudFormation and Terraform?<\/h3>\n            <div class=\"faq-answer\">\n                <p>CloudFormation is AWS-native and works only with AWS resources, while Terraform is cloud-agnostic and supports multiple providers. CloudFormation uses YAML\/JSON templates and manages resources through stacks. Terraform uses HCL (HashiCorp Configuration Language) and maintains state files. Choose CloudFormation if you are all-in on AWS; choose Terraform for multi-cloud environments.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. Why use Infrastructure as Code instead of the AWS Console?<\/h3>\n            <div class=\"faq-answer\">\n                <p>IaC provides version control, reproducibility, and consistency across environments. Manual console configuration cannot be tracked, shared, or reliably reproduced. With IaC, your entire infrastructure is defined in code that can be reviewed, tested, and deployed automatically &#8211; reducing human error and enabling disaster recovery through redeployment.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>Learn to deploy Infrastructure as Code with AWS CloudFormation. This tutorial covers EC2 instance creation, Auto Scaling Groups, security groups, and IAM configuration using YAML templates.&hellip;<\/p>\n","protected":false},"author":316106,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143538,143521],"tags":[95506],"coauthors":[47840],"class_list":["post-85736","post","type-post","status-publish","format-standard","hentry","category-dotnet-development","category-tools","tag-automate"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/85736","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/316106"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=85736"}],"version-history":[{"count":7,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/85736\/revisions"}],"predecessor-version":[{"id":109035,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/85736\/revisions\/109035"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=85736"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=85736"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=85736"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=85736"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}