AWS CI/CD for React Apps Using GitHub Actions, S3, CloudFront, and Route53
Deep hands-on understanding on React deployment steps using AWS resources
Introduction
The modern web development landscape is changing at a rapid pace, and developers are leveraging numerous technologies to improve their workflow and productivity. One such practice is Continuous Integration and Continuous Deployment (CI/CD). In conjunction with cloud storage solutions and Content Delivery Networks (CDNs), CI/CD practices can significantly enhance the development process.
In this article, we’ll explore the CI/CD pipeline for a React application, deploying the application on Amazon S3, and delivering it via Amazon CloudFront using GitHub Actions.
The Role of GitHub Actions
GitHub Actions provides developers with powerful CI/CD capabilities directly within GitHub. It allows automatic execution of software pipelines whenever there’s a trigger like a push or a pull request to specific branches. For a React application, this could mean automatically installing dependencies, running tests, building the application, and deploying it to the desired hosting platform.
The Workflow YAML File
A GitHub Actions workflow is defined in a YAML file. The file contains the steps required to build, test, and deploy your application. For our React app, the file could look something like this:
name: Node CI-CD for build, test, and deploy
on:
push:
branches:
- main
- production
- PBI-**
- coldfix
pull_request:
branches:
- production
jobs:
install:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use node 16.x
uses: actions/setup-node@v2
with:
node-version: 16.15.1
- run: npm ci
build:
needs: install
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [ 16.x]
steps:
- uses: actions/checkout@v3
- name: Use node ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm run build
sonarscan:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: run test and coverage
run: |
npm install
npm test
- name: SonarQube Scan
uses: sonarsource/sonarqube-scan-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/production' || github.ref == 'refs/heads/coldfix'
steps:
- uses: actions/checkout@v3
with:
python-version: 3.9
- name: Cofig AWS Cred
run: |
pip install awscli
aws configure set region ap-southeast-2
aws configure set output json
aws configure set aws_access_key_id ${{ secrets.AWS_ACCESS_KEY }}
aws configure set aws_secret_access_key ${{ secrets.AWS_SECRET_KEY }}
- name: deploy with s3 sync and Invalidate CloudFront cache
run: |
npm install
if [ ${{ github.ref }} = 'refs/heads/main' ]
then
export REACT_APP_BACKEND_URL=${{ secrets.REACT_APP_BACKEND_URL_STAGING }}
npm run build
aws s3 sync ./build s3://${{ secrets.S3_BUCKET_NAME_STAGING }}/
aws cloudfront create-invalidation --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID_STAGING }} --paths "/*"
elif [ ${{ github.ref }} = 'refs/heads/production' ]
then
export REACT_APP_BACKEND_URL=${{ secrets.REACT_APP_BACKEND_URL_PRODUCTION }}
npm run build
aws s3 sync ./build s3://${{ secrets.S3_BUCKET_NAME_PROD }}/
aws cloudfront create-invalidation --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID_PROD }} --paths "/*"
fi
This YAML file defines a workflow that installs the required Node.js version, installs project dependencies, builds the React app, and then deploys the application to Amazon S3. After that, it invalidates the CloudFront cache to ensure that the latest version of the app is served to users.
Understanding {Secrets} Environment Variable
Secrets are encrypted environment variables that you create in an environment, organization, repository, or repository environment in GitHub. They can be used to manage sensitive data, such as access tokens, passwords, or environment variables, that your project needs to function.
Secrets are used in GitHub Actions workflows to provide these sensitive details without exposing them in your code or log files. In a YAML file, secrets are accessed via the secrets
context. For example, secrets.AWS_ACCESS_KEY
would access the secret named AWS_ACCESS_KEY
.
In your CI/CD workflow, secrets are used to store sensitive information needed for deploying your React application to AWS. Specifically:
AWS_ACCESS_KEY
andAWS_SECRET_KEY
are your AWS credentials. They are necessary to authenticate with AWS and perform operations, such as syncing your build files to S3 or invalidating a CloudFront cache.S3_BUCKET_NAME_STAGING
andS3_BUCKET_NAME_PROD
are the names of your S3 buckets for the staging and production environments, respectively. These are the destinations for your built React application.CLOUDFRONT_DISTRIBUTION_ID_STAGING
andCLOUDFRONT_DISTRIBUTION_ID_PROD
are the IDs of your CloudFront distributions for the staging and production environments, respectively. They are needed to invalidate the CloudFront cache for the corresponding environment after deployment.REACT_APP_BACKEND_URL_STAGING
andREACT_APP_BACKEND_URL_PRODUCTION
are likely the URLs of your backend API for the staging and production environments, respectively. These are set as environment variables during the build process, and they are probably used by your React application to make API calls.
It’s essential to use secrets to store this kind of sensitive information. Exposing such details publicly could lead to unauthorized access to your AWS resources, and potential misuse. By using secrets, these details are encrypted and kept safe, and they can only be accessed by authorized GitHub Actions workflows.
Staging-Production Separation
In our GitHub Actions workflow, the staging
and production
environments are defined by specific branches in your Git repository: main
represents the staging
environment, while production
represents the production
environment. These branches are typically used to manage different versions of your application during the development process.
When a push is made to the main
branch, it's assumed that this represents a change meant for the staging
environment. The staging
environment is usually a replica of the production environment, used for testing and verifying changes before they are pushed to production.
Likewise, when a push is made to the production
branch, this triggers a deployment to the production
environment. The production
environment is where your live application resides. Changes here will be visible to your end-users.
In our YAML workflow:
- When a push is made to the
main
branch, the environment variableREACT_APP_BACKEND_URL
is set tosecrets.REACT_APP_BACKEND_URL_STAGING
. This probably changes the API endpoint that your React application uses during the build process. The built application is then synced to thestaging
S3 bucket (secrets.S3_BUCKET_NAME_STAGING
), and the CloudFront cache for thestaging
environment is invalidated. - If a push is made to the
production
branch,REACT_APP_BACKEND_URL
is set tosecrets.REACT_APP_BACKEND_URL_PRODUCTION
. The application is built, synced to theproduction
S3 bucket (secrets.S3_BUCKET_NAME_PROD
), and the CloudFront cache for theproduction
environment is invalidated.
This process allows for different versions of your React application to exist simultaneously in different environments, facilitating testing and deployment.
AWS IAM Access Key for Simplicity
AWS IAM, or Identity and Access Management, is a key service within AWS that allows you to manage access to your AWS services and resources securely. Using IAM, you can create and manage AWS users and groups and use permissions to allow and deny their access to AWS resources.
When it comes to using AWS in your CI/CD pipeline, like in this GitHub Actions workflow, you’re interacting with various AWS services such as S3 for storage and CloudFront for content delivery. To do this securely, you need a way to authenticate and authorize these interactions. This is where IAM comes in, particularly with the concept of IAM Users and Access Keys. Each IAM user can have access keys associated with it — an access key ID and a secret access key. These are used to perform programmatic calls to AWS, which is exactly what you’re doing in your GitHub Actions workflow. When setting up AWS CLI in the CI/CD pipeline, the aws_access_key_id
and aws_secret_access_key
are set using these access keys.
In the context of our GitHub Actions workflow, we’re using secrets (secrets.AWS_ACCESS_KEY
and secrets.AWS_SECRET_KEY
) to store the IAM user's access key ID and secret access key. This is excellent because it's crucial to keep these credentials secure - they should never be hardcoded into your codebase, as they can provide access to your AWS resources.
When the CI/CD pipeline runs, it retrieves these credentials from the secrets and uses them to set up the AWS CLI configuration. This enables the pipeline to interact securely with your AWS services.
Preparing Deployment to Amazon S3 and CloudFront
Amazon S3 (Simple Storage Service) is a storage service that allows you to store and retrieve any amount of data at any time from anywhere on the web. It is ideal for hosting static websites or web applications like a React app.
The built React application is a collection of static files, which are uploaded to an S3 bucket. The bucket is configured to behave as a web host, serving the application’s static files. Create an S3
Amazon CloudFront is a CDN service that securely delivers data, videos, applications, and APIs to customers globally with low latency and high transfer speeds. After deploying the application to an S3 bucket, a CloudFront distribution is used to deliver the application to end-users. CloudFront ensures that the application loads quickly, no matter where users are located globally.
When the application is updated, the CloudFront cache needs to be invalidated to ensure that users receive the latest version of the app. Invalidation removes the files from CloudFront edge locations, and the next time a viewer requests the invalidated files, CloudFront returns to the origin (the S3 bucket in this case) to fetch the latest version of the invalidated files.
AWS Route53 for cleaning the mess
Amazon Route 53 is a scalable and highly available Domain Name System (DNS) web service offered by AWS. It is designed to give developers and businesses an extremely reliable and cost-effective way to route end users to Internet applications by translating human-readable domain names (like www.example.com) into the numeric IP addresses (like 192.0.2.1) that computers use to connect to each other.
Here’s a deeper look at its functionalities:
Domain Registration: Route 53 lets you register domain names under a variety of top-level domains (like .com, .net, .org) and ensures the domain’s DNS settings are correctly configured.
DNS Routing: After a domain is registered, Route 53 helps route traffic to your application or website by translating friendly domain names to numerical IP addresses, facilitating connections between users and your application.
Health Checking: It can also perform health checks to monitor the health and performance of your application and its endpoints. If an outage should occur, Route 53 can automatically route traffic to healthy endpoints or to resources in a different location.
Integration with AWS Services: Route 53 is integrated with other AWS services such as AWS CloudFront, S3, and EC2, making it easy to route global traffic to your AWS resources.
In the context of your GitHub Actions workflow for a React application, after your application is built and deployed to an S3 bucket, and the CloudFront distribution cache is invalidated, the final step would be to ensure that your Route 53 records are correctly pointing to your CloudFront distribution.
Now lets create a hosted zone:
Next step, create an a record A on the hosted zone pointing to our resources:
‘Value’ on the right side is the url of our CloudFront CDN that point to the S3 Bucket.
This way, when a user enters our ‘vessel-staging.kapal.tech’ domain into their web browser, Route 53 correctly routes the request to the CloudFront distribution, which fetches the latest version of your application from the S3 bucket, ensuring that your users always have access to the most recent version of your application.
Conclusion
This is the final look of the architecture that we’ve built so far:
In conclusion, automating the CI/CD pipeline using GitHub Actions for a React application and deploying it with AWS services, including S3, CloudFront, and Route 53, can greatly enhance the development and deployment process. This workflow not only ensures a consistent and reliable build process but also delivers the application with high availability and performance.
By segregating staging and production environments through different branches and buckets, we can efficiently manage and test our application before it reaches the end-users. The use of AWS IAM for secure AWS access and environment secrets for secure storage of sensitive information fortify the security of our pipeline.
This setup not only brings robustness to our React application’s deployment but also frees up developers’ time by reducing manual deployment efforts and focusing more on feature development and enhancements.
Remember, automating processes is a key aspect of contemporary web development, and tools like GitHub Actions and AWS Services make it more achievable than ever.