logo

Sue Feng Design

‹ Back to blog

Deploying a self-hosted Next.js App

In this post, I'll be going over deploying a self-hosted Next.js App, rather than using Vercel. This may be good for someone who has their own server and doesn't want to host on various platforms, to maintain all websites and apps on one server. It also involves using Github for the repository and using Github actions that trigger the testing and deployment. If this fits what you're looking for, then this blog post is for you.

As always, there's so many ways to do something, so go check out other options as well to see which one works best for you. You will likely need to customize parts of this tutorial to your needs. For example, wherever there's a Node version set, set it to the one you are using.

Ingredients

  1. A Next.js app that you'd like to deploy
  2. A Github repository for your app
  3. PM2 for running your app
  4. A Linux server
  5. A database such as MySQL for storing data (optional)
  6. Nginx or Apache for mapping your application to the correct folder location, and running the server

Instructions

Set up the server for your application

You'll need Nginx or Apache, Git, Node.js, MySQL, and PM2 on your server for this example application to run. Depending on where you're hosting your application, the commands for installation may differ. Here is an example using Ubuntu and Nginx.

💡 Note: Wherever there are words inside <...> , put your own content there.

Set the timezone

This is so that our error, access logs, and other server stuff will be in our timezone, making it easier for debugging and other stuff.

  1. sudo dpkg-reconfigure tzdata to your timezone

Get the most updated version of aptitude package manager for Ubuntu

  1. sudo apt update && sudo apt -y full-upgrade
  2. system reboot [ -f /var/run/reboot-required ] && sudo reboot -f

Setup your folder

  1. Create the folder for your web app.
cd /var/www
mkdir <your-web-app-folder>
  1. Set the owner for that folder.
sudo chown <username:username> -R <your-web-app-folder>
  1. Install Git if it doesn't exist yet.
sudo apt install git
  1. Initialize and set the origin for the Github repository. Change the part after origin to the one that matches yours.
git init
git remote add origin [email protected]:<user-name>/<repository-name>.git

nginx

nginx [engine x] is an HTTP and reverse proxy server, a mail proxy server, and a generic TCP/UDP proxy server, originally written by Igor Sysoev. For a long time, it has been running on many heavily loaded Russian sites including Yandex, Mail.Ru, VK, and Rambler. According to Netcraft, nginx served or proxied 20.63% busiest sites in March 2024. Here are some of the success stories: Dropbox, Netflix, Wordpress.com, FastMail.FM.

  1. Install nginx
sudo apt install nginx

  1. After installing, create a file for nginx site config here: Link to the nginx config
/etc/nginx/sites-available/<your-web-app-folder>
  1. Create the nginx config setup in the file
sudo vim /etc/nginx/sites-available/<your-web-app-folder>
  1. Add the config contents. You'll want to configure it to how your application is setup.
server {
  listen 80;
  listen [::]:80;
  server_name <yourdomain.com>;
  root /var/www/<your-web-app-folder>/public;
  location /api {
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
  }
}
  1. Then link it to sites-enabled to enable the site:
sudo ln -s /etc/nginx/sites-available/<your-web-app-folder> /etc/nginx/sites-enabled/<your-web-app-folder>
  1. Then the nginx server was restarted to have the latest changes:
sudo systemctl restart nginx
  1. Your site should be showing up after running pm2 start ecosystem.config.js --env production

asdf

Version management for Node.js

  1. Clone the Github branch into .asdf folder
git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.0
  1. Go to ~/.bashrc and add these
. "$HOME/.asdf/asdf.sh"
. "$HOME/.asdf/completions/asdf.bash”

node.js

Node.js® is a free, open-source, cross-platform JavaScript runtime environment that lets developers create servers, web apps, command line tools and scripts.

This is used for our next.js application.

  1. Add the nodejs plugin to asdf
asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git
  1. Install the version
asdf install nodejs 18.17.1
  1. Set a global version
asdf global nodejs 18.17.1

PM2

PM2 is a daemon process manager that will help you manage and keep your application online 24/7

  1. Install PM2
