DEV Community

Cover image for Code, Cloud and Coffee: Deploying a MySQL, Spring and React application on AWS Free Tier with CI/CD through GitHub Actions
Saurabh Mahajan
Saurabh Mahajan

Posted on • Originally published at blog.saurabhmahajan.com on

Code, Cloud and Coffee: Deploying a MySQL, Spring and React application on AWS Free Tier with CI/CD through GitHub Actions


Introduction

Embarking on the AWS Free Tier journey, I set out to weave a tapestry of technology by deploying a full-stack application, combining the power of MySQL, Spring, and React. This blog serves as a chronicle of my exploration into the world of cloud computing, where budget constraints met the ambition to create a robust and dynamic web application.

In the realm of databases, I opted for Amazon RDS to host my MySQL database, leveraging its managed services to streamline administration tasks. On the backend, an EC2 instance powered by Spring provided the backbone for my application's logic and data processing. For the frontend, the user interface found its home on AWS Amplify, simplifying the deployment of my React application with a focus on scalability and ease of use.

This journey wasn't just about deploying code; it was a navigation through the intricate landscape of AWS services, striking a balance between functionality and frugality. Join me as I unravel the threads of my experience, sharing the triumphs, challenges, and lessons learned in the pursuit of a fully functional, cost-effective full-stack application on the AWS Free Tier. Let this be a guide for those venturing into the cloud, where innovation meets the constraints of a tight budget. Welcome to the story of MySQL, Spring, and React on AWS Free Tier.


AWS Services used

For the database, I chose Amazon RDS (Relational Database Service) to manage the MySQL database. RDS simplifies database management with automated backups, patching, and scalability features. The benefits include reduced operational overhead, easy scalability, and enhanced security through encryption. However, the AWS Free Tier imposes limitations on instance types and storage, which may require monitoring to avoid additional charges.

On the server side, Amazon EC2 (Elastic Compute Cloud) was employed for hosting the Spring application on a docker container. EC2 provides scalable compute capacity with full control over virtual machines. This allows customization and flexibility in choosing instance types based on application needs. While the AWS Free Tier offers limited hours of t2.micro instances per month, additional charges may apply for data transfer and storage beyond the allotted limits.

For the frontend, AWS Amplify was selected to streamline React application deployment. Amplify simplifies the development process with automatic deployment, hosting on a global CDN, and a user-friendly CI/CD pipeline. Its benefits include quick setup and integration with other AWS services. However, limitations in build minutes and storage on the Free Tier, along with potential charges for data transfer out of the hosting environment, should be considered when utilizing Amplify for hosting the frontend.


Architecture

Our application's architecture is a sophisticated ecosystem designed for seamless interaction between its integral parts. The backbone of our backend data storage is the MySQL database hosted on Amazon RDS, offering scalability and managed services. Meanwhile, the application's logic resides in a Spring backend, encapsulated within a Docker container on an EC2 instance. This environment is orchestrated by Docker, intricately configuring the MySQL and the Spring application to communicate flawlessly over the EC2 and RDS.

The orchestration reaches its crescendo with the Continuous Integration and Continuous Deployment (CI/CD) workflow facilitated by GitHub Actions. Whenever code changes are pushed to our GitHub repository, the orchestrated symphony begins. A self-hosted runner on the EC2 instance takes center stage, fetching the latest changes and seamlessly updating our Spring backend. This ensures that our backend remains cutting-edge with every push.

Simultaneously, the frontend undergoes a transformation of its own. The React frontend code, residing in our GitHub repository, is automatically deployed to AWS Amplify, a platform offering scalable and managed hosting for our frontend. This dynamic deployment process guarantees that our users experience the most up-to-date version of our application without any manual intervention.

The Github Actions will utilize 3 workflows. First for Continuous Integration which will be triggered on every push to the repository, second for building the back-end app docker image and pushing it to our private dockerhub repository and the last one for continuous delivery which will begin execution when the second workflow completes execution. The continuous delivery workflow will be run on a self-hosted runner which will be running on the ec2 instance. It will pull the lastest uploaded backend app image and run it in a container.

In essence, this intricate dance of technologies integrates AWS services, Docker containerization, and robust CI/CD practices into a harmonious system. Each component plays a crucial role, contributing to the continuous deployment and enhancement of our full-stack application. This carefully orchestrated architecture not only ensures a smooth and automated workflow but also lays the foundation for a scalable and efficient application ecosystem.


Repository Structure

Link - https://github.com/saurabhthecodewizard/opus

This is how the folder structure will look like for this example.

  1. The server folder will have spring application code.

  2. The client folder will have react app code.

  3. The .github/workflows will have the CI/CD workflows


