Skip to content

Deploying an AdonisJS application with PM2 and Catapult

This guide walks through a production-ready deployment setup for an AdonisJS application: versioned releases on a VPS, PM2 for process management, and automatic rollback on failure.

Prerequisites

  • A VPS with SSH key access and Node.js installed
  • An AdonisJS application in a Git repository
  • Catapult installed as a dev dependency
bash
npm install --save-dev @catapultjs/deploy

Configuration

Create a deploy.ts at the root of your project:

typescript
import { defineConfig } from '@catapultjs/deploy'
import '@catapultjs/deploy/recipes/git'
import '@catapultjs/deploy/recipes/adonisjs'
import '@catapultjs/deploy/recipes/pm2'

export default defineConfig({
  repository: 'git@github.com:you/myapp.git',
  hosts: [
    {
      name: 'production',
      ssh: 'deploy@your-server.com',
      deployPath: '/home/deploy/myapp',
    },
  ],
})

The three recipes wire automatically into the pipeline:

  • recipes/git — clones the repository into each release via a bare mirror cached on the server
  • recipes/adonisjs — installs dependencies, builds assets and runs migrations
  • recipes/pm2 — reloads PM2 after the release is published

Server setup

Run setup once per host to create the directory structure:

bash
npx cata deploy:setup

This creates releases/, shared/ and .catapult/ under deployPath. Add your .env to shared/:

bash
scp .env deploy@your-server.com:/home/deploy/myapp/shared/.env

The adonisjs recipe already declares .env and storage/ as shared paths. If you need to add more, use set() before the recipe import:

typescript
import { defineConfig, set } from '@catapultjs/deploy'

set('shared_dirs', ['storage', 'logs', 'tmp/uploads'])
set('shared_files', ['.env'])

import '@catapultjs/deploy/recipes/git'
import '@catapultjs/deploy/recipes/adonisjs'
import '@catapultjs/deploy/recipes/pm2'

export default defineConfig({ ... })

First deployment

bash
npx cata deploy

Catapult runs the full pipeline, verifies the healthcheck URL, and activates the new release atomically. If the healthcheck fails, it rolls back automatically.

PM2 ecosystem file

The pm2 recipe expects an ecosystem.config.cjs at the root of each release. A minimal example:

javascript
const path = require("path");
const root = path.resolve(__dirname, "../../", "current");

module.exports = {
  apps: [
    {
      name: 'myapp',
      script: path.join(root, 'build/bin/server.js'),
      instances: 'max',
      exec_mode: 'cluster',
      env: { NODE_ENV: 'production' },
    },
  ],
}

Commit it to your repository — it will be deployed with every release.

Rolling back

If something goes wrong after a deploy, roll back to the previous release in one command:

bash
npx cata rollback

Or pick a specific release interactively:

bash
npx cata rollback --interactive

What's next

  • Add a GitHub Actions workflow to trigger deploys on push to main
  • Use cata status to inspect the active release and PM2 version across hosts
  • Explore the adonisjs_local recipe if you prefer building the app locally before uploading