How to Deploy a Full-stack Web App in 10 Minutes

I’ve deployed webapps quite a few times now, using similar tech stacks, yet every time I find myself searching Google for the same questions and subsequently forgetting the answers. I Google the words “How to allow IP traffic on ports 80 443” or “Nginx config for HTTPS” and those accusing purple links stare back at me. Yesterday, when I deployed a webapp for the n-th time, it still took me about 90 minutes of Googling and ChatGPTing to arrive at my goal - running Nginx and Node on a VPS to serve webpages on my domain with TLS encryption. This post is an attempt to document all the steps in one place; it is more a reference for my own use than it is a tutorial for anyone else.

In this reference, I use the following “stack” of technologies: Google Cloud Platform, Linux, Express, Node, Nginx (the GLENN stack). If you’re using different tools, you may need to adapt this guide to suit your needs. note the lack of frontend tools, this reference is just the steps to deploy a starter app, that only serves a Hello World html page.

Set up a Google Cloud Virtual Machine

We’ll start by setting up a VPS or virtual private server. This is just a virtual machine or computer that we rent from Google where we will host our website. You’ll need to set up a Google Cloud Platform account, project, billing, etc. I’m assuming you’ve done all that, or you can figure it out on your own.

To create a new VM, navigate to the Compute Engine page, and click “Create Instance”

 

Configure the instance (virtual machine) to fit your needs, but make sure to remember to allow HTTP and HTTPS traffic:

This will create firewall rules to allow traffic on the ports 80 and 443 (the standard ports for HTTP and HTTPS)

The VM should take about a minute to create. Then you can click the “SSH” button to use GCP’s browser based SSH to connect to the machine.

This will bring up a new browser window where you can interact with the machine through a terminal.

That’s it for setting up our machine, now we can start installing the software that will run our web-app and serve it to the internet!

Installation

We’ll start by installing Node and npm. There are several ways to install node, but the best way to get the latest version is to download the binaries from the Node download page https://nodejs.org/en/download Copy the link for the Linux Binaries (x64). It should look something like this: https://nodejs.org/dist/v18.16.1/node-v18.16.1-linux-x64.tar.xz . Back in the SSH window we will run commands to install node:

curl -O https://nodejs.org/dist/v18.16.1/node-v18.16.1-linux-x64.tar.xz (replace with the link you copied)

extract the contents to your install directory:

sudo tar -xJvf node-v18.16.1-linux-x64.tar.xz -C /opt  (use the name of the file you downloaded)

then you can remove the tar file:

rm node-v18.16.1-linux-x64.tar.xz

Now you’ll want to add the binaries to your path, so you can access them easily in the terminal. To do this you’ll edit your .profile file:

 run:

sudo vim ~/.profile

and add this line at the end of the file (change the name to match your node version):

Run this command to refresh your terminal:

. ~/.profile

Run these commands to verify that node installation worked:

node -v

npm -v

You’ll also want to create a symbolic link to node and npm in the /usr/bin directory. This allows sudo to find the programs, in case you need to use “sudo node …” or “sudo npm …”

sudo ln -s /opt/node-v18.16.1-linux-x64/bin/node /usr/bin/node

sudo ln -s /opt/node-v18.16.1-linux-x64/bin/npm /usr/bin/npm

sudo ln -s /opt/node-v18.16.1-linux-x64/bin/npx /usr/bin/npx

Running the Template Project

Clone the starter project with git:

git clone https://github.com/thetateman/node-express-app-template

Cd to the project directory:

cd node-express-app-template

Install dependencies with npm

npm install

Now make sure you can run the starter project:

cd server

node server.js

Deploying the Starter App

Now we’ll install and configure PM2. PM2 is a process manager that lets us manage our server process easily and even configure our server to start on startup so our app is resilient in the case that the machine crashes or restarts.

Install PM2 with npm:

sudo npm i -g pm2

Run the following commands to configure the node server to run on startup:

pm2 start server.js

pm2 save

pm2 startup systemd (This will give you another command to run. Copy and run it in the terminal.)

Next we’ll install and configure Nginx. Nginx is the web server program we will be using to handle incoming traffic from the internet and pass it on to our Node process.

Install Nginx with apt:

sudo apt install nginx

Edit the default sites config:

sudo vim /etc/nginx/sites-available/default

Change the location block to proxy traffic to localhost at port 8080:

Configure Nginx by editing the nginx.conf file:

sudo vim /etc/nginx/nginx.conf

At the bottom of the http block, add the following server block:

server {

                server_name <your external ip address> www.<your domain name> <your domain name>;

                location / {

                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

                proxy_set_header Host $host;

                proxy_pass http://localhost:8080;

                proxy_http_version 1.1;

                proxy_set_header Upgrade $http_upgrade;

                proxy_set_header Connection "upgrade";

                }

        }

Not all of these settings are strictly necessary for serving webpages, some are just helpful if you want https, or if you need to access users’ IP addresses in the node server. This is what that section of the file should look like after you edit it:

Now start nginx using:

sudo service nginx start

Let’s do a sanity check to make sure everything is working so far. We should be able to reach our machine from the public internet with its external ip.  And nginx should proxy requests that come in on port 80 to our node server. To test this, in a browser we go to http://<your external ip>

You should see a page like this:

This means our server is running and serving a webpage to the public internet!

Notice how we can only get here using the IP address, we can’t use https, and the browser doesn’t even load any CSS! We will address all of this in the next few steps.

Last Steps

We’ll use Certbot to get a TLS certificate from Let’s Encrypt, so we can use HTTPS. Certbot can also automatically renew our certificate before it expires. Follow all the instructions here: https://certbot.eff.org/instructions?ws=nginx&os=ubuntufocal&tab=standard 

Configure DNS records

One last step to use an actual domain name instead of an IP address! Go to your domain registrar’s site and add DNS records for your domain to point at your IP address. You will just need to add an A record with your IP address like this:

You may need to wait a few hours for the DNS records to propagate. To test if everything is working correctly visit your domain in a browser. You should see the starter webpage:

Congratulations on deploying a full-stack webapp to the internet! We used a lot of tools just to display a static page in the browser, but this tech stack is a powerful foundation upon which you can build basically any web-based application you can imagine.