Deploying next.js on AWS ElasticBeanstalk
Michael Weibel
Posted on June 10, 2020
AWS ElasticBeanstalk (EB) is a service to deploy applications in a simple manner.
AWS EB has quite a range of features. It allows you to configure rolling deployment, monitoring, alerting, database setup, etc. It's generally much easier to use than doing it from scratch.
As with all such systems, this comes at a cost: you initially don't know a lot about the system and figuring out what's wrong might be difficult.
Additionally, AWS EB recently switched to Amazon Linux 2. This new version has a different way to deploy than the previous version "Amazon Linux AMI". As a result, lots of articles and StackOverflow questions/answers are outdated.
The documentation on AWS itself could be a lot better, too. It's not always clear to which version the docs refer to. For example serving static files does not work for Amazon Linux 2.
I deployed a next.js app on AWS EB recently and learned a few tricks. Here's a quick summary of them.
NODE_ENV
To configure the correct NODE_ENV when building and running the application on AWS EB, place the following contents in the folder .ebextensions/options.config
:
option_settings:
aws:elasticbeanstalk:application:environment:
NODE_ENV: production
.ebignore
.ebignore
allows to ignore files when deploying the repository archive using the EB CLI. The format is just like .gitignore
and if .ebignore
is not present, the deployment uses .gitignore
instead. Usually there are certain things which should be in git but not in the deployed archive, hence the need for a .ebignore
file.
Here's my example .ebignore
:
# dependencies
node_modules/
# repository/project stuff
.idea/
.git/
.gitlab-ci.yml
README.md
# misc
.DS_Store
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# non prod env files
.env.development
.env.test
PORT env variable
Like many other systems, AWS EB exposes the PORT
environment variable to specify on which port the app should listen on. If you don't customize the server, ensure to adjust your npm start
script in package.json
as follows:
"start": "next start -p $PORT"
Using yarn instead of npm
In case you have issues with dependencies not installed correctly (read: weird deployment issues you don't have locally), it might be because you use yarn
instead of npm
. AWS EB uses by default npm
to install your dependencies. If you use yarn
, the repository usually has a yarn.lock
file instead of a package-lock.json
. Here's how to "switch" to yarn instead:
# place in .platform/hooks/prebuild/yarn.sh
#!/bin/bash
# need to install node first to be able to install yarn (as at prebuild no node is present yet)
sudo curl --silent --location https://rpm.nodesource.com/setup_12.x | sudo bash -
sudo yum -y install nodejs
# install yarn
sudo wget https://dl.yarnpkg.com/rpm/yarn.repo -O /etc/yum.repos.d/yarn.repo
sudo yum -y install yarn
# install
cd /var/app/staging/
# debugging..
ls -lah
yarn install --prod
chown -R webapp:webapp node_modules/ || true # allow to fail
Ensure to specify the correct node.js version in the path of the curl
command.
"switch" is in quotes because after predeploy eb engine
will still run npm install
. However it seems to work quite well regardless.
I'd recommend: If you can avoid it, use npm.
Serving static files via nginx
It makes sense to serve static files directly via nginx. This avoids unnecessary load on the node.js server and nginx is generally much faster in serving static content.
Place the following file in .platform/nginx/conf.d/elasticbeanstalk/static.conf
:
root /var/app/current/public;
location @backend {
proxy_pass http://127.0.0.1:8080;
}
location /images/ {
try_files $uri @backend;
# perf optimisations
sendfile on;
sendfile_max_chunk 1m;
tcp_nopush on;
tcp_nodelay on;
}
# add more folders as you need them, using as similar location directive
Additionally you could add caching for the /_next/static
path - feel free to try it out. I didn't do it yet to avoid too many changes at once.
GZIP compression
Enabling GZIP Content-Encoding on nginx level requires you to override the default nginx.conf
. Find the default nginx.conf
in /etc/nginx/nginx.conf
, copy the contents to .platform/nginx/nginx.conf
and replace gzip off;
to gzip on;
.
Here's the current (June 2020) example:
#Elastic Beanstalk Nginx Configuration File
user nginx;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
worker_processes auto;
worker_rlimit_nofile 32153;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
include conf.d/*.conf;
map $http_upgrade $connection_upgrade {
default "upgrade";
}
server {
listen 80 default_server;
access_log /var/log/nginx/access.log main;
client_header_timeout 60;
client_body_timeout 60;
keepalive_timeout 60;
gzip on; # CHANGED(mw): enable gzip compression
gzip_comp_level 4;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
# Include the Elastic Beanstalk generated locations
include conf.d/elasticbeanstalk/*.conf;
}
}
Finally, disable gzip
compression in next.js to avoid double compressing and reduce load on the node.js server.
Deployment
Run, in the following order:
$ npm run build
$ eb deploy
Logging/Debugging
Here's a bunch of important files/directories. You might need sudo
to see/read those paths.
Path | Directory |
---|---|
/etc/nginx/ |
Nginx configurations |
/var/app/current |
Deployed application files |
/var/app/staging |
Only during deployment |
/opt/elasticbeanstalk |
Binaries, Configs, ... from AWS EB itself |
/var/proxy/staging |
Nginx staging deployment config |
/var/log/eb-engine.log |
Deployment log |
/var/log/web-stdout.log |
App stdout log |
/var/log/nginx |
Nginx log |
Other settings
Ensure to configure your AWS EB setup in the web console as well. Setup rolling deployments and configure monitoring/alarms.
Posted on June 10, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.