Deploy a React/Node App Using TravisCI and AWS

Bibek Ghimire
ucladevx
Published in
11 min readSep 16, 2018
“cloudy sky” by Alex Machado on Unsplash

Let’s say you have a web app project built with React on the front-end and Node on the back-end API. You have the front- and back-end split up into separate GitHub repositories and you fire both of them up on different ports and have the front-end communicate with the API server.

Now let’s say you want to deploy your beautiful web app to a public domain, mywebapp.com, so the rest of the world can see your work. There are a handful of ways to do this but in this guide, we’re going to focus on just one: continuous integration/deployment with TravisCI and AWS (S3 + CloudFront + ElasticBeanstalk).

This setup is pretty common for small- to medium-scale web applications and it’s what the product I work on, BruinMeet, employs. In addition to a public-facing domain, BruinMeet also has a private “staging” domain to which we deploy to in order to test before releasing changes to the public.

This tutorial will walk you through the steps of setting up all of this.

Why use this approach?

  • Continuous integration allows us to make testing an automatic part of our development process. Every time we push up a commit, TravisCI will run our branch’s tests.
  • Continuous deployment lets us make pushing to GitHub and deploying the app (almost) synonymous. Unless tests fail, we can push a commit to our production branch and soon after have our production site reflect those changes — automatically.
  • AWS’s ElasticBeanstalk — along with Docker — makes it easy to deploy our Node API server with just a little bit of configuration.
  • AWS’s S3 + CloudFront lets us deploy and serve up our front-end super easily, while taking advantage of AWS’s global scale content delivery network (CDN).

Alright, I’m convinced but this sounds complicated. Where do I even start? 😣

First off, let’s establish the prerequisites before diving into this tutorial. You should:

  • have a working knowledge of git and GitHub
  • have an AWS account and an AWS IAM user configured with the programmatic access and the permissions: AmazonS3FullAccess, AWSElasticBeanstalkFullAccess, CloudFrontFullAccess, IAMFullAccess(how to)
  • have the AWS CLI installed and configured with the above IAM user on your machine
  • have both your GitHub repositories hooked up to TravisCI

I’ve dropped links above for the official AWS documentation on how to get set up. Everything else, we’ll figure out as we go through the tutorial. Alright, with that out of the way, let’s get started.

Let’s start by looking at how the deployment architecture will look when we’re done. Below is a diagram that shows the paths that a request coming from a browser (or other client) might take.

The paths a browser request might take through our eventual setup. You can ignore the AWS Certificate Manager part for now (don’t worry, I’ll explain why it’s there later).

Say we send a request to www.mywebapp.com. This is gonna send a request for the root (/) resource. That request is going to first reach a CloudFront server (called an edge cache). The CloudFront server will then see if it already has the requested content. If it does, it’ll respond with it (which will be the index.html in our case) . If not, it’ll request it from the S3 bucket, cache it, then respond to the user with it. We can then repeat this reasoning for any other resources we bundle with our React app (your main bundle.js, image assets, CSS files, etc.)

What if we instead send a request to api.mywebapp.com (like our React app might)? This request is going to first meet an ElasticBeanstalk load balancer, which forwards requests to one of the underlying EC2 containers. These underlying EC2 containers are what actually contain the code for our Node server. ElasticBeanstalk’s job is simply to do this forwarding and to (elastically) add or remove containers as our application’s needs change.

Important to note here is that our API is hosted at the api subdomain rather than a /api route. There are certainly ways to achieve the latter but they will require more work and are outside of the scope of this tutorial.

With the end goal in mind, let’s get to work on making the magic happen. I’m gonna split the rest of this tutorial into 4 steps.

  1. Configure the front-end app for continuous deployment to S3 using TravisCI.
  2. Configure the back-end app for continuous deployment to ElasticBeanstalk using TravisCI and Docker.
  3. Configure our domain’s DNS settings to point correctly to both the front-end and back-end apps.
  4. Configure AWS Certificate Manager to enable HTTPS for our domain.

Step 1: Configure the front-end app

First off, another diagram to keep in mind as we walk through this step.

The deployment process that gets kicked off each time we commit and push a change to our repository.

Most of this UML sequence diagram should be fairly intuitive. If you’re entirely unfamiliar with UML sequence diagrams, the TL;DR on the link above is that the vertical axis represents time, each column is a different entity, and the arrows represent communication between entities. The “alt” box on the bottom simply represents a conditional flow, in which we only deploy to S3 if we determine that we want to for this branch.

Let’s start by setting up our S3 buckets. Open up a terminal session and type the following command to create a new S3 bucket. Change the bucket name and region values to your preference.

