Running production and deployment React apps on the same EC2 host

Say you have an Amazon EC2 host with TCP ports 80 and 3000 open. For the purposes of this post, we’ll run Ubuntu 16 on this EC2 host.

You want to run production and deployment versions of a React application on ports 80 and 3000, respectively, using nginx as the web server underneath.

Even if you have port 3000 open, trying to use the default React development application with your EC2 host can raise EADDRNOTAVAIL errors, when trying to point your web browser to the EC2 host’s public IP and development port. A little more work is necessary.

This post will walk you through setting up Nodejs, installing a blank React app, installing nginx, configuring nginx, and setting up the React app to support both production and deployment targets to be served through the nginx server.

Nodejs

Installation

Install node, npm, and npx from Nodejs:

$ cd ~
$ wget -qO- https://nodejs.org/dist/v11.1.0/node-v11.1.0-linux-x64.tar.xz > node-v11.1.0-linux-x64.tar.xz
$ tar xvf node-v11.1.0-linux-x64.tar.xz
...
$ cd node-v11.1.0-linux-x64/bin
$ sudo ln -s ${PWD}/node /usr/bin/node
$ sudo ln -s ${PWD}/npm /usr/bin/npm
$ sudo ln -s ${PWD}/npx /usr/bin/npx

nginx

Installation

$ sudo apt install nginx -y

Production

Throughout this post, change my-react-app to the name of your React application.

$ sudo mkdir /var/www/my-react-app
$ sudo gpasswd -a "$USER" www-data
$ sudo chown -R "$USER":www-data /var/www
$ find /var/www -type f -exec chmod 0660 {} \;
$ sudo find /var/www -type d -exec chmod 2770 {} \;

Open a text file called /etc/nginx/sites-available/my-react-app-production and add the following boilerplate:

server {
  listen 80;
  server_name 18.218.123.1;
  root /var/www/my-react-app;
  index index.html;
  
  access_log /var/log/nginx/my-react-app-production.access.log;
  error_log /var/log/nginx/my-react-app-production.error.log;
  location / {
    try_files $uri /index.html =404;
  }
}

This sets up nginx to listen to requests on port 80 and serve files from the document root /var/www/my-react-app. Later on, we’ll show how to put the production React application into this folder. For now, we first want to get the server set up.

I am using 18.218.123.1 as a placeholder. Your EC2 host will have its own IP address. Replace 18.218.123.1 with the public IP of your EC2 host. This host IP address will be available in the AWS EC2 console.

If you have an IP name that resolves to the public IP address, you can add a second server_name line that specifies this name, and then your web browser can point to this hostname.

Make a symbolic link to this configuration file in the /etc/nginx/sites-enabled directory:

$ sudo ln -s /etc/nginx/sites-available/my-react-app-production /etc/nginx/sites-enabled/my-react-app-production

Development

When we set up the EC2 host, we set up the security group to allow public-facing traffic on TCP ports 80 and 3000.

For the development server, we will use nginx as a proxy server that redirects any public-side web requests that hit port 3000, to redirect them internally to the EC2 host’s private IP address on port 8080.

We’ll show later how to configure our development React app to run on this private port assignment of 8080, but for now we start with the nginx configuration.

Open a text file called /etc/nginx/sites-available/my-react-app-development and add the following boilerplate:

server {
  listen 3000;
  server_name 18.218.123.1;
  
  access_log /var/log/nginx/my-react-app-development.access.log;
  error_log /var/log/nginx/my-react-app-development.error.log;
  location / {
    proxy_pass http://ip-172-31-123-1.us-east-2.compute.internal:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
  }
}

Change the line proxy_pass http://ip-172-31-123-1.us-east-2.compute.internal:8080 to replace ip-172-31-123-1.us-east-2.compute.internal with the private IP address of your EC2 host.

As with the public IP, this private IP address is available in the AWS EC2 console.

Make a symbolic link to this configuration file in the /etc/nginx/sites-enabled directory:

$ sudo ln -s /etc/nginx/sites-available/my-react-app-development /etc/nginx/sites-enabled/my-react-app-development

Startup

Use the following command to start the nginx service:

$ sudo service nginx start

If you make any changes to the web server’s configuration, such as to these two site configuration files, use the following to restart the service:

$ sudo service nginx restart

React

Installation

To set up a new app, you can run the following:

$ npx create-react-app my-react-app
$ cd my-react-app

In the React application directory, create a text file called .env and put in HOST and PORT settings:

HOST=0.0.0.0
PORT=8080

Development

To start the development server:

$ npm run start

This starts a development server on the EC2 host’s private-facing network on port 8080. If nginx is running and if it is set up correctly, then you can open your web browser to the EC2 host’s public IP and specify port 3000:

http://18.218.123.1:3000

Remember that we are using 18.218.123.1 as a placeholder. Replace that public IP with the one that Amazon assigned to your EC2 host.

The create-react-app tool creates an environment where you can edit the JSX files of your React application, and the development server will rebuild the application when those files change. So you should see any updates more or less immediately!

Production

When you’re ready to deploy your application to production, there are two steps.

First, set up a deployment target in the React application’s package.json file.

Open the package.json file in a text editor and go to the scripts property. Initially, it will look like this or similar:

...
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  ...

To this file, add a deploy target that synchronizes the build folder with what nginx has been configured to serve. The package.json file should look something like this:

...
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "deploy": "rsync -avzhe ssh --progress ./build/* /var/www/my-react-app"
  },
  ...

You only need to set up the deploy target once. This uses rsync and ssh. The use of ssh here is optional as both source and destination are on the same local filesystem.

However, you may want to push up a compiled application to a remote host at a later time. You might simply edit the destination, specifying the username and host parameters of that remote deployment host.

Second, run the build and deploy targets:

$ cd my-react-app
$ npm run build
$ npm run deploy

The first target “compiles” the React application to the build folder. The second target synchronizes the /var/www/my-react-app directory with the contents of the build folder.

After doing this, the contents of the public-facing web server will be available from the public IP address on port 80. If nginx is running and is set up correctly, then you can open your web browser to the EC2 host’s public IP and specify port 80:

http://18.218.123.1

Remember that we are using 18.218.123.1 as a placeholder. Replace that public IP with the one that Amazon assigned to your EC2 host.

Going forwards, to deploy to production, all you have to do is build and deploy. The updated application will be available to web clients.