Blog, Technology and consulting

Deploy a Next.js app with Caddy

Deploy a Next.js app with Caddy

In the modern area of Cloud Computing, self deploying a web application to a personal or small business server is a not as easy or common task. The most common way to deploy a web application is to use a cloud provider like AWS, Google Cloud, or Azure. However, these services can be expensive and complex to set up. If you’re looking for a simpler, more cost-effective way to deploy your web application, this article is for you.

In this article, we will explore how to deploy a Next.js application using Caddy, setting up an automated deployment pipeline without using Github Actions or Gitlab CI/CD.

In this article will cover the following topics:

  • Getting a Next.js application from Gitlab
  • Setting up PM2 for running the Next.js application
  • Setting up a Caddy server
  • Deploying a Next.js application with Caddy
  • Setting up an automated deployment pipeline

Although this article is focused on deploying a Next.js application, the concepts and techniques discussed can be applied to any web application.

What is Caddy?

Caddy is a modern, open-source web server that is designed to be easy to use and configure. It is written in Go and is designed to be fast, secure, and reliable. Caddy is a great choice for deploying web applications because it is easy to set up and configure, and it has built-in support for HTTPS.

The classic choice would be to use Nginx, but Caddy is a great alternative that is easier to set up and configure.

Getting a Next.js application from Gitlab

The first step in deploying a Next.js application with Caddy is to get the application from Gitlab. If you don’t already have a Next.js application, you can create one by following the Next.js documentation. Once you have a Next.js application, you can push it to a Gitlab repository. For the sake of this article, we will assume that you have a Next.js application in a Gitlab repository. If you don’t have a Gitlab repository, you can create one by following the Gitlab documentation.

Generating SSH keys

The next step is to generate SSH keys that will be used to authenticate with the Gitlab repository. To generate SSH keys, run the following command in your terminal:

ssh-keygen -t rsa -b 4096 -C "Deployment server"Language:bash

This command will generate a new SSH key pair. When prompted, press Enter to accept the default file location and name. Do not enter a passphrase. Copy the public key to your clipboard by running the following command:

xclip -sel clip < ~/.ssh/id_ed25519.pubLanguage:bash

Next, go to your Gitlab project and navigate to Settings > Repository > Deploy Keys. Click on Add deploy key and paste the public key into the Key field. Give the key a title and check the Write access box. Click on Add key to save the key.

Pull the application from Gitlab

Go to the directory where you want to deploy the application and clone the Gitlab repository by running the following command:

git clone git@gitlab.yourdomain.com:project-group/project.gitLanguage:bash
Discover Our Company

How We Collaborate with You

icon

Thinking Big

We turn your ambitions into concrete goals, putting innovation at the heart of our collaboration.

icon

Starting Small

We begin with pragmatic solutions and then evolve with agility to maximize your success at each step.

icon

Creating Fast

Our responsive team turns ideas into tangible results, accelerating your growth and impact.

icon

Innovating at Scale

We push boundaries to help you seize large-scale opportunities, ensuring visionary collaboration.

Setting up PM2 for running the Next.js application

PM2 is a process manager for Node.js applications that allows you to keep your application running in the background. To install PM2, run the following command:

npm install pm2 -gLanguage:bash

Next create the custom server file for the Next.js application, create a new file called server.js in the root of the Next.js application with the following content:

const { createServer } = require("http");
const { parse } = require("url");
const next = require("next");

const hostname = "localhost";
const port = 3000;
const app = next({ dev: false, hostname, port });
const handle = app.getRequestHandler();

app.prepare().then(() => {
  createServer(async (req, res) => {
    try {
      // Be sure to pass `true` as the second argument to `url.parse`.
      // This tells it to parse the query portion of the URL.
      const parsedUrl = parse(req.url, true);
      const { pathname, query } = parsedUrl;
      await handle(req, res, parsedUrl);
    } catch (err) {
      console.error("Error occurred handling", req.url, err);
      res.statusCode = 500;
      res.end("internal server error");
    }
  }).listen(port, (err) => {
    if (err) throw err;
    console.log(`> Ready on http://${hostname}:${port}`);
  });
});Language:javascript

More reading on custom server

Next, go the the directory and create a new file called ecosystem.config.js with the following content:

