Search code examples
reactjsamazon-web-servicesamazon-ec2vitepm2

Try to run react app on EC2 Amazon Linux with PM2


I try to run my app on EC2 Amazon Linux 2023, but even if it looks in pm2 logs that it runs (shows regular vite info that I can access it via localhost:5173). I cannot access it via "public-ip:5173". AWS CodeBuild Pipeline shows that everything went correct. Security group in inbounds rools is set like that:

Port/Protokol Source
5173/Custom TCP 0.0.0.0/0
80/HTTP 0.0.0.0/0
80/HTTP ::/0
443/HTTPS 0.0.0.0/0

My appspec.yml:

version: 0.0

os: linux 

 files:
 - source: /
   destination: /janus-ui
   overwrite: true

file_exists_behavior: OVERWRITE

permissions:
  - object: /
    pattern: "**"
    owner: ec2-user
    group: ec2-user

hooks:

  BeforeInstall:
    - location: scripts/before_install.sh
      timeout: 1600
      runas: root

  AfterInstall:
      - location: scripts/after_install.sh
        timeout: 1600
        runas: root

  ApplicationStart:
    - location: scripts/start_server.sh     
      timeout: 300
      runas: root

My after_install.sh:

#!/bin/bash

# navigate to app folder
cd /janus-ui

# install dependencies
npm install
npm install --save react react-dom react-scripts react-particles-js
npm install pm2 -g

My before_install.sh:

#!/bin/bash

# navigate to app folder
cd /janus-ui

# install node and npm
curl -sL https://rpm.nodesource.com/setup_18.x | sudo -E bash -
yum -y install nodejs npm

My start_server.sh:

#!/bin/bash

# navigate to app folder
cd /janus-ui

pm2 start npm --name "janus-ui" -- run prod
pm2 startup
pm2 save
pm2 restart all

my scripts in package.json

"scripts": {
    "dev": "env-cmd -f .env.development vite --port 5173",
    "prod": "env-cmd -f .env.production vite --port 5173",
    "build": "vite build",
    "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview"
  },

My buildspec.yml:

version: 0.2

phases:
  install:
    runtime-versions:
      nodejs: 18
   
    commands:
        # install npm
        - npm install
       
  build:
    commands:
        # run build script
        - npm run-script build
     
artifacts:
  # include all files required to run application
  # notably excluded is node_modules, as this will cause overwrite error on deploy
  files:
    - dist/**/*
    - src/**/*
    - package.json
    - appspec.yml
    - index.html
    - tsconfig.json
    - tsconfig.node.json
    - vite.config.ts
    - scripts/**/*
    - .env.production

Generally I stuck. I do it first time and I tried nginx, now pm2 looks easier, but still I cannot access even if logs looks like everything is correct.

I expect to see my page via browser on "public-ip:5173"


