How to deploy a Node.js app with HTTPS and a custom domain

Today's web development landscape is continuously evolving, and deploying projects directly from your Git repository to a virtual private server (VPS) or cloud server can be a significant time-saver. This guide is especially useful if you're developing a Node.js application, allowing you to concentrate on coding your app instead of dealing with deployment logistics.  

You don't need to waste your time studying documentation to deploy Node.js apps on a VPS, as with Serverless, AWS, Google Cloud, or Azure. On the other hand, all your cloud costs are fully predictable because you pay per server and real computing resources, not for requests and traffic. In this post, I will present two options for deploying Node.js applications with HTTPS and custom domains on virtually any cloud server. The first option will use CaddyServer (a proxy server) and PM2 (Node.js app management service), while the second option employs LocalCloud - a cloud-agnostic platform with available source code (in most cases, it's open-source) that simplifies deployments of web projects. Both options have zero vendor lock-in and require a small number of resources on your server, but LocalCloud provides additional features like auto-deployments, load balancing, localhost, and much more.

Here's a simplified breakdown on how you can get your app up and running a Node.js app or even multiple apps on a single server with automatic TLS certificates (https://) and custom domains in just a few steps:

  1. Choose Your Server and Buy Domain: Start by picking a cloud server. For instance, Scaleway offers servers suitable for small to medium-sized projects starting at just 10 EUR/month. These servers typically come with 1 VCPU and 2 GB of memory, which should suffice for hosting 1-5 side projects or one project with around 100-1000 active monthly users. The domain can be ordered at https://www.ovhcloud.com/en-ie/domains/, OVH provides free DNS API to manage DNS records over API.
  2. Set Up Your Server: Once you've selected your server and got a domain name, the next step is to SSH or log in with a root password into it. Then, depending on option 1 or 2 you should run around 10 commands (option 1) or install LocalCloud with a single command on your server (option 2).
  3. Deploy Your App: Clone the code and start your web app. CaddyServer + PM2 or LocalCloud will manage https and restart your app in case if the app is terminated or after the server is rebooted.

Need a more detailed breakdown? Don't worry, we have you covered! Follow along our step by step guide below for both options, and you'll be surprised how quickly you'll be up and running.

What we’ll cover:

Let's dive in!

1. Tools and Basic Knowledge You Need

To successfully deploy your Node.js app, you'll need the following:

  • Basic Node.js knowledge
  • Basic Git knowledge (specifically, the git commit and git push commands)
  • An account on Bitbucket or GitHub (if you don't have one, don't worry—it only takes a few minutes)
  • A VPS/Cloud server with SSH access, a public IP, and Ubuntu 22.04. You can opt for the cheapest instance (from 10 EUR / 11 USD / month) from providers like Scaleway, DigitalOcean, OVH, etc.
  • Domain name
  • Just 5-10 minutes of your time

2. (Optional) Creating a Project on Your Local Machine

