Engineering Excellence

Build and Host a Website using Astro, Vultr, NGINX, SSL and a sprinkle of Continuous Delivery

Overview

This article will describe how to build a website using the Astro Framework, NGINX and host it cheaply on a Vultr Virtual Private Server. We’ll also demonstrate how to implement continuous delivery using Github webhooks and a simple CGI script

The intent is to demonstrate the core tenets of Continuous Delivery without obscuring them with the complexity of fully-fledged CI/CD tooling like Github Actions or Jenkins.

This article assumes you are familiar with the following technologies:

  1. Modern Javascript or Typescript Development and NodeJS Tooling
  2. Debian based Linux distributions, command line and package management using apt
  3. How to use SSH to remotely control a system using the command line
  4. Bash Scripting, a basic understanding
  5. Git version control and how to commit and push changes to a GitHub repository

Create your Astro Project

Create your Astro site using the command below

npx create astro@latest

Upload code to GitHub

  1. Login to GitHub
  2. Create a new Repository
  3. Astro automatically initializes a local Git repository, all you need to do is connect it your GitHub repository using a remote
git remote add origin \<url of your GitHub repo\>
  1. Commit all your changes
git add --all
git commit -m "Initial Commit"

Don’t push your code just yet, we’re going to set up our Virtual Private Server (VPS) first and configure it to run a build of our Astro site on every push

Generate an SSH Key & Add it to your Vultr Account

We will be using SSH to access our VPS, to do that we need to create an SSH key Vultr will install on the server, so we can connect to it remotely.

  1. Generate an SSH Key
ssh-keygen
  1. Enter a strong passphrase
  2. Take note of where the keys are written usually go into your $HOME directory. On Windows this is typically C:\\Users\\\<username\>\.ssh on Unix it would be ~/.ssh
  3. Open or print the contents of id_rsa.pub file
  4. Login to your Vultr Account
  5. Open the SSH Key Management Tool by clicking on your name in the top right corner (next to the bell shaped Alert Icon)
  6. Click the + Button and choose Add SSH Keys
  7. Enter a descriptive name
  8. Past the contents of your id_rsa.pub file (make sure you don’t accidentally copy your private-key)
  9. Press the Add SSH Key button

Provision your Vultr Virtual Private Server

We’re now ready to create our VPS, use the details below to configure a Cloud server to host our new Astro website.

  1. Login to your Vultr Account
  2. Open your Products View
  3. Click the + Button in the top right corner and choose Deploy New Server
  4. Configure your server using the following options:
CategoryOption
Server TypeCloud Compute
CPU & Storage Technologyintel - Regular Performance
Server LocationChoose somewhere cheap
Server ImageUbuntu 22.04 LTS
Server Size10GB SSD 1 vCPU 0.5GB Memory 0.5TB Bandwidth
Automatic BackupsOff
SSH KeyAdd your Public SSH Key

Install Required Software

  1. Get the IP Address of your newly minted Ubuntu Server from the Product section of your Vultr account
  2. Login to the server, but be careful since you’ve got root access shell ssh root@\<VPS IP Address\>
  3. Update your software package index using the command
apt update
  1. The system will automatically launch and unattended upgrade, you’ll need to wait for this to complete and reboot VPS before continuing
  2. Monitor the upgrade using the command
tail -f /var/log/unattended-upgrade/unattended-upgrades.log

When the log file indicates the upgrade is complete you can move on to the next step 6. Install the required software using the following command

apt install nginx python3-certbot fcgiwrapper

Configure our NGINX Virtual Host

If you have your own domain

Update your DNS entries to point to the IP of the new VPS server

The service you bought your domain through usually provides a control panel to do this, a quick google of <domain provider> DNS control panel will usually turn up the right documentation

If you want to use the Vultr Host Name

Take note of the fully qualified host name of the VPS in the Product Dashboard

Setup your website on your VPS

  1. Create directory to host your site content
