NextJS on Elastic Beanstalk with Circle CI

Posted on March 08, 2019

It’s a dream combination to have. But like all the things, it’s hard to get there. But here is a crash course.

PS: This assumes that you already know why you want to use Elastic Beanstalk and CircleCI.

Planned Workflow:

Flow from Local to CI to EB
  1. We create the application locally in different branches.
  2. We merge to master branch.
  3. CircleCI runs lint and test on these branches.
  4. CircleCI runs the NextJS build. (Also, run custom server build if you have one)
  5. CircleCI creates a ZIP file with necessary folders and pushed it to Elastic Beanstalk.
  6. Elastic Beanstalk runs the build and application is deployed.

Thanks to Ryan Simms Gist for the cornerstone.

So, let’s start.

Setup EB Environment

  1. Create New Application
  2. Create New Environment
  3. When it asks you over two choices, choose Create a Web Server.
  4. Configure the rest of the server, you would probably want a load balancer in there if you want to setup HTTPS.

Setup CircleCI

  1. Create a CircleCI application
  2. Link it to your repository on Github/Gitlab and select your technology.
  3. We have NodeJS here and our configuration script being:
1version: 2
2jobs:
3 test:
4 docker:
5 -
6 image: 'circleci/node:10.8.0'
7 working_directory: ~/app-name-directory
8 steps:
9 - checkout
10 -
11 restore_cache:
12 name: Restore Yarn Package Cache
13 keys:
14 - yarn-packages-{{ checksum "yarn.lock" }}
15 -
16 run: 'yarn install'
17 -
18 save_cache:
19 name: Save Yarn Package Cache
20 key: 'yarn-packages-{{ checksum "yarn.lock" }}'
21 paths:
22 - ~/.cache/yarn
23 -
24 run: 'yarn test'
25 deploy_prod:
26 working_directory: ~/app-name-directory
27 docker:
28 -
29 image: 'circleci/node:10.8.0'
30 steps:
31 - checkout
32 -
33 run:
34 name: 'Build Application'
35 command: yarn install && yarn run build && yarn run generate-zip
36 -
37 run:
38 name: 'Install Python'
39 command: "sudo apt-get -y -qq update
40sudo apt-get -y -qq install python3.4-dev
41echo 'export PATH=/root/.local/bin/:$PATH' >> $BASH_ENV
42echo 'export PATH=/home/circleci/.local/bin/:$PATH' >> $BASH_ENV
43"
44 -
45 run:
46 name: 'Install pip'
47 command: "curl -O https://bootstrap.pypa.io/get-pip.py
48python3.4 get-pip.py --user
49"
50 -
51 run:
52 name: 'Install AWS EB CLI & S3 CLI'
53 command: "pip install awsebcli --upgrade --user
54pip install awscli --upgrade --user
55"
56 -
57 run:
58 name: 'Deploy to Elastic Beanstalk'
59 command: "eb use AppName-env
60timeout 1m eb deploy -v --staged || true
61"
62workflows:
63 version: 2
64 Test_and_Deploy:
65 jobs:
66 - test
67 -
68 deploy_prod:
69 requires:
70 - test
71 filters:
72 branches:
73 only: master

This configuration runs in yarn test on all branches pushed. It runs deploy_prod only on the master branch, after running test successfully.

eb use AppName-env
timeout 1m eb deploy -v --staged

In this command, use the name you provided to your environment earlier in Elastic Beanstalk.

Create IAM user

Now we have to provide permissions for Circle CI to deploy to Elastic Beanstalk. This is done with the help of AWS IAM User.

  1. Add a AWS User from here
  2. Set a username and select Programmatic access as the Access type
  3. Click ‘Create group’ on the user permissions page
  4. Set a group name and search for the AWSElasticBeanstalkFullAccess policy type and select it
  5. Create the group so it’s assigned to your new user
  6. Review and create the user.

Add IAM User to CircleCI

Add deployment user environment variables to CircleCi

  • Project Settings > Environment Variables
    • AWS_ACCESS_KEY_ID
    • AWS_SECRET_ACCESS_KEY

Deploying NextJS

Now, is the tricky part. You do not want to transfer every part of your application to Elasticbeanstalk. Instead, you might want to transfer the .next folder that houses your build and may be a custom server combination you are using with it.

Elastic Beanstalk very essentially works with a ZIP file being uploaded to the service. This is what happens when you use the CI with the command eb deploy, but here what we want to do is create a custom ZIP file for EB and ask it to deploy the same.

yarn run generate-zip

This is what this line in our CircleCI configuration is doing.

"generate-zip": "sh ./scripts/generate-zip.sh"

generate-zip command in our package.json points to an sh file that is to be executed after taking build of your application.

Here is the generate-zip.sh file:

# If the directory, `dist`, doesn't exist, create `dist`
stat dist || mkdir dist
# Archive artifacts
zip dist/$npm_package_name.zip -r dist .next package.json yarn.lock next.config.js assets

This creates a ZIP in the dist folder with .next, package.json, lock files, config scripts and assets. If you have a custom server being build somewhere, add that folder name too. In my case, it is being build to dist folder itself.

Now, we need to ask EB to actually use this zip file. We do that with .elasticbeanstalk/config.yml file.

1branch-defaults:
2 master:
3 environment: AppName-env
4global:
5 application_name: app-name
6 default_ec2_keyname: app-web
7 default_platform: Node.js
8 default_region: ap-south-1
9 include_git_submodules: true
10 instance_profile: null
11 platform_name: null
12 platform_version: null
13 profile: null
14 sc: git
15 workspace_type: Application
16deploy:
17 artifact: dist/appname.zip

Note: Use the same names you used while creating the environment here too.

The last line is the major part to take notice. This is the ZIP we made with the sh file in last step.

Now, just push the master branch and it should build the application on EB.

Feel free to create an issue if you need help.