If you already have a Node.js project, feel free to skip this step and proceed to "Connecting a VPS/Cloud Server." Otherwise, follow these steps to create a simple web server using Polka (https://github.com/lukeed/polka):

  • Install Node.js and NPM on your local machine if you haven't done so already.
  • Install Git on your local machine if it's not already installed.
  • Create a new folder, for example, in your home directory:
  • Add Polka to your project
  • Edit your main JavaScript file (the one specified during npm init). In our case, it's index.js. Here's an example code snippet:
    mkdir starter
    cd started
    npm init
    npm install polka --save
    const polka = require('polka');
    
    function one(req, res, next) {
      req.hello = 'world';
      next();
    }
    
    function two(req, res, next) {
      req.foo = '...needs better demo 😔';
      next();
    }
    
    polka()
      .use(one, two)
      .get('/users/:id', (req, res) => {
        console.log(`~> Hello, ${req.hello}`);
        res.end(`User: ${req.params.id}`);
      })
      .listen(3000, () => {
        console.log(`> Running on localhost:3000`);
      });

In this example, we’ve created a Polka webserver and run it on Port 3000.

  • Run your app:
  • Check that the app is running without any issues. In the terminal, you should see something like:
  • Push this project to Bitbucket or GitHub.
    node index.js
    > Running on localhost:3000
  • Order a cloud server with SSH access, a public IP, and Ubuntu 22.04. You can opt for the cheapest instance (from 10 EUR / 11 USD / month) from providers like Scaleway, DigitalOcean, OVH, etc.
  • Add an A record to DNS with the IP address of your new server. For instance, if the public IP is 153.111.51.139 and your custom domain is project.com, you can add a wildcard A record: *.test.project.com -> 153.111.51.139. Be sure to update the IP address and domain name accordingly. We recommend using a wildcard A record to avoid adding a new one for each service deployment.

4. Option 1 - Deploy with CaddyServer and PM2

Almost any service that simplifies deployments and cloud management claims that "the cloud is hard," but that's not entirely true, at least until you need to deploy something very unusual. For the simplest deployment, you need only two things - a service that handles HTTPS and forwards requests received by a server to your web app, and a service to restart the web app in case it crashes or the server is rebooted. It would be great to have auto-deployments, a load balancer, and monitoring (I'll show how to get them in option 2 below), but for the simplest deployment, a proxy server and app management service are enough.

  • SSH/login to your server with fresh Ubuntu 22.04 (don't forget to replace 153.111.51.139 with your IP)
ssh root@153.111.51.139
sudo ufw allow 80
sudo ufw allow 443
sudo ufw allow 22
sudo ufw --force enable

sudo mkdir -p /etc/apt/keyrings

curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg

echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list

DEBIAN_FRONTEND=noninteractive sudo apt-get update
DEBIAN_FRONTEND=noninteractive sudo apt-get install nodejs -y

sudo npm install -g npm

DEBIAN_FRONTEND=noninteractive sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https

curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --batch --yes --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 DEBIAN_FRONTEND=noninteractive sudo apt -y install caddy

sudo npm install pm2@latest -g
  • Clone a git repository with your Node.js app where you want on a server
cd ~
git clone git_url
  • Install dependencies and run the app with PM2 (replace index.js with your start file)
cd folder_with_repo
npm install
pm2 start index.js --name my-app
  • Create a file and name it "Caddyfile" on your local machine, and add to that file the text below (don't forget to replace myproject.com and 5005 with your domain and port)
myproject.com {
    reverse_proxy * localhost:5005
}
  • Open a new Terminal window and upload Caddyfile from your local machine to a server with scp
scp path_to_caddy_file root@153.111.51.139:/root/Caddyfile
  • Return to the Terminal window #1 and reload Caddyfile that you just uploaded
cd ~
caddy reload
  • Wait a few minutes and try to open the URL with the app in the browser

5. Option 2 - Deploy with LocalCloud

LocalCloud is a secure and developer-friendly platform-as-a-service that allows you to deploy web apps directly from Git repositories. It's source-available and multi-cloud, ensuring the longevity of your cloud infrastructure. The best part? You don't even need to register a new account.

LocalCloud uses containers to deploy applications. Containers are the most secure and reliable option for web project deployments because each container is fully isolated from others.

  • To deploy our Node.js app you should create a special file Dockerfile in the project's root folder and add this content:
  • Push changes (you added Dockerfile in the previous step) to the remote Git repository
  • SSH into the server (don't forget to use IP of your server)
  • Install the LocalCloud agent. Replace "service_node_domain" in the command below with your domain (e.g., agent.test.project.com):
FROM node:16

# Create app directory
WORKDIR /usr/src/app

# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./

RUN npm install
# If you are building your code for production
# RUN npm ci --only=production

# Bundle app source
COPY . .

EXPOSE 3000
CMD [ "node", "index.js" ]
ssh root@153.111.51.139
curl https://localcloud.dev/install | sh -s your_domain

Note: The service_node_domain should exclude "http://" or "https://" for Bitbucket, GitHub, and other webhooks. Examples: agent.test.project.com or deploy.domain.com.

  • Wait for the LocalCloud agent to finish server provisioning, and run
  • In the terminal, select "Add service" from the main menu.
  • Type in the URL of your GitHub/Bitbucket repository, add the public SSH key and webhook URL shown in the terminal to your Bitbucket or GitHub repository, and confirm the addition.
  • Wait for a brief moment (usually around up to a minute, depending on project size and complexity), and check if the service/app is accessible at the URL you specified in the previous step.
localcloud

That's it! Your project will be deployed shortly. With LocalCloud, you can monitor resource usage, scale your project, and add unlimited environments and custom domains.

Before putting your project into production, we recommend creating at least two environments: one for production and another for development. This approach provides better control, even for small teams or solo developers. The great news is that LocalCloud allows the creation of an unlimited number of environments. For example, you have 2 branches - master (or main if you use GitHub) and dev. You can create environments for these 2 branches and have 2 instances of your project which will be available on different URLs.

Here's a simple example of how you can create multiple environments for different branches.

On your local machine:

Create a new dev branch on your local machine (where you develop)

  • Create a new branch in your Git repository and name it "dev." (of course, you can use another name)
  • Update the text in index.js from "...it's a master branch" to "...it's a dev branch."
  • Push the changes to the remote Git repository.
  • Check if the URL with the development environment displays "...it's a dev branch."
  • Go to the root of your project.
  • Change the text in index.js from "...needs better demo 😔" to "...it's a master branch."
  • Push the changes to the remote Git repository.

On a server with LocalCloud:

  • SSH into a server and run the "localcloud" CLI in the terminal:
  • Select "Services" from the main menu.
  • Choose a service from the list.
  • Select "Add environment."
  • Specify a branch name and a custom domain (don't forget to add an A record for this domain with the public IP of your server).
  • Wait for approximately 30 seconds, and check if the dev branch of your project is accessible via the custom domain you entered.
localcloud

5. What's Next?

That's it! You have now learned the basics of deploying Node.js apps with CaddyServer and PM2 or LocalCloud and how it's really simple. Our primary aim is to make DevOps accessible even to small teams and solo developers while reducing cloud costs and time to market.

Stay updated on new guides by following us on Twitter.

If you have any feedback, questions or feature requests, feel free to submit them to hey@localcloud.dev.