Keep in mind that S3 bucket names must be globally unique.

aws s3api create-bucket \
--bucket mywebapp-react-production \
--region us-west-2 \
--create-bucket-configuration LocationConstraint=us-west2

Running this command will output the URL your S3 bucket will be accessible at. We won’t need it for anything in particular so you can ignore it. If you ever need to see it again, you can find it by going to your bucket, then Properties > Static Website Hosting.

With the bucket successfully created, let’s configure it to host a static website. Fortunately, the AWS CLI makes this super easy with the following command. This command’s main job is to make the S3 aware that a request to / is the same as a request to index.html.

aws s3 website s3://mywebapp-react-production/ \
--index-document index.html

Next, make the bucket content publicly accessible by going to the S3 bucket in the AWS console and adding the following configuration under Permissions > Bucket Policy, making sure to change the bucket name under “Resource” to match your bucket’s name.

{   
"Version":"2012-10-17",
"Statement":[{
"Sid":"PublicReadGetObject",
"Effect":"Allow",
"Principal": "*",
"Action":["s3:GetObject"],
"Resource":["arn:aws:s3:::mywebapp-react-production/*"]
}]
}

Once that’s done, our bucket is set up and ready to host a static website! ✔️

If you’ve been paying close attention, you’re probably recalling the first diagram and thinking, “Okay great, S3 is set up but what about CloudFront?”

Setting up CloudFront is a breeze as well. There are two key steps: (1) Create the distribution with the AWS CLI, (2) Configure the error page to redirect to /.

Start by running the following command to create a distribution.

aws cloudfront create-distribution \
--origin-domain-name mywebapp-react-production.s3.amazonaws.com \
--default-root-object index.html

This will spit out a huge configuration JSON describing your new CloudFront distribution. The key value to note is Distribution.DomainName. For me, this was d3ois221lkhb7q.cloudfront.net. This is the URL that we’ll point our main domain www.mywebapp.com at later in the tutorial.

Next, we’ll force CloudFront to route 404 errors to / since our React app will be responsible for displaying a 404 page on its own (via react-router). To accomplish this, open up CloudFront in the AWS Console and in the distribution we just made, go to the Error Pages tab, and “Create Custom Error Response” with the following config.

With this in place, if a user goes to www.mywebapp.com/invalid-route, it’ll still serve the index.html but the React app’s router will recognize it as a 404 path and show the appropriate 404 page.

CloudFront setup. ✔️

Lastly, let’s add a few new files to our project that will allow us to upload our React app bundle to our S3 bucket whenever we push a commit to a certain branch.

This Travis configuration file will cause our Travis virtual environment to automatically run our package.json’s “test” script first, then run our custom deploy script (below).
This deploy script is the heart of the deployment process. Although you can deploy to S3 using the Travis YML file alone, using this script allows us to customize the bucket we upload to based on the branch name.

As you might notice, there are a couple of variables in here that seem to be coming from nowhere (CLOUDFRONT_DIST_ID_STAGING and CLOUDFRONT_DIST_ID_PRODUCTION). We’ll actually need to define them in the TravisCI settings for this repo under “More Options” > Settings > Environment Variables.

With our S3 bucket made, CloudFront distribution set up, these new files in our project, and TravisCI env variables set up, our front-end is good to go. Pushing to master should now cause our React app to deploy to the S3 bucket, which is served up by the CloudFront distribution. As such, it should be accessible at both our S3 bucket endpoint and CloudFront domain name.

A few additional points to consider:

  • CloudFront — being a cache — will not automatically detect new versions of your app in S3. As such, if we want changes to be deployed instantly, we’ll need to create an invalidation after deploying to S3. We can either do this in the CloudFront console or programmatically as part of our deployment in deploy.sh.
  • As of now, our CloudFront distribution will only accept requests via the HTTP protocol. HTTPS requests simply won’t work. Step 3 of this tutorial will walk through setting that up using AWS Certificate Manager.
  • A great optimization to add to our CloudFront distribution is to enable “Compress objects automatically” under Behavior > Default(*) path pattern > Edit. Turning this lets CloudFront use gzip to compress our files for faster transfers to clients.

Step 2: Configure the back-end deployment process.

The overall process for back-end deployment will be nearly identical to that of the front-end. The only difference is that instead of deploying to S3, we deploy to ElasticBeanstalk. This comes with a few additional tasks but we’ll walk through them step-by-step.

Let’s first set up our ElasticBeanstalk environment on AWS.

We’ll start by creating an ElasticBeanstalk application.

aws elasticbeanstalk create-application --application-name mywebapp-api

