How to Build and Deploy an API-Driven Streamlit Python Microservice on AWS
Tags: cicd, python, streamlit, aws
Published: 2023-10-25 05:29:02

I wanted a feature that did not exist in the trading platform I'm using, so I built it as a Web App API-based microservice. To achieve this, I first read through the platform API documentation to gather the necessary parameters. 

I then started building the application by creating a CLI version using Python. To ensure that each API parameter was functional, I tested them separately in a demo environment, followed by testing the entire application in a pre-production environment. 

Recently, I came across several AI workshops that utilized the Streamlit framework for building plotting applications in a simple manner. Given the ease of use of Streamlit, I decided to build the trading web app version using Sreamlit as a microservice.

In this scenario, the communication between the frontend and the backend is through the API, the frontend is the Web App and the backend is the trading platform.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gkbylbxocbnb1hjja9t4.png)
    

### Streamlit App

Streamlit is an open-source app framework written in Python. It is similar to Flask and is used for creating simple web applications quickly and easily. While it is commonly used for projects related to Machine Learning and Data Science, it works perfectly with major Python libraries and can include HTML code or markdown, making it an excellent choice for building simple applications. 
As a beginner to Streamlit, I used GenAI to convert my Python code to Streamlit code. It didn't take long for me to create a functional web app. I then tested and refactored the app until I reached the final version.

---   

## Building the Microservice

During the preparation phase of building the web app microservice, I first created an ECR repository named 'webapp-base-image' to store the container base-image. Then, I pushed the Docker base-image (python:3.10-slim-buster) to it. Next, I created another ECR repository to store the Web App container image. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j7sck1uyb2wh11gobg7e.png)  

After that, I created a GitHub repository and pushed the source code and artifacts to the repository directory. Then, I created the Dockerfile required to build the Web App container.  Finally, to build the CICD pipeline using CodePipeline, I created 'buildspec.yml' for CodeBuild and 'appspec.yml' for CodeDeploy.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s9ppszd0w3uo9pvzeg3r.png)

It's important to note that the usage and content of these files would vary depending on the chosen computing service.

The following are the available compute options on AWS to deploy the container, each would require a different procedure and minor configuration changes.

- AWS App Runner (the fastest option, but limits infrastructure control) 
- Fargate (Serverless container service) 
- EC2 instance (Compute service with more control, but requires OS/Apps maintenance)

After the source code, artifacts, and configuration files were added to the web app repo, the container image was ready for deployment. I deployed the container on a free-tier EC2 instance as explained in the next phase.


### Resources Creation 

To build and deploy the Web App, I first created the necessary resources using an EC2 instance as the host, an Application Load Balancer to decouple the TLS connection from the EC2 instance and provide load balancing for future scaling, a Build project to package and build the container using CodeBuild, CodeDeploy application to deploy the container on the EC2 instance, and finally CodePipeline to run the CICD pipeline.

Afterward, I created the remaining resources that are needed to access the Web App via the Internet, including the CloudFront distribution and a Route 53 hosted zone record.



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ohkvdklsq1dgzeoqhplz.png)


