Capistrano can be used to deploy a NextJS app into a linux server as well. We can take advantages of capistrano rake tasks to automate the whole process of building and restarting pm2 server. Start with adding gemfile to the root of the project.

# Gemfile
source 'https://rubygems.org'

gem 'capistrano'
gem 'capistrano-npm'
gem 'dotenv' 
gem 'httparty' # these will be used to send honeybadger notification after deployment is complement

and bundle:

bundle install

Now we can intialize config for capistrano and add some configs.

bundle exec cap install

This commnd creates multiple files required by capistrano to read configuration and work accordingly. we have following files:

config/deploy.rb
config/deploy/production.rb
config/deploy/staging.rb
Capfile

The production.rb and staging.rb are environment specific configuration files. For eg: server’s ip or git branch to build is different for production and staging environments that can be mentioned in those configuration files. Before modifying those files let’s first modify the Capfile.

# Capfile
# Load DSL and set up stages
require "capistrano/setup"
require "capistrano/deploy"
require "capistrano/scm/git"
require "capistrano/npm"

install_plugin Capistrano::SCM::Git
# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }

we are requiring capistrano DSLs(basic predefined classes from capistrano and other packages) for git and npm managment. We are also requiring custom rake tasks. We haven’t written the rake tasks yet. We will also modify deploy.rb to add generalized configurations for both of our environments and environment specific files for environment specific configurations.

# config/deploy/production.rb
role :web, %w{xxx.xxx.xxx.xxx}
# config/deploy.rb
# config valid for current version and patch releases of Capistrano
lock "~> 3.11.0"

set :application, "my-app-name"
set :repo_url, "git@github.com:my-app/my-app-frontend.git"
set :remote_user, "deploy"
set :application_folder, "my-app-folder"
set :npm_flags, ""
set :deploy_to, "/home/#{fetch(:remote_user)}/#{fetch(:application_folder)}"
set :keep_releases, 3
set :honeybadger_env, fetch(:stage)
set :honeybadger_server, -> { roles(:web).first.hostname }

append :linked_files, ".env"

set :ssh_options, {
  user: fetch(:remote_user), # overrides user setting above
  forward_agent: true,
  auth_methods: %w(publickey),
  port: 3939
}

# Default branch is :master
ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp

after "npm:install", "npm:build"
after "deploy:publishing", "pm2:reload"
after "deploy:finished", "deploy:notify_honeybadger"

Every after hook is a rake task, some of them are custom written, let’s write them.

# lib/capistrano/tasks/capistrano_npm.rake
namespace :npm do
  desc "Build Web application"
  task :build do
    on roles(:web) do
      within fetch(:npm_target_path, release_path) do
        with fetch(:npm_env_variables, {}) do
          execute :npm, "run build:#{fetch(:stage)}"
        end
      end
    end
  end
end
# lib/capistrano/tasks/post_tasks.rake
namespace :pm2 do
  desc "Reload PM2 instance"
  task :reload do
    on roles(:web) do
      within fetch(:release_path) do
        execute :pm2, "reload application.json --env #{fetch(:stage)}"
      end
    end
  end
end

namespace :deploy do
  desc "Notifies Honeybadger locally using HTTParity"
  task :notify_honeybadger do
    require "httparty"
    require "dotenv/load"

    honeybadger_api_url = "https://api.honeybadger.io/v1/deploys"
    options = {
      body: {
        deploy: {
          environment: fetch(:stage),
          revision: fetch(:revision),
          repository: fetch(:repo_url),
          local_username: ENV["USER"],
        },
        api_key: ENV["HONEYBADGER_API_KEY"],
      },
    }
    result = HTTParty.post(honeybadger_api_url, options)

    if result&.parsed_response["status"] == "OK"
      puts "Honeybadger Notification Complete."
    else
      puts "Honeybadger Notification Failed: #{result.parsed_response}"
    end
  end
end

The above rake tasks are for reloading a pm2 instance and sending out http request for honeybadger notification after everything from capistrano is done.