loading...
Cover image for I Deployed A Server-side React App With AWS Elastic Beanstalk. Here's What I Learned.

I Deployed A Server-side React App With AWS Elastic Beanstalk. Here's What I Learned.

johanrin profile image Johan Rin Originally published at Medium ・8 min read

I'm doing the #AWSCertified challenge introduced by freeCodeCamp and I decided to write about my journey. I'm preparing now the AWS Certified Developer Associate exam with the course developed by Andrew Brown of ExamPro. To pass the certification, I decided to experiment more and share with you what I learn and also the pain points I encounter during the process.

During this Easter weekend, I decided to explore AWS Elastic Beanstalk.

My objective was to deploy a basic React app using Elastic Beanstalk command line interface (EB CLI). The experience had to be as simple as possible.

I decided to use Razzle, a server-rendering framework, for this experiment because I think it's more flexible than Next.js. But we can easily adapt the commands explained below for other frameworks.

Prerequisites

If you want to follow along and run the commands, be sure to:

AWS Elastic Beanstalk Node.js platform

I have to talk first about the changes I made to Razzle boilerplate and explain why I made it before deploying the React app.

Switch from yarn to npm

By default, Elastic Beanstalk Node.js platform uses npm as package manager contrary to Razzle which uses yarn. I tried to customize Elastic Beanstalk to use yarn but I found this difficult and also useless for my purpose.

I wanted to keep it simple! So, I decided to switch the boilerplate to npm and stay with AWS default configuration.

To switch to npm, I simply removed node_modules folder and yarn.lock file and reinstalled the dependencies:

rm -rf node_modules yarn.lock && npm install

Customize AWS Elastic Beanstalk environment

When you deploy with Elastic Beanstalk Node.js platform, it will use by default app.js, then server.js, and then npm start (in that order) to start your application.

Because I wanted to deploy my compiled app, I had to figure out a way to build my application before starting it.

I found out I can use configuration files to customize AWS resources or configure Elastic Beanstalk environment.

Basically, a configuration file is a YAML or JSON file with the extension .config that you place in a folder named .ebextensions of your source code.

I created node-settings.config file with the following content to customize Elastic Beanstalk Node.js default command:

option_settings:
  aws:elasticbeanstalk:container:nodejs:
    NodeCommand: "npm run build && npm run start:prod"

After deploying with this configuration, I had the following screen when I tried to access the public URL:

502 Bad Gateway

I spent almost one hour debugging this error! I had to set up my Elastic Beanstalk environment for SSH and check the logs.

Elastic Beanstalk couldn't process the build and start:prod commands sequentially... 😢

The nodejs.log helped me to understand the problem:

> my-razzle-app@0.1.0 build /var/app/current
> razzle build "&&" "npm" "run" "start:prod"

Creating an optimized production build...
Compiling client...
Compiled client successfully.
Compiling server...
Compiled server successfully.
Compiled successfully.

File sizes after gzip:

  49.76 KB  build/static/js/bundle.28c7c906.js
  323 B     build/static/css/bundle.ab37dd62.css


┌──────────────────────────────────────────────────┐
│             npm update check failed              │
│       Try running with sudo or get access        │
│       to the local update config store via       │
│ sudo chown -R $USER:$(id -gn $USER) /tmp/.config │
└──────────────────────────────────────────────────┘

Add a new script line in package.json

I decided to update my package.json by adding a new script line eb:prod combining both the build and start:prod commands to solve the problem:

{
  "name": "my-razzle-app",
  "version": "0.1.0",
  "license": "MIT",
  "scripts": {
    "start": "razzle start",
    "build": "razzle build",
    "test": "razzle test --env=jsdom",
    "start:prod": "NODE_ENV=production node build/server.js",
    "eb:prod": "npm run build && npm run start:prod"
  },
  "dependencies": {
    "express": "^4.17.1",
    "razzle": "^3.0.0",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-router-dom": "^5.1.2"
  }
}

And also modify my node-settings.config file:

option_settings:
  aws:elasticbeanstalk:container:nodejs:
    NodeCommand: "npm run eb:prod"

Solve permission denied with .npmrc

During the deployment, I also faced permission denied errors. Fortunately for me, someone already had this error and wrote a solution in stack overflow! 👍