mkdir /var/www/\<domain name\>
echo hello > /var/www/\<domain name\>/index.html # Create a simple test file to serve that will allow us to verify our configuration
chown www-data:www-data /var/www\<domain name\>
  1. Create a new virtual host file named the same as your domain in /etc/nginx/sites-available
# /etc/nginx/sites-available/\<domain name\>.conf
server {
    listen 80; # Listen on port 80 on your IPv4 Address
    list [::]:80; # Listen on port 80 on your IPv6 Address
    server_name \<domain name\> www.<\domain name\>;
    index index.html;
    root /var/www/\<domain name\>; # Make sure it matches the directory you created above
    location / {
        try_files $uri $uri/ =404; # Make the server respond with 404 - Not found if requested file isn't present
    }
}
  1. Test your NGINX Configuration
nginx -t
  1. Enable the virtual host by creating a symlink in the /etc/nginx/sites-enabled folder
ln -s /etc/nginx/sites-available/\<domain name\>.conf /etc/nginx/sites-enabled
  1. Restart NGINX
systemctl restart nginx
  1. Test
curl -H "Host: \<domain name\>" http://localhost/index.html

It should respond with hello

Enable SSL using Certbot

Hat’s off to Let’s Encrypt, they could not have made securing a website any easier. To enable SSL for your new virtual host simply run:

certbot --nginx

The above command will prompt you to select the domain names you want to request certificates for and then update your NGINX configuration to enable SSL and restart the NGINX service using systemctl.

If you want to review the changes open your NGINX configuration file in /etc/nginx/sites-available

Configure Fast CGI Wrapper to Execute the Build Script

We will use FastCGI to serve the webhook that is going to be the foundation of our Continuous Delivery process.

  1. Open your domains NGINX configuration file in /etc/nginx/sites-available
  2. Create a new directory /var/www/webhooks
  3. Add a new location block as follows, you’ll notice we don’t include the full path to the directory in the root or SCRIPT_FILENAME lines, since the $fastcgi_script_name already includes the webhooks path fragment
location /webhooks/ {
    gzip off;
    root /var/www;
    fastcgi_pass unix:/var/run/fcgiwrap.socket;
    include /etc/nginx/fastcgi_params;
    fastcgi_param SCRIPT_FILENAME /var/www$fastcgi_script_name;
}

Setup our CI/CD CGI Script

We’re almost there! We’re going to use 2 scripts to handle building our website and connect them to a GitHub webhook. The first script is responsible for ensuring there is only one build in progress at any point in time. The second script contains our build logic.

Create a Build Directory

We need some scratch space to perform our builds, create a new directory and change the ownership so the NGINX user has read-write access to it

mkdir /var/build
chown www-data:www-data /var/build

Generate a new SSH Key and add it to GitHub

Our build script needs to be able to clone our website’s code. For security, we will create a dedicated SSH key.

⚠️ Do not use the same key you created to access your VPS server

  1. Create a new keypair and save it to the build directory that you created
ssh-keygen
  1. Make the NGINX user the owner of the file
chown www-data:www-data /var/build/ida_rsa*
  1. Print the contents of the public key and copy it to the clipboard
cat /var/build/ida_rsa.pub
  1. Add the public key to your GitHub account
    1. Login to GitHub
    2. Click on your Avatar icon in the top right corner of the screen
    3. Click on Settings
    4. Click on SSH & GPG Keys
    5. Click the New SSH Key Button
    6. Enter a descriptive name
    7. Paste the contents of the /var/build/ida_rsa.pub key into ‘Key’ text box
    8. Press the Add SSH Key Button

Create the Launch Script

The launch script ensures that the build process completes regardless of whether the caller of the webhook (GitHUb) hangs up or not. It also ensures that only 1 build at a time is processed.

Create a new file called: /var/www/webhooks/build.cgi and copy the following contents into it

#!/bin/bash
pushd /var/www/webhooks
if [ ! -z $(pgrep -F /tmp/build.pid) ]
then
  echo "Status: 409"
  echo ""
  echo "<html><head><title>Build in Progress</title></head><body><h1>Build Already in Progress</body></html>"
  echo ""
  echo ""
  exit 0