RDS Setup for Database

  1. Navigate to the AWS Management Console and open the Amazon RDS dashboard.

  2. Click on the "Create database" button.

  3. Choose the "Standard create" method.

  4. In the "Engine options" section, select "MySQL" as the database engine and choose the latest engine version available.

  5. Under "Templates," select "Free Tier" to ensure the usage falls within the free tier limits.

  6. Provide a unique DB instance identifier of your choice.

  7. Set a username and password for the database credentials, as these will be needed for connecting to the database.

  8. Choose the "db.t4g.micro" instance type under "Free tier" to stay within the free tier limits.

  9. For storage, select "gp2" as the storage type and select 20 GB storage since 20 GB is allocated for free tier, and uncheck the "Enable storage auto scaling" option.

  10. In the "Connectivity" section, choose "Don't connect to an EC2 compute resource" and allow public access, as the database will be accessed locally and later from the EC2 instance.

  11. Select the security group with EC2, RDS and AdministratorAccess.

  12. Under the "Database authentication" section, choose "Password authentication."

  13. In the "Additional configuration" section, uncheck "Automated backups," "Encryption," and "Minor version upgrade."

  14. It may show you estimated cost, but we should be good as long as we are on free tier.

  15. Finally, click on the "Create database" button to initiate the creation of the MySQL RDS instance.


Development Setup

  1. Develop a Spring application in the server folder with a simple API fetching data from the database, using the RDS database configuration (The RDS database url can be take from endpoint from RDS instance details and the username and password should be the one you setup during the RDS instance creation).

  2. This project uses flyway migrations for versioning the database. More information can be found here - https://www.baeldung.com/database-migrations-with-flyway

# application.properties# Database configurationspring.datasource.url=${SPRING_DATASOURCE_URL}spring.datasource.username=${SPRING_DATASOURCE_USERNAME}spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}spring.jpa.show-sql=true# Flyway configurationspring.flyway.baseline-on-migrate=truespring.flyway.locations=classpath:db/migration
Enter fullscreen mode Exit fullscreen mode
  1. I have added a clients table and a single entity to it in the migration.
-- V1697817392__add_opus_to_clients.sqlCREATE TABLE clients ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, create_date DATE, status TINYINT(1), -- 1 for true (active), 0 for false (inactive) website VARCHAR(255), picture_url VARCHAR(255));INSERT INTO clients (name, create_date, status, website, picture_url)VALUES ('Opus', '2023-10-20', 1, 'https://www.opus.com', 'https://www.opus.com/image.jpg');
Enter fullscreen mode Exit fullscreen mode
  1. Add an endpoint for us to test the application
// Controller.java@GetMapping("/")public ClientDTO getOpus() { return clientService.getClientById(1L);}
Enter fullscreen mode Exit fullscreen mode

The api should be working now which will fetch our client entity from the rds instance


Docker Setup - Backend

  1. Create a Dockerfile to ./server directory.
# ./server/Dockerfile# Use the Maven 3.9.5 image as the build environmentFROM maven:3.9.5 AS build# Set the working directory within the container to /appWORKDIR /app# Copy the content of the current directory into the /app directory in the containerCOPY . .# Run Maven to clean and install dependenciesRUN mvn clean install# Use the OpenJDK 21 image as the base image for the final containerFROM openjdk:21# Set the working directory within the container to /appWORKDIR /app# Copy the compiled JAR file from the build stage to the /app directory in the final containerCOPY --from=build /app/target/*.jar server.jar# Expose port 8080 to allow external connectionsEXPOSE 8080# Define the default command to run the Java applicationCMD ["java", "-jar", "server.jar"]
Enter fullscreen mode Exit fullscreen mode

You can test this by running the image locally by the following commands in the ./server directory

  1. Build the server image
docker build -t server .
Enter fullscreen mode Exit fullscreen mode
  1. You can see if the image is built successfully by running docker image ls or in docker desktop app.

  2. Run the image in container with appropriate port mapping and appropriate environment variables in the command

docker run -d -p 8080:8080 -e SPRING_DATASOURCE_URL=${DB_URL} -e SPRING_DATASOURCE_USERNAME=${DB_USERNAME} -e SPRING_DATASOURCE_PASSWORD=${DB_PASSWORD} -e SPRING_MAIL_USERNAME=${SPRING_MAIL_USERNAME} -e SPRING_MAIL_PASSWORD=${SPRING_MAIL_PASSWORD} --name server-container server
Enter fullscreen mode Exit fullscreen mode

You can see the container running docker container ls or in the docker desktop app.


Github Secrets

Add the following github secrets which we will be using in our workflows for CI/CD as shown in the picture below.


Github Actions Setup

We will be creating 3 files in the .github/workflows directory.

  1. Continuous Integration - server-intergartion.yml :-

  2. Server Build - server-build.yml :-

  3. Continuous Deployment - server-deploy.yml :-


