How to Auto Deploy a Next.js App on Ubuntu from GitHub

How to Auto Deploy a Next.js App on Ubuntu from GitHub

Streamline Your Deployment Process with Automation

Introduction:

Automating the deployment process of your Next.js app can save you time and effort, especially when you frequently update your codebase. In this tutorial, we'll walk through the steps to set up an auto-deployment pipeline for your Next.js app on an Ubuntu server using GitHub and PM2.

Prerequisites:

Before you begin, make sure you have the following:

  • An Ubuntu server with SSH access.

  • Git, Node.js, and npm installed on your server.

  • A Next.js app hosted on GitHub.

Step 1: Setting up your Ubuntu server:

First, we need to prepare our Ubuntu server by installing Git, Node.js, npm, and PM2. Connect to your server using SSH and run the following commands:

sudo apt update
sudo apt install git nodejs npm
sudo npm install -g pm2

Next, create an SSH directory for your user:

sudo mkdir /var/www/.ssh
sudo chown -R username:username /var/www/.ssh/

Replace username with your actual username.

Step 2: Setting up your GitHub repository:

Now, we'll generate an SSH key on your server and add it to your GitHub repository.

Generate an SSH key on your server:

sudo -Hu username ssh-keygen -t rsa

Replace username with your actual username.

Copy the SSH key and add it to your GitHub repository's Deploy Keys in the repository settings. This will allow your server to authenticate with GitHub without using a password.

Step 3: Setting up a webhook for auto-deployment:

In your GitHub repository settings, add a webhook with the Payload URL set to a custom API endpoint in your Next.js app. This endpoint will trigger the deployment process.

Step 4: Automating the deployment process with a shell script:

Create a shell script on your server to automate the deployment process. Create a new file, for example, deploy.sh, and add the following content:

#!/bin/bash

# Define the log file path
LOG_FILE="/var/www/html/error.log"
SUCCESS_FILE="/var/www/html/success.log"

# Function to log errors
log_error() {
    local message="$1"
    local timestamp="$(date +'%Y-%m-%d %H:%M:%S')"
    echo "[$timestamp] ERROR: $message" >> "$LOG_FILE"
}

log_success() {
    local message="$1"
    local timestamp="$(date +'%Y-%m-%d %H:%M:%S')"
    echo "[$timestamp] Success: $message" >> "$SUCCESS_FILE"
}

FOLDER="/var/www/html/your-repo"

# Check if the folder exists
if [ -d "$FOLDER" ]; then
    echo "Folder $FOLDER exists."
else
    echo "Folder $FOLDER does not exist."
    mkdir "$FOLDER" || { log_error "Failed to create directory"; exit 1; }
fi

# Change directory
cd "$FOLDER" || { log_error "Failed to change directory to $FOLDER"; exit 1; }

# If directory is not empty, fetch changes
if [ "$(ls -A)" ]; then
    # Fetch changes from the Git repository
    git fetch origin main || { log_error "Failed to fetch changes from Git repository"; exit 1; }
    git reset --hard origin/main || { log_error "Failed to reset local changes"; exit 1; }
else
    # Clone the repository if directory is empty
    git clone git@github.com:your-username/your-repo.git . || { log_error "Failed to clone repository"; exit 1; }
fi

npm install > /dev/null 2>&1
npm run build > /dev/null 2>&1

# Check if the application is already running
if pm2 list | grep -q "app-name"; then
    pm2 restart "app-name" || { log_error "Failed to restart the application with PM2"; exit 1; }
else
    pm2 start npm --name "app-name" -- start || { log_error "Failed to start the application with PM2"; exit 1; }
fi

# Save PM2 process list
pm2 save || { log_error "Failed to save PM2 process list"; exit 1; }

log_success "Deployment successful"

Make the script executable:

chmod +x deploy.sh

Step 5: Add the webhook URL in your Next.js API route:

Create a new API route in your Next.js app under the pages/api directory. For example, create a file named deploy.ts:

import { exec } from "child_process";
import { NextApiRequest, NextApiResponse } from "next";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method === "POST") {
    try {
      await runWebhook();
      return res.status(200).json({ message: "Success" });
    } catch (error) {
      console.error("Error executing webhook:", error);
      return res.status(500).json({ message: "Internal Server Error" });
    }
  } else {
    return res.status(405).json({ message: "Method Not Allowed" });
  }
}

const runWebhook = async () => {
  return new Promise((resolve, reject) => {
    exec(
      "/var/www/deploy.sh",
      { cwd: "/var/www/html" },
      (error, stdout, stderr) => {
        if (error) {
          console.error(`Error executing shell script: ${error}`);
          reject("Internal Server Error");
        } else {
          console.log(`Shell script output: ${stdout}`);
          resolve("Shell script executed successfully");
        }
      }
    );
  });
};

With these changes, your Next.js app will have an API route /api/deploy that can be triggered to run the deployment script. Additionally, pushing to the main branch will automatically trigger this API route, ensuring your app is deployed whenever you push changes.

Feel free to customize the API route and the GitHub Actions workflow to fit your specific requirements!

Conclusion
By following these steps, you've successfully set up an auto-deployment pipeline for your Next.js app on an Ubuntu server using GitHub and PM2. This will streamline your development workflow and ensure that your app is always up-to-date with the latest changes.

Additional Tips:

  • Use environment variables to manage sensitive information in your app.

  • Consider setting up a reverse proxy (e.g., Nginx) for better performance and security.