DEV Community

Cover image for Deploy Adonis 5 in Production (with CI/CD + Github Actions)
Julien Le Coupanec
Julien Le Coupanec

Posted on • Updated on

Deploy Adonis 5 in Production (with CI/CD + Github Actions)

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

4. Set up Redis (For Adonis Bull).

sudo apt install redis-server
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Check that redis is working.

sudo systemctl status redis
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

5. Build for Production

Go back to your project repository and create a new .env file.

nano .env
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

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 run cp .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?
Enter fullscreen mode Exit fullscreen mode

One final step is to run the migrations and seed the database.

node ace migration:run --force
node ace db:seed --force
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Useful certbot commands

certbot certificates # List certificates
certbot delete       # Delete a specific certificate
Enter fullscreen mode Exit fullscreen mode

Configure Nginx.

cd /etc/nginx/sites-available
nano default
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Then, restart Nginx:

sudo systemctl restart nginx
Enter fullscreen mode Exit fullscreen mode

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 }}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)