else
  nohup /var/www/webhooks/doBuild.sh 2>&1 > /dev/null &!
  echo $! > /tmp/build.pid
  echo "Status: 202"
  echo "\r\n"
  echo "Build Started"
  echo ""
  echo ""
fi
exit 0

Create the Build Script

The build script uses NVM (Node Version Manager) to install NodeJS, Git to clone the repository and NPM to install the required libraries and frameworks and build the site

Create a new file called: /var/www/webhooks/doBuild.sh and copy the following contents into it

export HOME=/var/build
if [ ! -d /var/build/nvm ]; then
mkdir /var/build/nvm
fi
export  NVM_DIR=/var/build/nvm && wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.4/install.sh | bash 2>&1 | tee /var/build/build.log
export PATH=/var/build/nvm/bin:$PATH
source $NVM_DIR/nvm.sh
nvm install 18 2>&1 | tee --append /var/build/build.log
nvm use 18 2>&1 | tee --append /var/build/build.log
pushd /var/build
if [ -d /var/build/ee ]; then
rm -rf ee
fi
# Make sure you correctly refer to your new SSH Key in the next command, we disable strict host key checking since our NGINX user doesn't have a HOME directory
git -c "core.sshCommand=ssh -i **/var/build/id_rsa** -F /dev/null -o StrictHostKeyChecking=no" clone git@github.com:\<github user\>/\<github repository\>.git 2>&1 | tee --append /var/build/build.log
pushd ee
npm ci 2>&1 | tee --append /var/build/build.log
npm run build 2>&1 2>&1 | tee --append /var/build/build.log
rm -rf /var/www/\<domain name\>/*
cp -rf dist/* /var/www/\<domain name\> 2>&1 | tee --append /var/build/build.log

Please make sure you replace all instances of \<domain name\> with the correct directory

Make sure the scripts are owned and executable by the NGINX User

  1. Change ownership of the file
chown www-data:www-data /var/webhooks/*
  1. Make them executable
chmod +x /var/webhooks/*

Test

You’ve already checked in your code to GitHub, so we can verify everything is working by calling the /var/www/webhooks/build.cgi script. To ensure there are no permission issues when this is running normally we must run it as the www-data user via the su command

su www-data -c "/var/www/webhooks/build.cgi"

If everything goes to plan, the code should be checked out to the /var/build folder, NodeJS will be installed and your site will be built and copied to the root folder served by NGINX for that domain. You can look at the build logs using the following command:

less /var/build/build.log

This will highlight any errors that may have occurred whilst building the code. If the build was successful you should be able to access your new site at your domain name: https://<domain name>

Connect GitHub to your Build Script

All that is left to do now is connect GitHub to our build script.

  1. Navigate to your Repository in GitHub
  2. Open the Settings page of the Repository
  3. Click on the Webhooks link in the menu on the left hand side
  4. Click the add Webhook button
  5. Enter the URL of your domain name and path to the build.cgi webhook https://\<domain name\>/webhooks/build.cgi
  6. Leave SSL verification enabled since we have used LetsEncrypt & Certbot to secure our site
  7. Select ‘Just the push event’ when answering ‘Which events would you like to trigger this webhook’
  8. Press the ‘Add webhook` button to create the webhook

Test the Webhook

It’s time to verify everything is working together to continuously deliver your site to your new VPS. On your local machine make a minor but noticeable change to the App.tsx file and commit it. Don’t push it to your GitHub repo just yet.

To verify the webhook is being called we’re going to tail the logs on the server to observe the webhook invocation and any errors.

tail -f /var/log/nginx/\<domain name\>.*.log /var/build/build.log # monitor the log files and print any new content to the console.

You can now push your changes to the repository, a couple seconds later you should see the output in the log files update as GitHub invokes your build script. The build.log file will update with the output from the build process and should terminate with a success message.

Visit your domain in a new browser window and you will see the change you made.

Congratulations

You have just created a continuous delivery pipeline using a cheap and reliable VPS

content