Deploying Your App with AWS CloudFormation and CodePipeline - A Comprehensive Guide
Tags: infrastructureascode, cicd, aws, containers
Published: 2024-01-03 15:59:31


I wrote a blog post earlier on [how to create and deploy a containerized API-driven Python Streamlit web app using CICD on AWS](https://dev.to/aws-builders/how-to-build-and-deploy-an-api-driven-streamlitpython-microservice-on-aws-3bkj). Although the steps I explained using ClickOps "AWS Console" were beginner-friendly, it's best to use a more automated method that's less prone to errors during production or big-scale development stages. That's where Infrastructure as a Code (IaC) comes in.  

## What is Infrastructure as Code (IaC)?

IaC refers to the process of managing and provisioning infrastructure through code instead of manual processes. The main benefit of IaC is that it automates the creation and management of infrastructure environments, so developers can focus on building and enhancing applications. This approach helps to control costs, reduce risks, and accelerate the process.

Another advantage of IaC is that it allows you to replicate the same environment and deploy it in another location using the same code. By designing the IaC in a modular way, it can be used anywhere with fewer restrictions, making deployment more agile. The more you design the IaC to be modular, the more agile the deployment will be.

IaC is commonly used in software development to build, test, and deploy applications. 

In this article, we will explore a similar concept. I will explain the steps required to build and deploy the same environment and Web App "Streamlit Web App" used in the previous article, using [AWS CloudFormation](https://aws.amazon.com/cloudformation/) stacks and [CodePipeline](https://aws.amazon.com/codepipeline/) while increasing the Web App's scalability. 

In my next blog post, I will demonstrate how to utilize this setup to integrate your web app with a GenAI model deployed using Amazon SageMaker JumpStart. 

---
---
## Getting Started

To begin with, we will create two ECR repositories. The first one will store the base image, while the second will store the web app image. We will then pull the base-image from Docker Hub and push it to the appropriate ECR repository. In this demo, we used the python:3.10-slim-buster as the base-image.  You may follow the steps [here](https://github.com/astroveny/WebApp-Iac-CICD/blob/main/guide/Step1-ECR-Repo-Base-Image.md).

Next, we need to prepare the content of the GitHub repository. This involves adding the source code, deployment scripts, definition files (such as Dockerfile, buildspec.yml, and appspec.yml), the "cfn" directory that contains all Cloudformation stacks, and the bin directory that contains all bash scripts to deploy the stacks. You can take a look at this [repository sample](https://github.com/astroveny/WebApp-Iac-CICD) to get an idea of how it should be structured. Each stack will have AWS resources with similar lifecycles and ownership.

After creating the repository, create a folder for each stack that contains a template.yaml file and parameters.json to define parameter values. Here's an example:
```bash
├── cfn
│   ├── compute
│   │   ├── parameters.json
│   │   └── template.yaml
│   ├── cdn
│   │   ├── parameters.json
│   │   └── template.yaml
```  

Lastly, we will create a deployment script for each stack inside the [bin directory](https://github.com/astroveny/WebApp-Iac-CICD/tree/main/bin) and then deploy them in a specific order.


![GitHub Repository](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gi0lyjncl3j56ht4qnlx.png "GitHub Repository")


>> **Note:** While deploying directly to EC2 instances can be suitable for development, testing, or small-scale applications, for production environments, it's generally best to use container orchestration services like ECS or EKS for improved scalability, reliability, and maintainability.  
We can discuss how to create a similar environment using either ECS or EKS, but we'll save that for another blog post. The benefit of this setup is that during the development or testing phases, you can deploy a different container using the same resources created by the stacks. You can use the same repository or another one, and you would only need to make a few changes such as updating the allowed ports by the Security Groups, Dockerfile, and the base image.

Now that the structure of the environment is in place, we can proceed to building the stacks.

---
---  

## Building The Stacks 

We will create the necessary files for each stack, go through the configuration of the resources in each stack then develop the deployment scripts. You can clone the [repository sample](https://github.com/astroveny/WebApp-Iac-CICD) and update the files to suit your setup. In general, we will start the template by defining the parameters for each resource and use an external parameter file named parameters.json. Next, is the resource section, which will contain the configuration details of each resource. Finally, we will use the output sections to export the resource values that will be used in other stacks.

Before we proceed to the next steps, take a look at the solution diagram


![Solution architect](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/skmldcv6nxzeb4bbipyu.gif "Solution architect")

First, we will start with the Networking stack to lay down the necessary network resources, next is the ALB stack to distribute the traffic to the Web App instances, then the Compute stack and the CDN stack. 
Here is a high-level diagram of each stack and the cross-stack references.


![AWS CloudFormation Stacks](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/najrr3s8v9umxutv1wfo.png "AWS CloudFormation Stacks")


![AWS CloudFormation CICD Stacks](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cdbsqbmxpwwhe8wfwixt.png "AWS CloudFormation CICD Stacks")

### 1. Networking Stack

The Networking stack will create the base networking resources necessary for this environment. This will include the VPC that has an Internet Gateway, Route Table, two Availability Zones for redundancy, and one public subnet per Availability Zone.
To build the stack, create a Cloudformation template file inside the `cfn/networking` directory similar to this ([template.yaml](https://github.com/astroveny/WebApp-Iac-CICD/blob/main/cfn/networking/template.yaml)) file. 

The template description includes a list of the resources that will be generated by it.

```yml
Description: |
  Networking stack Resources:
  - VPC
  - IGW
  - Availability Zone
  - Public Route Table
    - route to the IGW
    - route to Local
  - 2 Public Subnets
```
Here are the resources:

![AWS VPC Resources](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yebcixi2ex3s5gkk6myi.png "AWS VPC Resources")
---
### 2. ALB Stack

The ALB stack will create an Application Load Balancer to decouple the traffic from the Web App and distribute the traffic load across the Web App instances. Additionally, it includes a Target Group that specifies the target instances, and the ALB Security Group manages the traffic security. Create a CloudFormation template file inside the `cfn/alb` directory similar to this ([template.yaml](https://github.com/astroveny/WebApp-Iac-CICD/blob/main/cfn/alb/template.yaml)).  

The template description includes a list of the resources that will be generated by it.

```yml
Description: |
  WebApp ALB Stack:
  - ALB
    - ipv4
    - Internet facing
    - certificate attached from Amazon Certification Manager (ACM)
  - HTTPSListener
    - HTTPS to WebApp Target Group
  - Target Group
  - ALB Security Groups
``` 
  
Going forward we will use an external parameters file (parameters.json) to define some parameters, so you will have to create it in the same directory similar to this ALB stack parameter file([parameters.json](https://github.com/astroveny/WebApp-Iac-CICD/blob/main/cfn/alb/parameters.json)) file.  
Update the values for each of the following ParameterKey:
- WebAppPort "The port your Web App will use"
- CertificateArn "ACM certificate Arn"

Here are the resources:

![AWS ALB Resources](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eh6mpfvogkvn21arpybf.png "AWS ALB Resources")
---
### 3. Compute Stack

In the Compute stack, we will create an IAM Role, Launch Template, and an Auto Scaling Group. These will define the EC2 instance configurations and types, as well as the scalability capacity required for the Web App. Additionally, we will create a Security Group that will allow the required traffic.

Create a template file inside the `cfn/compute` directory similar to this ([template.yaml](https://github.com/astroveny/WebApp-Iac-CICD/blob/main/cfn/compute/template.yaml)) file.  

The template description includes a list of the resources that will be generated by it.

```yml
Description: |
  WebApp Compute Stack:
  - Launch Template
  - LT Security Group
  - ASG
  - IAM Role and Policies
```

Create the Compute stack parameters file inside the same directory, similar to this ([parameters.json](https://github.com/astroveny/WebApp-Iac-CICD/blob/main/cfn/compute/parameters.json))file

The content of the user-data can be changed by encoding the new file using base64 `base64 user-data` and updating the parameters.json file with the new value.

Here are the resources:

![AWS Compute Resources](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hk9wklow48lmesurkrkb.png "AWS Compute Resources")
---
### 4. CDN Stack

In this stack, we will create a CloudFront Distribution with an ALB origin. Assuming that you have already a Domain Name registered in the Route 53 hosted zone, we will create a record to publish the distribution URL for the Web App.

Create a template file inside the `cfn/cdn` directory similar to this ([template.yaml](https://github.com/astroveny/WebApp-Iac-CICD/blob/main/cfn/cdn/template.yaml)) file.  

The template description includes a list of the resources that will be generated by it.

```yml
Description: |
  CDN Stack:
  - CloudFront Distribution
  - Route 53 Hosted Zone Record
```

Next, we will create the parameters file inside the same directory, similar to this [parameters.json](https://github.com/astroveny/WebApp-Iac-CICD/blob/main/cfn/cdn/parameters.json) file.  
Update the values for each of the following ParameterKey:

- DomainName
- HostedZoneRecord
- DomainHostedZoneId
- CustomDomainName
- CertificateArn

---  
### 5. CICD Stack

Lastly, we will create a CodePipeline Stack and two nested stacks to create and set up CodeBuild and CodeDeploy. The pipeline will build and then deploy the container image as you update the Web app code inside the GitHub repository. 

First, we need to create a pipeline artifact bucket that will be defined as a parameter. Run the following aws cli to create the bucket:  

```bash
aws s3api create-bucket --bucket codepipeline-webapp-$(date +%Y%m%d%H%M%S) --region us-east-1
```

Second, create the main pipeline template file inside the `cfn/cicd` directory. The file should be similar to this ([template.yaml](https://github.com/astroveny/WebApp-Iac-CICD/blob/main/cfn/cicd/template.yaml)) file.  

The template description includes a list of the resources that will be generated by it.

```yml
Description: |
  WebApp CICD Stack:
  - CodePipeline IAM Role
  - CodeDeploy IAM Role
  - Web App Pipeline
```
To be able to connect to the GitHub repository, we need first to create a CodeStar connection with the GitHub account. 
- Run the following command to create a new CodeStar connection:  

```bash
aws codestar-connections create-connection --provider-type GitHub --connection-name MyGitHubConnection
```
- make sure the repo is public otherwise you need to allow access to the private repo on GitHub
- Go to the AWS CodePipeline console
- Select **Settings** on from the left-side panel then click on **Connections**
- Select the connection name "MyGitHubConnection"
- Then click on "**Update pending connection**"

Next, we will create the parameters file inside the same directory, similar to this [parameters.json](https://github.com/astroveny/WebApp-Iac-CICD/blob/main/cfn/cicd/parameters.json) file.  
Update the values for each of the following ParameterKey:

- GitHubConnectionArn
- GitHubRepo
- GitHubBranch
- GitHubSourceRepo
- ArtifactBucketName

Here are the resources:

![AWS CICD Resources](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i2kozl0f2016mmm0yb4f.png "AWS CICD Resources")

#### 5.1 CodeBuild Stack

The CodeBuild stack will create a CodeBuild project and the necessary IAM Role to build the container image and then push it to the ECR repository 

Create a template file inside the `cfn/cicd/nested` directory similar to this ([codebuild.yaml](https://github.com/astroveny/WebApp-Iac-CICD/blob/main/cfn/cicd/nested/codebuild.yaml)) file.  

The template description includes a list of the resources that will be generated by it.

```yml
Description: |
  AWS CodeBuild Project as part of the CICDStack:
  - CodeBuild Service Role
  - CodeBuild Project
```


#### 5.2 CodeDeploy Stack

This Stack will create CodeDeploy Application and Deployment Group to deploy the Web app Container. For this demo purposes, we will use (in-place deployment), but it is recommended to create a Blue/Green deployment with multiple compute resources to ensure the availability of the Web App.

Create a template file inside the `cfn/cicd/nested` directory similar to this ([codedeploy.yaml](https://github.com/astroveny/WebApp-Iac-CICD/blob/main/cfn/cicd/nested/codedeploy.yaml)) file.  

The template description includes a list of the resources that will be generated by it.

```yml
Description: |
  - AWS CodeDeploy Application
  - Deployment Group
```

  
---

## Deployment Scripts

At this stage we will create the bash deployment script for each stack, we will follow the same order we used to create the stacks by starting with the networking deployment script and so on.

You will have to define the following variables inside each script:
```bash
BUCKET="" # Cloudformation bucket
REGION="us-east-1" # replace with the relevant region
STACK_NAME=""
```
  
>>NOTE: if you change the STACK_NAME then you have to change the relevant stack name example: "NetworkingStack" value, in  parameters.json of the other stacks.

If you do not have an existing Cloudformation artifact bucket, create a new one using the following aws cli:  
```bash
aws s3api create-bucket --bucket cfn-artifact-$(date +%Y%m%d%H%M%S) --region us-east-1
```

Before we start, we need to install the following packages:

- [AWS CloudFormation Linter](https://github.com/aws-cloudformation/cfn-lint) Validate the syntax and properties of resources by running cfn-lint with each template.yaml file. 
- Install JQ (JSON Processor) to transform the structure of the parameters.json into key=value structure. We will use jq tool inside the deployment scripts


### Networking Stack Deployment Script

Create a bash file inside the bin directory similar to ([networking-deploy](https://github.com/astroveny/WebApp-Iac-CICD/blob/main/bin/networking-deploy)).
The script will deploy the Networking Stack. 


### ALB Stack Deployment Script

Create a bash file inside the bin directory similar to ([alb-deploy](https://github.com/astroveny/WebApp-Iac-CICD/blob/main/bin/alb-deploy)). This script will deploy the ALB Stack.

### Compute Stack Deployment Script

Create a bash file inside the bin directory similar to ([compute-deploy](https://github.com/astroveny/WebApp-Iac-CICD/blob/main/bin/compute-deploy)). This script will deploy the Compute Stack

### CDN Stack Deployment Script

Create a bash file inside the bin directory similar to ([cdn-deploy](https://github.com/astroveny/WebApp-Iac-CICD/blob/main/bin/cdn-deploy)). This script will deploy the CDN Stack

### CICD Stack Deployment Script

Create a bash file inside the bin directory similar to ([cicd-deploy](https://github.com/astroveny/WebApp-Iac-CICD/blob/main/bin/cicd-deploy)). This script will deploy the CICD Stack

  
## Implementation


### Build The Container Image

Although CodeBuild can be used to build the first version of the Web App image, the build project stack needs to be created outside the pipeline.
For now, we will just build the image using docker locally and then push it to the ECR repository. [Here is an example](https://github.com/astroveny/WebApp-Iac-CICD/blob/main/guide/Step2-Build-WebApp-Image.md)


### Let's Recap

We have stored the base-image and Web App image in the appropriate ECR repository. The definition files are ready and located in the root directory. The stack's templates and parameters files are in their corresponding stack folder, and all deployment scripts are available in the bin directory.

### Stacks Deployment

We will walk through the steps required to deploy each stack in the right order. Starting with the Networking stack, that will create the necessary networking resources. Next, is the ALB stack which will create the ALB for the EC2 instances. After that, we will deploy the Compute stack to create the EC2 instances. Then, we will deploy the CDN stack which will create a CloudFront distribution and add it to the Route 53 host zone record. Finally, we will deploy the CICD stack which will create the pipeline to build and deploy future changes in the code.

#### Stacks Deployment Order

1. Networking Stack: networking-deploy
2. ALB Stack: alb-deploy
3. Compute Stack: compute-deploy
4. CDN Stack: cdn-deploy
5. CICD Stack: cicd-deploy
>> Note: Make sure all scripts inside the bin directory are executable 

- Go to bin directory then run the first script `./networking-deploy`
- Copy the ARN from the output, similar to this

```bash
Waiting for changeset to be created..
Changeset created successfully. Run the following command to review changes:
aws cloudformation describe-change-set --change-set-name arn:aws:cloudformation:us-east-1:XXXXXXXXXXXX:changeSet/awscli-cloudformation-package-deploy-XXXXXXXXXX/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX
```

- To execute the change set, run the command below with the ARN you copied.

```bash
aws cloudformation execute-change-set --change-set-name arn:aws:cloudformation:us-east-1:XXXXXXXXXXXX:changeSet/awscli-cloudformation-package-deploy-XXXXXXXXXX/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX
```

- Go to the AWS Cloudformation console 
- Select the stack name and then **Event** tab to check the status of the stack
- Once the status changes to "CREATE_COMPLETE", go to the next step

Repeat the above steps for the rest of the stack in the right order using the relevant script. Once all stacks are deployed successfully, try to access the Web App using the domain name you used in the CDN stack.