[In an upcoming blog post](https://dev.to/aws-builders/deploying-your-app-with-aws-cloudformation-and-codepipeline-eea), I will explain how to optimize this solution to improve scalability and make it more cost-effective using IaC.

Next, are the steps required to implement this solution.


## Implementation 

We will go through the steps required to build and deploy the Web App container.

### Step 1: Create an Amazon Elastic Container Registry and Base-Image.

First, we will create an ECR repository to store the base-image, pull the image from Docker Hub, then push it to the ECR repository.
After that, we will create another ECR repository to store the Web App container image.

#### Step 1a: Create an ECR repository for the Base-image

- Run the following aws command to create the ECR repository:
    
```bash
aws ecr create-repository --repository-name webapp-base-image --image-tag-mutability MUTABLE
```

#### Step 1b: Create the Base-Image

- Set the Env variable on your machine
  ```bash
  export AWS_ACCOUNT_ID=YourAWS-ID
  export AWS_DEFAULT_REGION=AWS-Region
  export ECR_PYTHON_URL="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/webapp-base-image"
  ```
- Login to ECR
    
  ```bash
  aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin "$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com"
  ```
- Pull the image from Docker Hub
    
  ```bash
  docker pull python:3.10-slim-buster
  ```
- Tag the image
  
  ```bash
  docker tag python:3.10-slim-buster $ECR_PYTHON_URL:3.10-slim-buster
  ```
- Push the image to ECR repository
  
  ```bash
  docker push $ECR_PYTHON_URL:3.10-slim-buster
  ```
      

#### Step 1c: Create ECR repository for the Web App Container Image
    
- Run the following aws command to create the ECR repository:

```bash
aws ecr create-repository --repository-name webapp --image-tag-mutability MUTABLE
```


---  
  
### Step 2: Create a GitHub repository.

We will need to [create a new GitHub repository](https://docs.github.com/en/get-started/quickstart/create-a-repo) for the Web App. Once done, you can clone the repository on the local machine and move the source code and artifacts to the root directory of the repository. 

#### Step 2a: Create Python requirements.txt

Next, you need to create the Python requirements.txt file which will include **streamlit** and other required packages and libraries for your Web App. 
  
```bash
$ touch requirements.txt
$ echo 'streamlit' > requirements.txt
```  
  
#### Step 2b: Create Dockerfile

Then create a Dockerfile similar to the following

```dockerfile
# app/Dockerfile

# use the ECR repo base-image link
FROM [YourAccountID].dkr.ecr.[YourRegion].amazonaws.com/[ECR-repo-base-image]:3.10-slim-buster

WORKDIR /app


COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt &&\
    apt-get update -y &&\
    apt-get install curl -y 

COPY . .

EXPOSE 8501

HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health

# replace YourWebApp.py with the relevant file name
ENTRYPOINT ["streamlit", "run", "YourWebApp.py", "--server.port=8501", "--server.address=0.0.0.0"]
```
  
#### Step 2c: Create the Deployment Scripts

Then create a 'scripts' directory within the repository's root directory that will contain the deployment scripts, similar to ['start_app.sh'](https://github.com/astroveny/WebApp-Microservice-ST/blob/main/scripts/start_app.sh) and ['stop_app.sh'](https://github.com/astroveny/WebApp-Microservice-ST/blob/main/scripts/stop_app.sh).   
  

#### Step 2d: Create the buildspec.yml

Create the [buildspec.yml](https://github.com/astroveny/WebApp-Microservice-ST/blob/main/buildspec.yml) at the GitHub repository's root directory. 
CodeBuild will use the buidlspec.yml configuration file to build the container image and then store it in the ECR repository.


#### Step 2e: Create the appspec.yml

Create the [appspec.yml](https://github.com/astroveny/WebApp-Microservice-ST/blob/main/appspec.yml) at the GitHub repository's root directory. CodeDeploy will use the appspec.yml configuration file to deploy the container on the EC2 instance.

---    

### Step 3: Create the IAM Roles

Before creating the required resources, you will have to create IAM Roles for the EC2 instance and CodeDeploy.
It is recommended to customize each policy and create an inline policy to restrict access to the required resources, but for simplicity, we will use the policies managed by AWS 

#### Step 3a: IAM Role for EC2 instance

This role has 2 policies to allow EC2 instance access to S3 and ECR ("AmazonEC2ContainerRegistryPowerUser", "AmazonS3ReadOnlyAccess")


#### Step 3b: IAM Role for CodeDeploy

This role has 2 policies to allow CodeDeploy access to EC2, ELB, Autoscaling and ECR ("AmazonEC2ContainerRegistryPowerUser", "AWSCodeDeployRole")

---    

### Step 4: Create an AWS EC2 instance

You will create an instance and attach this [user-data file](https://github.com/astroveny/WebApp-Microservice-ST/blob/main/user-data) which will install the codedeploy-agent, and docker.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3osb8yoyl60ji1kzwvjp.png)

1. Go To **AWS EC2 console**
2. Choose **Launch Instance**.
3. Enter the instance **Name**
4. **Application and OS Images:** Choose AMI "eg: Amazon Linux 2023 AMI"  
5. Choose **Instance type** "eg: t2.micro"
6. Choose **Key pair (login)**
7. **Network settings:** Enter the following details
   - **VPC:** "eg: default VPC"
   - **Subnet:** "eg: SubnetPub1"
   - **Auto-assign public IP:** Enable
   - **Security Group:** Select Create security group
     - Security group name: "eg: WebApp-SG"
     - Skip Inbound rules for now
8. **Configure storage:** add storage volume
9. **Advanced details:**
   - **IAM instance profile:** Choose the IAM role created in the previous step
   - **User data:** upload the [user-data file](https://github.com/astroveny/WebApp-Microservice-ST/blob/main/user-data)
10. Choose **Launch instance.**

---    

### Step 5: Create an Application Load Balancer 

Before creating the Load Balancer, create a **Security Group** with an Inbound Rule (**Type:** HTTPS; **Source:** Custom 0.0.0.0/0) and note the security group ID.

#### Step 5a: Configure a target group

Configure a target group then register the EC2 instance as a target

1. Go to the AWS EC2 console
2. Choose **Target Groups** from the navigation pane
3. Choose **Create target group**
4. **Basic configuration:** 
   - **Target type:** Select **Instance**
   - **Target group name:** Enter a name "WebApp-TG"
   - Select **Protocol** &  **Port**
   - Select **IP address type:** IPv4
   - Select **VPC:** "eg: Default VPC"
5. **Health checks:**
   - Select **Health check protocol:** HTTP
   - Select **Health check path:** enter the health check route or keep the default / "eg: /HeathCheck"
6. Choose **Next** to register a target
7. **Register targets:**
   - Select the instance created before, and the port, and then choose **Include as pending below**
   - Choose **Create target group**.

#### Step 5b: Create a load balancer

Once the target group is ready, set up an application load balancer.

1. Go to the AWS EC2 console
2. Choose **Load Balancers** from the navigation pane
3. Choose **Create Load Balancer**.
4. Under **Application Load Balancer**, choose **Create**
5. **Basic configuration:**
   - **Load balancer name:** Enter a name for your load balancer "eg: WebApp-ALB**
   - **Scheme:** choose Internet-facing
   - **IP address type:** IPv4
6. **Network mapping**
   - **VPC:** select the VPC "eg: Default VPC"
   - **Mappings:** Choose the subnets
7. **Security groups:** Select the security group created earlier
8. **Listeners and routing:**
   - **Protocol:** HTTPS
   - **Port:** 443
   - **Default action:** Select the target group created above
9. **Secure listener settings:**
    - **Default SSL/TLS server certificate:** Select **Certificate source**
10. Choose **Create Load Balancer** 

#### Step 5c: Update EC2 instance security group

Now that ALB is ready, update the EC2 instance security group to allow ingress connection from ALB using a specific port

1. Go to the AWS EC2 console
2. Choose **Security Groups** from the navigation pane
3. Choose the security group created for the EC2 instance
4. Under **Inbound rules**  Choose **Edit inbound rules**
5. Choose **Add rule** 1
   - **Type:** Custom TCP
   - **Port:** Enter the web app port
   - **Source:** Custom - select the ALB security group
6. Choose **Add rule** 2
   - **Type:** ssh
   - **Source:** select My IP
7. Choose **Save rules**

---   

### Step 6: Create a CodeBuild project

The build project will build the container image and then push it to the ECR repository. You will need to follow the steps below to create the build project.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eu14t49yevjf55hdmg4r.png)

1. Go to the AWS CodeBuild console
2. Choose **Create build project**
3. **Project configuration:**
   - **Project name:** Enter the project name
4. **Source:**
   - **Source provider:** Select Github
   - follow the instructions to connect (or reconnect) to GitHub and authorize access to AWS CodeBuild
   - **Repository:** Select Public repo, or repo in your GitHub account
   - **GitHub repository:** Enter the repo link "eg: https://github.com/YourGitHubUser/RepoName.git"
   - **Source version:** Enter the branch name
   - **Webhook** optional: Rebuild every time a code change is pushed to this repository
5. **Environment:**
   - **Environment image:** Managed image
   - **Operating system:** Amazon Linux
   - **Runtime(s):** Standard
   - **Image:** 5.0 or latest
   - Select **Privileged**
   - **Service role:** select **New service role**
   - **Role name:** Enter a service role name
6. **Buildspec:**
   - **Build specifications:** select **Use a buildspec file**
   - *By default, CodeBuild looks for a file named buildspec.yml in the source code root directory*
7. **Artifacts:** select *No artifacts* for now, it will be updated during the setup of CodePipeline 
8. Choose **Create build project**
9. Go to AWS IAM console
10. Choose **Roles** from the navigation pane
    - Search & select the service role name you entered while setting up the build project
    - Choose **Add permissions:** select **attach policy**
    - Search & select policy: AmazonEC2ContainerRegistryPowerUser
    - Choose **Add permission**
    
---   

### Step 7: Create an AWS CodeDeploy Application

In this demo, you will create a CodeDeploy application for an in-place deployment using the CodeDeploy console. Otherwise, it is recommended to create a Blue/Green deployment with multiple compute resources to ensure the availability of the Web App.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m4boa2wqwv61v4v1bgb4.png)

#### Create the Application

1. Go to the AWS CodeDeploy console.
2. expand **Deploy**, and then choose **Getting started**.
3. Choose **Create application**.
4. **Application configuration:**
   - **Application name:** Enter an application name.
   - **Compute platform:** EC2/On-premises.
   - Choose **Create application**.
5. Choose the application name.
6. Choose **Create deployment group** under **Deployment groups** tab.
7. **Deployment group name:** Enter a deployment group name.
8. **Service role:** Select the service role created previously for CodeDeploy.
9. **Deployment type:** In-place.
10. **Environment configuration:**
    - Select **Amazon EC2 instances**
    - Enter the tag key used while creating the EC2 instance.
    - Enter the tag value. 
11. **Install AWS CodeDeploy Agent:** Never
12. **Deployment configuration:** CodeDeployDefault.AllAtOnce
13. Choose **Create deployment group**.

---   

### Step 8: Create an AWS CodePipeline

Now that build GitHub repository, build project, and CodeDeploy application are ready for the pipeline, you will follow the steps below to configure it.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ee96l6a8yfyfh9jki9z.png)

#### Step 8a: Setup the Pipeline

1. Go to the AWS CodePipeline console.
2. Choose **Create pipeline**.
3. **Pipeline settings:**
   - **Pipeline name:** Enter the pipeline name.
   - **Service role:** Choose **New service role** or **Existing service role** if you have an existing role.
4. **Source:**
   - **Source provider:** GitHub (version 2)
   - **Connection:** Choose exiting Github connection or choose **Connect to Github** to create a new connection.
   - **Repository name:** Enter the repository name "eg: YourGitHubUser/WebAppRepo"
   - **Branch name:** Enter the branch name.
   - **Output artifact format:** CodePipeline default.
5. **Build:**
   - **Build provider:** AWS CodeBuild.
   - **Region:** Select your region.
   - **Project name** Choose the project name created earlier.
   - **Build type:** Single build.
6. **Deploy:**
   - **Deploy provider:** AWS CodeDeploy
   - **Region:** Enter your region.
   - **Application name:** Choose the application you created earlier. 
   - **Deployment group:** Choose the deployment group you created earlier. 
7. Review the configuration then choose **Create pipeline**

---   

#### Step 8b: Run the Pipeline

The pipeline will run once it is created. starting by running the Source stage to get the files from the GitHub repository, then running the Build stage to build the container image then push it to the ECR repository. Finally, run the Deploy stage which will deploy the container on the EC2 instance.

---   

### Step 9: CloudFront Distribution

You may choose to use CloudFront to cache your web app content and bring it closer to your users. This can help reduce the load on the ALB, resulting in lower costs. Additionally, you can add another layer of security by configuring WAF/Shield. However, it's important to note that using CloudFront is an optional step.

#### Step 9a: Create a CloudFront Distribution

Follow the steps below to create a CloundFront Distribution.

1. Go to the AWS CloudFront console.
2. Choose **Create distribution**
3. **Origin:**
   - **Origin domain:** Select the ALB name
   - **Protocol:** HTTPS only
   - **HTTPS port:** 443
4. **Default cache behavior:**
   - **Viewer protocol policy:** Redirect HTTP to HTTPS
   - **Cache key and origin requests:** Cache policy and origin request policy
   - **Cache policy:** CachingDisabled
   - **Origin request policy -:** AllViewerAndCloudFrontHeaders
5. **Settings:**
   - **Custom SSL certificate:** Select the ACM certificate
6. Choose **Create distribution**

---   

### Step 10: Publish the Web App

After deploying the CloudFront distribution, the final step is to publish your web application using Route 53. If you already have a domain name, you should create a new A record as an Alias and then choose the ALB URL or the CloudFront distribution URL if you have already created it. In case you do not have a domain name, you can create a new one using Route 53 and then add the A record. 

Alternatively, you can use the CloudFront URL or the ALB URL if you are looking for a temporary option.

---   

## Conclusion

We have developed a MACH architecture for building and deploying a user-friendly Streamlit web application using AWS services. By utilizing CICD with AWS CodePipeline, we can dynamically build and deploy the web app on a container, which speeds up the process, provides flexibility, agility, and ensures availability. The solution is highly adaptable and can be deployed in any environment. It can also be optimized to scale using Auto-Scaling, which reduces costs based on the required traffic.