We’ll then create the environment.

aws elasticbeanstalk create-environment \
--application-name mywebapp-api \
--environment-name production \
--solution-stack-name "64bit Amazon Linux 2018.03 v2.12.0 running Docker 18.03.1-ce"

The relationship between an EB application and environment is one-to-many: in other words, one application may have one or more environments. For example, if you want to host both a production and staging version of your application, you’d create an environment for each.

Lastly, if we need to define environment variables for our EB application, we can do so by navigating to Configuration > Software (Modify) > Environment properties in the AWS EB Console for the environment in question.

ElasticBeanstalk setup. ✔️

Now, for the new files.

First, we’ll need to add some ElasticBeanstalk configuration details to our project. In the root directory of your project, create a new folder named .elasticbeanstalk and in it create a file called config.yml with the following contents:

Now let’s add the files telling TravisCI to push your code to ElasticBeanstalk.

Would ya look at that… it’s identical to the one we used for front-end. 😄
Just like before, we use the branch to determine the environment to deploy to, install the AWS EB CLI, then deploy.

The key difference in this deploy script is the configuration of your AWS credentials before deploying since the eb command doesn’t pull it from your environment variables like aws s3 does.

Okay, now for a new file exclusive to the back-end deployment process, Dockerfile.

This file defines the steps that a Docker container needs to run to start up the API server. There’s tons more to learn about Docker here if you’re interested but for the sake of this tutorial, we’ll just consider it as a necessary step in ElasticBeanstalk deployment.

That’s it! We’re done. Now pushing your code to the hooked up branch should automatically kick off a deployment!

Step 3: Configure the domain’s DNS settings

At this point, your front-end and back-end apps should be deployed and independently functional. The problem is, they aren’t hosted on the domain you want (and therefore can’t even communicate with each other)!

The React app is hosted at a CloudFront endpoint that looks something liked3ois221lkhb7q.cloudfront.net and the API at an ElasticBeanstalk endpoint that looks like production.nghvu3hx5z.us-west-2.elasticbeanstalk.com.

To point your domain name at these apps, go to your domain hosting service’s DNS settings and add the following new DNS records, tweaking the URLs you put for value to match your own app’s CloudFront, EB, and domain URL’s. The interface for adding these records varies by hosting service but the general information you need to enter is the same.

# For the React app
Record Type: CNAME
Host: www
Value: d3ois221lkhb7q.cloudfront.net
# For the API server
Record Type: CNAME
Host: api
Value: production.nghvu3hx5z.us-west-2.elasticbeanstalk.com
# For redirecting mywebapp.com to www.mywebapp.com
Record Type: URL Redirect
Host: @ # This means the root
Value: http://www.mywebapp.com

Give the DNS record changes a generous amount of time to propagate through the network (we’re talking on the scale of hours here).

Step 4: Configure HTTPS using AWS Certificate Manager

At this point, your app should be fully wired up to be accessible via your chosen domain (in other words, navigating to mywebapp.com should work as expected).

But there’s one small problem. Your site isn’t secure.

In order to ensure that communication between your site and its users is secure, you’ll need to add HTTPS/SSL support. Fortunately, AWS makes this a breeze.

Head on over to the Certificate Manager in the AWS Console. Click “Request a Certificate” and then “Request a public certificate”.

Under the domain names section, specify both the root and the www subdomain.

Click Next and follow the instructions to validate your certificate.

Once that’s done, head on over to your CloudFront instance you created earlier. Under the General tab, click Edit. Under the SSL Certificate section, check “Custom SSL Certificate” and select the certificate you just made. Then under the Behaviors tab, select the first Behavior and Edit, selecting “Redirect HTTP to HTTPS”.

Our React app server is now HTTPS secure. Now let’s secure our API.

Go to the ElasticBeanstalk console and open up your application environment’s configuration panel. In there, click “Modify” under “Load Balancer”.

Finally, click “Add Listener” and create a Load Balancer listener with the following settings, selecting the certificate you just made for the “SSL Certificate” dropdown.

This configuration tells EB to listen for HTTPS messages on port 443, then forward them to the underlying EC2 instances at port 80 using the HTTP protocol. The load balancer serves as a sort of “bouncer” at the door of your API.

Add the certificate and now your API is secure as well.

Done.

Whew, that was pretty long but you made it to the end. Congratulations! You now have a web app that’s configured to run tests (and conditionally) deploy to AWS S3/CloudFront and ElasticBeanstalk whenever you push to GitHub. 🎉👏😄

--

--

Bibek Ghimire
ucladevx

I write about tech, life, and whatever else finds its way into my mind • bibekg.com