Deploy Adonis in Production
Let's see how we can deploy and run Adonis in production on a remote server. I will assume that you are using Ubuntu all along with this article to adapt the command for another distribution.
1. Initial Server Setup
Skip this step if your server is already set up.
sudo adduser adonis # Create a new user
sudo usermod -aG sudo adonis # Grant administrative privileges
sudo ufw allow OpenSSH # Make sure that the firewall allows SSH connections
sudo ufw enable # Enable the firewall
sudo apt-get update # Update packages
sudo apt-get install nodejs npm # Install node and NPM
sudo npm i -g pm2 # Install pm2 to manage node
# Install nvm to manage the node versions
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash
bash # Reload bash environment
nvm install 14.15.4 # Install your Node version
node -v # Check node version
To access your server with SSH, go back to your local machine and execute the following instructions to display your public key.
cat ~/.ssh/id_rsa.pub
Copy the SSH key printed to the terminal and go back to your remote server.
# If you are still logged in as root, run the command below:
su - adonis
mkdir ~/.ssh
chmod 700 ~/.ssh
nano ~/.ssh/authorized_keys # Copy the public key here
chmod 600 ~/.ssh/authorized_keys # Restrict the permissions of the authorized_keys file
2. Clone Your Adonis Repository
To allow the server to access your Adonis project on Github/Gitlab, we need to generate a new SSH key and it to your account.
# Generate a new ssh key
# Follow the instructions and make sure to remember the name for the newly created key
ssh-keygen -t rsa -b 2048 -C "email@example.com"
# Copy the SSH key
pbcopy < ~/.ssh/id_rsa.pub # MacOs
xclip -sel clip < ~/.ssh/id_rsa.pub # Linux (requires the xclip package)
cat ~/.ssh/id_rsa.pub | clip # Git Bash on Windows
Then, add this new SSH Key to Github or Gitlab. Read the article below to learn more about the exact steps.
You should now be able to clone your repository and install its dependencies.
git clone git@github.com:username/repository-name.git
git checkout production # If you need to switch to a specific production branch
npm install # Install dependencies
3. Set up Mysql
Here is how you can install MySQL on your server.
sudo apt install mysql-server
systemctl status mysql # Check the MySQL is installed
mysql_secure_installation # Secure MySQL (Follow the instructions)
mysql -u root -p # Connect to MySQL
Here are the commands to run to create a new user and database for your project.
# Create a new MySQL user
CREATE USER 'root'@'%' IDENTIFIED BY 'YOUR_PASSWORD';
# Replace YOUR_PASSWORD
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'YOUR_PASSWORD';
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'YOUR_PASSWORD';
# Create a new database
CREATE DATABASE database_name;
# Grant the new user privileges to the tables on the new database
GRANT ALL ON *.* TO 'root'@'%';
# For the changes to take effect
FLUSH PRIVILEGES;
# Exit the MySQL server
exit
To allow remote access:
ufw allow 3306
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
# Then replace 127.0.0.1 by 0.0.0.0 for bind-address
bind-address = 0.0.0.0
# Restart mysql
service mysql restart
4. Set up Redis (For Adonis Bull).
sudo apt install redis-server
Configure redis.
sudo nano /etc/redis/redis.conf
# Inside the file, find the supervised directive.
# This directive allows you to declare an init system to manage Redis as a service, providing you with more control over its operation.
# The supervised directive is set to no by default. Since you are running Ubuntu, which uses the systemd init system, change this to systemd.
supervised systemd
# By default, Redis is only accessible from localhost.
# However, if you installed and configured Redis by following a different tutorial than this one, you might have updated the configuration file to allow connections from anywhere.
# This is not as secure as binding to localhost.
bind 127.0.0.1 ::1
Check that redis is working.
sudo systemctl status redis
Upgrade Redis to a specific version.
# Next, download the redis tar file from https://redis.io/download, then install it from the directory it downloaded to:
cd ~/
wget http://download.redis.io/releases/redis-6.0.10.tar.gz redis-6.0.10.tar.gz
tar -xf redis-6.0.10.tar.gz
rm redis-6.0.10.tar.gz
cd redis-6.0.10
make
# Next, we’ll move the new installed redis to the location where the current instance is running.
sudo mv src/redis-server /usr/bin
sudo mv src/redis-cli /usr/bin
# After copy content you need restart redis-server:
sudo /etc/init.d/redis-server restart
# To validate the version of redis-server and redis-cli run:
redis-cli -v #redis-cli version
redis-cli INFO #redis-server version
rm -rf ~/redis-6.0.10
5. Build for Production
Go back to your project repository and create a new .env
file.
nano .env
npm run build # Generate the production bundle
cp .env build/.env # Copy the .env file to the production bundle
pm2 start build/server.js --name API # Start the Adonis server
You may be wondering why we need to copy the
.env
file to the build folder. In a nutshell, there is no standard way of defining environment variables. One could do it via a.env
file, via Heroku or AWS management console's or an app specification file for Digital Ocean. Earlier (before the recent release), we used to copy the.env
file. But this can lead to false-positive behavior too. This means that from now on, you must runcp .env build/.env
after each build.
Then ping your server with curl to see if everything is behaving correctly.
curl 127.0.0.1:3333 # Do you get a response?
One final step is to run the migrations and seed the database.
node ace migration:run --force
node ace db:seed --force
Regarding pm2
, here are a few commands you should know to manage your processes.
-
pm2 kill
: stop and remove all processes. -
pm2 start command --name PROCESS_NAME
: stop the process name -
pm2 stop PROCESS_NAME
: stop a given process -
pm2 restart PROCESS_NAME
: restart a given process
6. Set up Nginx with SSL.
To allow people to access Adonis from your domain name, we will install Nginx. We will also configure SSL to make sure the connection is secured.
sudo apt install nginx # Install Nginx
sudo ufw allow 'Nginx HTTPS' # Open the port 443 only (TLS/SSL encrypted traffic)
systemctl status nginx # Check that Nginx is running
sudo apt install certbot python3-certbot-nginx # Install certbot
sudo ufw disable # Disable the firewall while generating the certificate
sudo certbot certonly -d api.example.com # Generate the certificate
sudo ufw enable # Enable the firewall
Useful certbot commands
certbot certificates # List certificates
certbot delete # Delete a specific certificate
Configure Nginx.
cd /etc/nginx/sites-available
nano default
Here is a possible configuration file to deliver your website with SSL.
server {
server_name api.example.com;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://127.0.0.1:3333;
proxy_set_header Host $http_host;
proxy_cache_bypass $http_upgrade;
proxy_redirect off;
}
listen [::]:443 ssl ipv6only=on;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
server {
if ($host = api.example.com) {
return 301 https://$host$request_uri;
}
listen 80 default_server;
listen [::]:80 default_server;
server_name api.example.com;
return 404;
}
Then, restart Nginx:
sudo systemctl restart nginx
7. Set up Your Continuous Deployment Process with Github Actions
If you are looking to deploy a new version of Adonis while merging on your production branch, here is a GitHub action file that connects to your remove server, run some instructions and notify you on Slack if something fails or succeeds.
Make sure to configure the related secret variables:
secrets.HOST
,secrets.USERNAME
,secrets.KEY
,secrets.SLACK_WEBHOOK
.
name: Deploy
on:
push:
branches:
- production
jobs:
deploy:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v2
- name: Deploying
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY }}
script_stop: true
script: |
# Copy and build new version
rm -rf my-project-repo-new
cp -r my-project-repo my-project-repo-new
cd my-project-repo-new
git checkout production
git reset --hard origin/production
git pull
npm rebuild
npm install
# Build the api
npm run build
cp .env build/.env
# Run migrations
node ace migration:run --force
node ace db:seed --force
# Replace current version with the new one
cd ..
mv my-project-repo my-project-repo-old
mv my-project-repo-new my-project-repo
# Restart server
cd my-project-repo
pm2 delete my-project-repo
pm2 start build/server.js --name my-project-repo
rm -rf ../my-project-repo-old
- name: Slack success notification
if: success()
uses: rtCamp/action-slack-notify@master
env:
SLACK_CHANNEL: my-channel
SLACK_COLOR: good
SLACK_ICON: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
SLACK_MESSAGE: 'Deployment achieved with success'
SLACK_TITLE: CI
SLACK_USERNAME: GitHub
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
- name: Slack error notification
if: failure()
uses: rtCamp/action-slack-notify@master
env:
SLACK_CHANNEL: my-channel
SLACK_COLOR: danger
SLACK_ICON: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
SLACK_MESSAGE: 'The deployment has failed @channel'
SLACK_TITLE: CI
SLACK_USERNAME: GitHub
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
Top comments (0)