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.pub
Language: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.git
Language:bash
How We Collaborate with You
We turn your ambitions into concrete goals, putting innovation at the heart of our collaboration.
We begin with pragmatic solutions and then evolve with agility to maximize your success at each step.
Our responsive team turns ideas into tangible results, accelerating your growth and impact.
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 -g
Language: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 caddy
Language: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 run
Language: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 reload
Language: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."
fi
Language:bash
Make the script executable by running the following command:
chmod +x deploy.sh
chmod +x deploy-if-changed
Language: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>&1
Language: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.sh
Language:bash
To set up the cron job, run the following command:
crontab -e
Language:bash
Add the following line to the crontab file:
* * * * * /absolute-path-to-your-automation-folder/deploy-cron.sh
Language: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.