module.exports = {
  apps: [
    {
      name: "my-application",
      script: "./server.js",
      env: {
        NODE_ENV: "production",
      },
    },
  ],
};Language:javascript

Setting up a Caddy server

To set up a Caddy server, you will need to install Caddy on your server. You can download the latest version of Caddy from the official website. Once you have downloaded Caddy, you can install it by running the following command:

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddyLanguage:bash

For the sake of the example, we used Ubuntu server. You can find the installation instructions for other operating systems on the official website.

Next, create a new file called Caddyfile in the root of the Next.js application with the following content:

mydomain.com {
    reverse_proxy 0.0.0.0:3000
    encode gzip
}

http://mydomain.com {
  redir https://{host}{uri}
}Language:text

Replace mydomain.com with your domain name. Start the Caddy server by running the following command:

caddy runLanguage:bash

What is great about Caddy is that it automatically generates SSL certificates for your domain using Let’s Encrypt. This means that your application will be served over HTTPS by default.

Setting up an automated deployment pipeline

To set up an automated deployment pipeline, normally we use you can use a tool like Jenkins, Gitlab CI/CD or Github Actions. However, in this article, we will use a simple bash script to automate the deployment process.

Create a new file called deploy.sh in the root of the Next.js application with the following content:

#!/bin/bash

git pull

BUILD_VERSION=$(git rev-parse HEAD)

echo "$(date --utc +%FT%TZ): Releasing new server version. $BUILD_VERSION"

echo "$(date --utc +%FT%TZ): Running build..."
yarn build
echo "$(date --utc +%FT%TZ): Restarting PM server..."
pm2 restart all

sleep 10

caddy reloadLanguage:bash

This bash script will pull the latest changes from the Gitlab repository, build the Next.js application, restart the PM2 server, and reload the Caddy server.

We’ll be running this script with a cron every minute. But to make sure that the script will not be deploying indefinitely, we will add a scripte to check if a new version is available. Create a new file called deploy-if-changed.sh in the root of the Next.js application with the following content:

#!/bin/bash

echo "$(date --utc +%FT%TZ): Fetching remote repository..."
git fetch

UPSTREAM=${1:-'@{u}'}
LOCAL=$(git rev-parse @)
REMOTE=$(git rev-parse "$UPSTREAM")
BASE=$(git merge-base @ "$UPSTREAM")

if [ $LOCAL = $REMOTE ]; then
  echo "$(date --utc +%FT%TZ): No changes detected in git"
elif [ $LOCAL = $BASE ]; then
  BUILD_VERSION=$(git rev-parse HEAD)
  echo "$(date --utc +%FT%TZ): Changes detected, deploying new version: $BUILD_VERSION"
  ./deploy.sh
elif [ $REMOTE = $BASE ]; then
  echo "$(date --utc +%FT%TZ): Local changes detected, stashing"
  git stash deploy.sh
else
  echo "$(date --utc +%FT%TZ): Git is diverged, this is unexpected."
fiLanguage:bash

Make the script executable by running the following command:

chmod +x deploy.sh
chmod +x deploy-if-changedLanguage:bash

For the cron job, will be creating a new file called deploy-cron.sh in a new folder outside the Next.js application named automation. So the new file will be located at automation/deploy-cron.sh with the following content:

#!/bin/bash

LOCK_FILE="$(pwd)/my-app.lock"
cd /absolute-path-to-your-nextjs-app
flock -n $LOCK_FILE ./deploy-if-changed.sh >> /home/automation/deploy-logs.log 2>&1Language:bash

This script will run the deploy-if-changed.sh script every minute and log the output to a file called deploy-logs.log. We are using flock to prevent the script from running multiple times concurrently.

Make the script executable by running the following command:

chmod +x deploy-cron.shLanguage:bash

To set up the cron job, run the following command:

crontab -eLanguage:bash

Add the following line to the crontab file:

* * * * * /absolute-path-to-your-automation-folder/deploy-cron.shLanguage:bash

Conclusion

In this article, we explored how to deploy a Next.js application using Caddy, setting up an automated deployment pipeline without using Github Actions or Gitlab CI/CD.

We hoped you enjoyed this article and found it useful. If you have any questions or feedback email me at kharrat.m [at] tekru.net.

This article is inspired by the article The cloud is over-engineered and overpriced (no music) by Tom Delalande.

Tags:
    ;