You will find an extract of the error below:

  gyp ERR! clean error
  gyp ERR! stack Error: EACCES: permission denied, rmdir 'build'
  gyp ERR! System Linux 4.14.171-105.231.amzn1.x86_64
  gyp ERR! command "/opt/elasticbeanstalk/node-install/node-v12.16.1-linux-x64/bin/node" "/opt/elasticbeanstalk/node-install/node-v12.16.1-linux-x64/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "rebuild"
  gyp ERR! cwd /tmp/deployment/application/node_modules/fsevents
  gyp ERR! node -v v12.16.1
  gyp ERR! node-gyp -v v5.0.5
  gyp ERR! not ok
  npm ERR! code ELIFECYCLE
  npm ERR! errno 1
  npm ERR! fsevents@1.2.12 install: `node-gyp rebuild`
  npm ERR! Exit status 1
  npm ERR!
  npm ERR! Failed at the fsevents@1.2.12 install script.
  npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

  npm ERR! A complete log of this run can be found in:
  npm ERR!     /tmp/.npm/_logs/2020-04-12T17_29_44_535Z-debug.log
  Running npm install:  /opt/elasticbeanstalk/node-install/node-v12.16.1-linux-x64/bin/npm
  Setting npm config jobs to 1
  npm config jobs set to 1
  Running npm with --production flag
  Failed to run npm install. Snapshot logs for more details.
  UTC 2020/04/12 17:29:44 cannot find application npm debug log at /tmp/deployment/application/npm-debug.log

To solve this problem, I simply created .npmrc file with the following content:

# Force npm to run node-gyp also as root
unsafe-perm=true

AWS Elastic Beanstalk Docker platform

I also have to talk about the changes I made for the Docker platform. These changes are simpler than Node.js platform.

I only created Docker files, switched my package manager from yarn to npm, and updated my package.json file.

Switch from yarn to npm

See the explanations above in the previous section... 👆

Add a new script line in package.json

Because I learned my lessons with Elastic Beanstalk Node.js platform, I decided to use directly eb:prod script line in my package.json before creating the Docker files:

{
  "name": "my-razzle-app",
  "version": "0.1.0",
  "license": "MIT",
  "scripts": {
    "start": "razzle start",
    "build": "razzle build",
    "test": "razzle test --env=jsdom",
    "start:prod": "NODE_ENV=production node build/server.js",
    "eb:prod": "npm run build && npm run start:prod"
  },
  "dependencies": {
    "express": "^4.17.1",
    "razzle": "^3.0.0",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-router-dom": "^5.1.2"
  }
}

Create Docker files

Elastic Beanstalk only needs a Dockerfile to launch Docker environments. Basically, Elastic Beanstalk builds an image based on the commands provided in the Dockerfile.

Here is the Dockerfile I used to deploy my application:

FROM node:10
# where our app will be located in the image
RUN mkdir -p /app
WORKDIR /app
# move all source code
COPY . .
RUN npm install
CMD [ "npm", "run", "eb:prod" ]
EXPOSE 3000

To ignore node_modules folder during the build, I also created .dockerignore file:

node_modules

Deploy your server-side React app

It's easy to deploy your application with Elastic Beanstalk CLI. You can use the same commands for both Node.js platform and Docker platform. You simply need to know 4 commands:

eb init

This command is used to initialize your Elastic Beanstalk project by specifying your default region, application name, and platform (Node.js or Docker in our case). Behind the scenes, AWS will create an S3 bucket and your application in Elastic Beanstalk.

$ eb init

Select a default region
1) us-east-1 : US East (N. Virginia)
2) us-west-1 : US West (N. California)
3) us-west-2 : US West (Oregon)
4) eu-west-1 : EU (Ireland)
5) eu-central-1 : EU (Frankfurt)
6) ap-south-1 : Asia Pacific (Mumbai)
7) ap-southeast-1 : Asia Pacific (Singapore)
8) ap-southeast-2 : Asia Pacific (Sydney)
9) ap-northeast-1 : Asia Pacific (Tokyo)
10) ap-northeast-2 : Asia Pacific (Seoul)
11) sa-east-1 : South America (Sao Paulo)
12) cn-north-1 : China (Beijing)
13) cn-northwest-1 : China (Ningxia)
14) us-east-2 : US East (Ohio)
15) ca-central-1 : Canada (Central)
16) eu-west-2 : EU (London)
17) eu-west-3 : EU (Paris)
18) eu-north-1 : EU (Stockholm)
19) ap-east-1 : Asia Pacific (Hong Kong)
20) me-south-1 : Middle East (Bahrain)
(default is 3): 1

Enter Application Name
(default is "ssr-app-nodejs"): ssr-app-nodejs
Application ssr-app-nodejs has been created.

It appears you are using Node.js. Is this correct?
(Y/n): Y

Select a platform version.
1) Node.js 12 (BETA)
2) Node.js 10 (BETA)
3) Node.js
(default is 1): 3
Cannot setup CodeCommit because there is no Source Control setup, continuing with initialization
Do you want to set up SSH for your instances?
(Y/n): n

eb create --single

After Elastic Beanstalk project is initialized, you can create your environment to deploy your React app. I choose deliberately to deploy without load balancer to reduce costs!

$ eb create --single
Enter Environment Name
(default is ssr-app-nodejs-dev): ssr-app-nodejs-dev
Enter DNS CNAME prefix
(default is ssr-app-nodejs-dev): ssr-app-nodejs-dev
Creating application version archive "app-200412_100649".
Uploading: [##################################################] 100% Done...
Environment details for: ssr-app-nodejs-dev
  Application name: ssr-app-nodejs
  Region: us-east-1
  Deployed Version: app-200412_100649
  Environment ID: e-wyzcjqwm6v
  Platform: arn:aws:elasticbeanstalk:us-east-1::platform/Node.js running on 64bit Amazon Linux/4.14.1
  Tier: WebServer-Standard-1.0
  CNAME: ssr-app-nodejs-dev.us-east-1.elasticbeanstalk.com
  Updated: 2020-04-12 08:08:14.088000+00:00
Printing Status:
2020-04-12 08:08:13    INFO    createEnvironment is starting.
2020-04-12 08:08:14    INFO    Using elasticbeanstalk-us-east-1-717193739363 as Amazon S3 storage bucket for environment data.
2020-04-12 08:08:43    INFO    Created security group named: awseb-e-wyzcjqwm6v-stack-AWSEBSecurityGroup-1BAOESKYY2P2Q
2020-04-12 08:08:59    INFO    Created EIP: 18.214.57.52
2020-04-12 08:10:04    INFO    Waiting for EC2 instances to launch. This may take a few minutes.
2020-04-12 08:11:20    INFO    Application available at ssr-app-nodejs-dev.us-east-1.elasticbeanstalk.com.
2020-04-12 08:11:20    INFO    Successfully launched environment: ssr-app-nodejs-dev

eb open

When your React app is deployed, you can open your default browser with the command eb open. You have to be patient first... It can take more than 5 minutes to deploy!

eb terminate

Don't forget to terminate the environment when you are done with your application!

You don't want to pay for an EC2 instance you don't need, right?

$ eb terminate
The environment "ssr-app-nodejs-dev" and all associated instances will be terminated.
To confirm, type the environment name: ssr-app-nodejs-dev
2020-04-12 08:34:39    INFO    terminateEnvironment is starting.
2020-04-12 08:34:57    INFO    Waiting for EC2 instances to terminate. This may take a few minutes.
2020-04-12 08:36:43    INFO    Deleted EIP: 18.214.57.52
2020-04-12 08:36:43    INFO    Deleted security group named: awseb-e-wyzcjqwm6v-stack-AWSEBSecurityGroup-1BAOESKYY2P2Q
2020-04-12 08:36:45    INFO    Deleting SNS topic for environment ssr-app-nodejs-dev.
2020-04-12 08:36:46    INFO    terminateEnvironment completed successfully.

Conclusion

With only 4 commands we deployed a basic React app using Elastic Beanstalk CLI! 🎉🎉🎉

I found this CLI very nice to use with few commands to remember. It was great!

My problems started when I encountered errors! Error messages are not explicit and I had to check the logs. At first, I tried to get the logs using Elastic Beanstalk console but I found more convenient to directly configure my environment to use SSH and use command lines.

I found the overall experiment time-consuming! I don't know if it was because of the EC2 instances (t2.micro) but each deployment took me at least 4 or 5 minutes...

Fortunately, Elastic Beanstalk documentation is very useful and helps me to solve most of my problems. Also, I found answers easily to my other problems on Google because someone had them already...

That's it for me, hope you learned something!

Don't forget to Stay Home, Save Lives, and Pass your AWS exam!

Posted on by:

johanrin profile

Johan Rin

@johanrin

Technical Architect @delaware_france. Living in the Cloud. AWS and Azure Certified.

Discussion

markdown guide