EC2 setup for back-end

  1. Sign in to the AWS Management Console.

  2. Open the EC2 dashboard.

  3. Click on the "Instances" link in the left navigation pane.

  4. Click the "Launch Instances" button.

  5. Select Ubuntu Server

  1. Select t2.micro to stay in the free tier

  1. Create a Key pair of .pem type so we can login through SSH

  1. Allow HTTP/HTTPS traffic from internet.

  2. Launch the instance.

  3. Change the inbound rules as given below


Github Runner setup on EC2

  1. Navigate to .pem file

  2. Connect to EC2 instance through SSH client

  3. Update the package list on your EC2 instance

  4. Upgrade the installed packages on your EC2 instance. The -y flag automatically confirms the upgrade.

  5. Create Github Runner

  6. Run all the commands provided through the Downlaod and Configure section of the github runner on our EC2 instance that we previously connected through SSH client.

  7. Run the ./run.sh script in the background using the & operator to keep the process running in the background while freeing up the terminal.

  8. Confirm runner connection

  9. Install Docker on EC2 instance

  10. Trigger the CI/CD flow

  • Push our code to the repository (This should automatically trigger the Server Build workflow).

  • Trigger the Server Build workflow manually through github actions.

  • The successful completion of Server Build workflow should automatically trigger the Server Deploy workflow.

    Note: If you have any errors in the server build or server integration, you will have to solve those locally and then trigger the CI/CD again.

  • Your latest server build should now be successfully deployed on the EC2 instance and you can check by triggering the api we provided.

  • You can also check the depoloyment by calling the api through browser with appropriate url. You can get the public IP from instance details and the rest of the api url as you configured in the spring application.


Setup nginx server on EC2 for proxy

Run the following commands on EC2 instance

  1. Install nginx

  2. Get Docker container IP

    Find the IP address of your Docker container using the following command. Replace containerId with the actual ID. You can find the docker container id by running docker container ps.

  3. Navigate to nginx sites-available directory

  4. Edit the default configuration

  5. In the location / { ... } block, add the following line with the appropriate IP address and port:

  6. Restart nginx

  7. Check the Nginx configuration for syntax errors

  8. Nginx is now configured as a reverse proxy for your Docker container. Requests to the Nginx server will be forwarded to the Docker container. Make sure to replace the placeholder IP address and port with the actual values from step 2.


Setup HTTPS on EC2

You can setup HTTPS on EC2 if you have custom domain. Follow this process if you do not have custom domain.

  1. Now, install Caddy on the server. Instructions can be found here. Caddy 2 is a powerful open source web server with automatic HTTPS. Caddy simplifies your infrastructure and takes care of TLS certificate renewals.

  2. Next we'll create a Caddyfile with the following contents(Insert your ec2 public ip).


Front-end setup on AWS Amplify

  1. Create a basic react app and consume the api we created in the Spring application with the ec2 URL

  2. Navigate to AWS Amplify on AWS management console.

  3. New app -> Host a web app

  4. Select Github

  5. Authorize the repository -> Select your repository -> Select your branch

  6. (Optional) -> If the frontend application is not in the root directory, mention the correct directory of the app in the folder section

  7. Next -> Next -> Save and Deploy

Note: You may need to make changes to the build commands or your react app based on the packages and libraries you are using

  1. You can setup the backend url in environment variables in amplify if you want

  2. Your app is now live


Conclusion

In conclusion, the journey of deploying a full-stack application on AWS Free Tier was a dynamic exploration that delved into the intricacies of cloud architecture and development. The experience not only involved the successful deployment of MySQL on RDS, Spring on EC2, and React on AWS Amplify but also incorporated a streamlined CI/CD pipeline. The backend server benefited from GitHub Actions, ensuring continuous integration and deployment, while AWS Amplify seamlessly handled the CI/CD for the frontend.

As I reflect on the project, it's clear that the CI/CD setup significantly contributed to the overall efficiency of development and deployment processes. GitHub Actions provided a robust solution for automating backend deployments, while AWS Amplify's integration with the frontend codebase streamlined the continuous delivery workflow. The synergy between these tools not only enhanced the development speed but also facilitated a more consistent and reliable release cycle.

While navigating through the intricacies of AWS services, I gained insights into best practices for cloud development, security considerations, and the critical role of scalability. The process wasn't without its hurdles, but each challenge presented an opportunity for growth and learning.

Sharing these experiences serves not only as a documentation of my journey but also as a guide for fellow developers venturing into the realm of AWS. By reflecting on the lessons learned and embracing the continuous learning ethos of cloud development, I believe the community can benefit from a shared knowledge pool that empowers developers to make informed decisions and build robust, scalable applications on AWS Free Tier.

Top comments (0)