Solution

  • Summary

    To run a react web application you need an http server. React comes with a tiny server just for development (npm run dev) but not for production (for real users) environments.

    I will show you some options from manually to automated

    Advice

    I advice you to use docker for your react and spring boot api. I can provide you ready to use templates. Also with this you will work like a pro.

    You can install docker with the official guide or my script

    https://gist.github.com/jrichardsz/cf8fcec5f652a0cca432120c15d8595f#file-docker_docker_compose_oneclick_install-v3-sh

    Ec2 port

    Open the 80 port is not complicated

    enter image description here

    You can use another port if you need: 8080 for spring boot and 8081 for react

    Then the aws public url is something like

    http://ec2-aa-bb-cc-131.compute-1.amazonaws.com

    enter image description here

    image source: https://mkyong.com/server/namecheap-domain-name-and-amazon-ec2/

    If you chose the 80 port for react, you could use directly the aws public url

    thanks erick258 comment

    React build

    No matter the option you choose, you will need to build the app before serve it. Usually npm run build does it. Check if the build folder has your web with javascript minified. Like:

    enter image description here

    If you are not able to build the web app (react, angular, vue, svelte, etc), you could not serve it in the ec2 server

    Apache

    By default the 80 port is used by apache, so that matches with the 80 of your ec2.

    Check these:

    I think the easiest way is to wipe this folder /usr/local/apache2/htdocs/ and put your dist content. Then restart the apache

    Apache with docker

    So easy. Just create this Dockerfile at the root of your react project:

    FROM httpd:2.4
    WORKDIR /usr/local/apache2/htdocs/
    COPY build/ /usr/local/apache2/htdocs/
    RUN chmod -R 755 /usr/local/apache2/htdocs/
    

    Then run this is your shell

    npm install
    npm run build
    docker build -t acme-web-container .
    # wait some seconds
    docker run -d --name acme-web -it --rm -p 80:80 acme-web-container
    

    The web will be ready to use with the ec2 public domain

    Nginx

    Exactly the same of apache but with other folders and commands. Read these:

    Nginx with docker

    Create a nginx.conf file with this content

    FROM nginx:1.15.8-alpine
    
    #config
    copy ./nginx.conf /etc/nginx/nginx.conf
    
    #web
    copy build/ /usr/share/nginx/html/
    RUN chown -R nginx:nginx /usr/share/nginx/html/
    

    Then run this is your shell

    npm install
    npm run build
    docker build -t acme-web-container .
    # wait some seconds
    docker run -d --name acme-web -it --rm -p 80:80 acme-web-container
    

    The web will be ready to use with the ec2 public domain

    Tomcat

    Basically you just need to configure a folder in the web.xml. Read:

    As I told you, tomcat is for java. You could use it but in a real environments and or for easy management and scaling:

    • web and api should be in different git repositories
    • web and api should be deployed in different hosts, each one with custom configurations.
      • For example the server for react don't need java (tomcat). With apache/nginx/docker the web site can be very light to an easy horizontal scalling.
      • The api server(java) will need more ram (vertical scaling) or some java tuning

    Nodejs

    If you like nodejs, you could use these options to server your spa

    Also you can use my library which is able to serve any spa (react, angular, vue, etc)

    https://github.com/usil/nodeboot-spa-server

    One spa build for all environments

    In spring boot if you use environment variables, you can build the application just one time and then deploy it everywhere just changing the env variables. Example:

    • localhost
      • database_host: 127.0.01
      • database_port: 3306
      • database_user: root
      • database_password: changeme
    • production
      • database_host: 10.10.15.20 or some private ip
      • database_port: 3306
      • database_user: usr_foo
      • database_password: TwFrMMic+rin91BMhQOIY

    This is for real or enterprise environments in which you need several environment for the same application like:

    • localhost:8081
    • acme-dev.com
    • acme-test.com
    • acme.com (for real users)

    If you like that way, sadly in react is not possible. Read this to understand it : https://jrichardsz.github.io/devops/just-one-build-for-spas

    In the spas (react, angular, vue) you need to build for each environment (dev, test, staging, uat, prod, etc) which is not devops suitable. To fix this read https://jrichardsz.github.io/web/how-serve-spa-csr-react-angular-vue-webs and or you can use again my library:

    https://github.com/usil/nodeboot-spa-server

    Read the What is /settings.json ? section

    React with docker

    In this mode, all the react repository is docker driven, so you don't need to perform the npm run build before the docker build ... With this your react application could be fully ported to kubernetes or some orchestrator. I like https://aws.amazon.com/elasticbeanstalk

    Also you open the possibility to use devops:

    • git push to react repository
    • perform only docker commands (without human)
    • deploy (without human)

    Basically your Dockerfile should be like this:

    # build environment
    FROM node:alpine as build
    WORKDIR /app
    COPY . .
    RUN npm install
    RUN npm run build
    
    # production environment
    FROM nginx:stable-alpine
    COPY --from=build /app/build /usr/share/nginx/html
    EXPOSE 80
    CMD ["nginx", "-g", "daemon off;"]
    

    And finally the required commands will be

    docker build -t acme-web-container .
    docker run -d --name acme-web -it --rm -p 80:80 acme-web-container
    

    More details here: