The Onist

The Onist


Tags


Deploying an Express App to AWS Beanstalk

A broad overview of AWS's Beanstalk service, its default Node and NPM configurations, build customization options, and a few of my hard learned lessons along the way.

Introduction to Beanstalk

Elastic Beanstalk is an Amazon web service that allows you to quickly deploy and manage web applications to the cloud without the hassle of learning and managing the infrastructure surrounding it all. You can simply upload your application and Beanstalk automatically handles the details of capacity provisioning, load balancing, scaling, and application health monitoring.

Beanstalk supports applications developed in Go, Java, .NET, Node.js, PHP, Python, and Ruby. When you deploy your application, Elastic Beanstalk builds the selected supported platform version and provisions one or more AWS resources, usually Amazon EC2 instances, to seamlessly host and run your application. Simply upload your application in the form of an application source bundle (usually a .zip of the source) to Elastic Beanstalk, and then provide some information about the application. Elastic Beanstalk automatically launches an environment and creates and configures the AWS resources needed to run your application.

To deploy a test application to Beanstalk, follow this quick guide provided by Amazon. When on the "Create Application" step, use the following configurations:

We will obviously modify these settings and add our own source code next. But after following the rest of the guide, you will have an EC2 instance created and deployed as well as a fully functioning web app.

Custom Build & Express Configuration

After the EC2 instance is setup and provisioned, your source code will be unzipped and added to the Docker container. Once this has occurred, the following will happen by default unless otherwise configured:

  1. npm install --production will be ran.
  2. npm start will be ran whereby your code should be built and the server started and listening on port 8081.

This configuration may work for some but my workflow was much more complicated than this. For myself, I needed the following:

  1. npm install to be ran in development mode.
  2. Transpile, bundle and generate translations for my application via Babel and Webpack.
  3. Start my Express server on a port other than 8081.

To fix the issues above, first create an .ebextensions folder in the root of your project. Next add 00_change_npm_permissions.config and options.config files to this folder. These configuration files can be written in either YML or JSON. My examples below use YML.

The configuration file below technically fixes all of the issues outlined above. But depending on your applications dependencies you my need another configuration file as I did.

option_settings:
  aws:elasticbeanstalk:application:environment:
    PORT: 1771
    NPM_USE_PRODUCTION: false
  aws:elasticbeanstalk:container:nodejs:
    NodeCommand: "npm run deploy"
    NodeVersion: 10.15.1

The source above is pretty self explanatory but lets explain further:

Great! That should customize and therefore fix all of the problems for most people. However I had a few dependencies that required access to the tmp directory to install properly. I found this out when I attempted to deploy my app and ran into the following error:

npm ERR! Error: EACCES: permission denied, open '/tmp/.npm/_locks
npm ERR! Please try running this command again as root/Administrator.

To fix this error, add the the following to your 00_change_npm_permissions.config file. This will instruct the container to run a chmod cmd the tmp folder to give the node process write access to the directory.

files:
  "/opt/elasticbeanstalk/hooks/appdeploy/post/00_set_tmp_permissions.sh":
    mode: "000755"
    owner: root
    group: root
    content: |
      #!/usr/bin/env bash
      chown -R nodejs:nodejs /tmp/.npm

Below is a contrived example of my Express server file that is listening on my custom port.

const express = require('express')

const app = express()
app.set('port', (process.env.PORT || 1771))
app.use(express.static('dist'))

app.listen(app.get('port'), () => {
	console.log('Express server has started! http://localhost:' + app.get('port') + '/')
})

To complete my build process and then start my Express server, I took advantage of NPMs pre and post script ability to run multiple tasks via one command. Read more about this awesome feature here.

{
  "predeploy": "npm install",
  "deploy": "cross-env-shell NODE_ENV=production webpack-cli --config ./config/webpack.prod.config.js --display-error-details",
  "postdeploy": "node prodServer.js",
}

With this configuration, I can now install dependencies via predeploy, then transpile and build my code via deploy and finally start my server via postdeploy. This will be executed in order by the npm run deploy command that I configured the build container to initialize with in the options.config file above.

Uploading The App

Now that the application and server is configured, uploading and deploying it is pretty straightforward. First we will need to compress our source code (not including the root container folder) into into a zip file. For example, you may have all of your files in a folder called "Project". Rather than zipping "Project", you should zip and upload the contents of "Project". Be sure to include hidden files and folders and not the node_modules folder, as the dependencies will be installed during the build process.

If you fail to zip the source correctly, you will most likely get the following error when attempting to deploy.

npm ERR! enoent ENOENT: no such file or directory, open '/var/app/current/package.json'
npm ERR! enoent This is most likely not a problem with npm itself
npm ERR! enoent and is related to npm not being able to find a file.

Next go to your Elastic Beanstalk applications page inside AWS and click the "Upload and Deploy" button. Add your new zip file, give the version a label and deploy the application. It might take a few minutes to build and deploy the application and the health of the system will fluctuate between various statuses.

Key Takeaways

Next Time

That's really all there is to it if you're okay with a manually intensive build process. But c'mon, let's be real here, we are engineers and we all hate repetitive tasks. As with anything AWS, it is super simple to create an automated CI/CD pipeline that is complete with source code webhooks, build stages, test stages and custom deployment options.

Tune in next time as I will dive into AWS's Codebuild service!

Share Post
View Comments