npm install pm2 -g

MySQL

MySQL offers a range of products and cloud services for data management, including MySQL HeatWave for real-time analytics and machine learning. Learn more about MySQL features, customers, events, and webinars.

  1. Install MySQL
sudo apt install mysql-server

Setting up your domain name server to point to your server

  1. Go to your domain management dashboard from your domain hosting provider, or Cloudflare DNS page.
  2. Create mappings to your server IP address for your A records.

Running tests before deployment

Depending on your needs, you may set up a testing workflow for running tests before merging any branch to your deployment branch. Below is an example of a testing workflow

.github/workflows/tests.yml

# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Your App Name CI

on:
  pull_request:
    paths: ['**']

jobs:
  tests:
    name: Tests
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./
    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js 18.17.1
        uses: actions/setup-node@v3
        with:
          node-version: '18.17.1'
          cache: 'npm'
          cache-dependency-path: ./
      - name: Install packages
        run: npm install
      - name: Run tests
        run: npm run test
      - name: Build the application
        run: npm run build
      - name: Start the application
        run: npm run start & npx wait-on http://localhost:3000

Deploying your app

  1. Sign up for an account on PM2 if you don't have one already. Then follow the quick start guide to get started with using it. You can create a Ecosystem file to configure the startup instructions and environment by running pm2 ecosystem to generate the file ecosystem.config.js.

  2. Setup a deployment workflow for deploying and running your app on the server. This should get triggered after you merge a branch into your deployment branch. Below is an example of a deployment workflow. You may customize to how your application and server is set up. There are secrets that are set in Github Secrets such as secrets.REMOTE_PATH. They are stored in the Github Actions page “Secrets” tab. There’s an SSH key stored in the Settings > Deploy keys I used this guide for the deploy key setup.

.github/workflows/deploy.yml

# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Your site name Deploy

on:
  push:
    branches: ['deploy']

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./
    env:
      HOST: ${{ secrets.REMOTE_HOST }}
      USER: ${{ secrets.REMOTE_USER }}
      MYSQL_HOST: ${{ secrets.MYSQL_HOST }}
      MYSQL_DATABASE: ${{ secrets.MYSQL_DATABASE }}
      MYSQL_USER: ${{ secrets.MYSQL_USER }}
      MYSQL_PASSWORD: ${{ secrets.MYSQL_PASSWORD }}
      TARGET: ${{ secrets.REMOTE_PATH }}
      PUBLIC_KEY: ${{ secrets.DEPLOY_USER_PUBLIC_KEY }}
      PRIVATE_KEY: ${{ secrets.DEPLOY_USER_PRIVATE_KEY }}
    services:
      mysql:
        image: mysql:8.0.34
        ports:
          - '3306'
        env:
          MYSQL_DB: ${{ secrets.MYSQL_TEST_DB }}
          MYSQL_USER: ${{ secrets.MYSQL_TEST_USER }}
          MYSQL_PASSWORD: ${{ secrets.MYSQL_TEST_PASSWORD }}
    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js 18.17.1
        uses: actions/setup-node@v3
        with:
          node-version: '18.17.1'
          cache: 'npm'
          cache-dependency-path: ./
      - name: npm install
        run: npm install --ignore-platform
      - name: npm run test
        run: npm run test
      - name: Configure SSH
        run: |
          mkdir -p ~/.ssh/
          echo "$PRIVATE_KEY" > ~/.ssh/staging.key
          chmod 600 ~/.ssh/staging.key
          cat >>~/.ssh/config <<END
          Host staging
            HostName $HOST
            User $USER
            IdentityFile ~/.ssh/staging.key
            StrictHostKeyChecking no
          END
      - name: Check out the source, and setup
        run: ssh staging 'cd $REMOTE_PATH && git fetch && git reset --hard origin/main && npm install && npm run build && pm2 start ecosystem.config.js --env production'
Posted on: March 28, 2024Categories: TutorialsTags: Deployment, Github actions
‹ Back to blog