Back in the olden days I used to "deploy" applications by using FTP to transfer PHP code files on the server. Now, I love being able to push code to my Git repo and then see the app get deployed automatically. What I still miss though is how fast old school FTP deployments were. These days there is usually a container build step involved with deployments. Then there is the time it takes to transfer the containers, not to mention the extra costs involved with hosting a container repo, managing a cluster, running a load balancer, and so on.
Since containers solve problems that very few of my projects have, I wanted to see if I could cook up a relatively simple and low cost application hosting and deployment rig.
Ingredients:
- A Node.js App
- Caddy
- PM2
- GitHub Actions
- A low cost VM on vultr
Instructions:
If you don't have one already, create an SSH key on your local machine that you can use to connect to your VM
Create a Debian 11 VM on Vultr. I setup mine for only $3.50 per month. Add your SSH key during the config process.
Once the VM is ready, install Caddy : https://caddyserver.com/docs/install
-
Open the firewall to accept connections on ports 80, and 443
sudo ufw allow 80 sudo ufw allow 443
-
Install node.js
sudo apt update curl -sL https://deb.nodesource.com/setup_16.x | sudo bash - sudo apt -y install nodejs
-
Install PM2 and Yarn
npm install -g yarn pm2
-
Configure PM2 to run on startup
https://pm2.keymetrics.io/docs/usage/startup/
pm2 startup
-
Install Git and setup SSH Key
Since PM2 deploys will be using git to pull new code to the server, you'll need to make it easy for the server to access your repository. If the repo is public then you can skip this step.
Begin by creating a new ssh key on the server
ssh-keygen -t ed25519 -C "pm2 on server"
Then copy contents of ~/.ssh/id_ed25519.pub into a new SSH key in your GitHub user account settings.
You'll also need to make sure that GitHub is added to your list of known hosts on the server. The quickest way to do this is to do a quick checkout:
cd git clone git@github.com:aaronblondeau/mpg2co2.git rm -Rf mpg2co2
On this first checkout on the server you should be prompted to add github to the list of known hosts.
-
Configure environment variables.
I chose to simply add app environment variables to the .bashrc file on the server:
Add to .bashrc
export PGHOST=db.bit.io export PGUSER=HIDDEN export PGPASSWORD=HIDDEN export PGPORT=5432 export PGDATABASE=HIDDEN export PGSSL=yes
-
Create a PM2 config file
I created two separate config files - one for staging and one for production. One file with both configs will work too:
- https://github.com/aaronblondeau/mpg2co2/blob/main/ecosystem.staging.config.js
- https://github.com/aaronblondeau/mpg2co2/blob/main/ecosystem.production.config.js
Info on PM2 configs is here : https://pm2.keymetrics.io/docs/usage/application-declaration/
-
Create a directory for the app.
This directory needs to match where you told PM2 to operate from when setting up the config file above : https://github.com/aaronblondeau/mpg2co2/blob/main/ecosystem.staging.config.js#L18
mkdir /var/www
-
Run PM2 setup and deploy
These commands are run from your local development environment in order to setup the application and do an initial deploy.
Note, if on Windows, you may get a "spawn sh ENOENT" error. You can resolve this by making sure "sh" is in your path : https://github.com/Unitech/pm2/issues/3839#issuecomment-484347776
pm2 deploy ecosystem.production.config.js production setup pm2 deploy ecosystem.production.config.js production
Your application should be running on the server at this point.
-
Setup DNS records
Make sure you've setup a DNS record that points your domain at the server. Note that Caddy is going to provide completely automatic SSL certificates!
-
Configure Caddyfile reverse proxy
Back on the server, add your domain to /etc/caddy/Caddyfile
mpg2co2.com { reverse_proxy localhost:3001 } www.mpg2co2.com { redir https://mpg2co2.com{uri} }
Then tell caddy to reload the config (run this in /etc/caddy)
sudo caddy reload
At this point your application should be up and running on the server with its own domain name. You can also manually deploy updates from your development machine by running
pm2 deploy ecosystem.production.config.js production
-
Setup GitHub deploy action
Like the PM2 files I setup a separate GitHub action file for each environment:
- https://github.com/aaronblondeau/mpg2co2/blob/main/.github/workflows/deploy-production.yml
- https://github.com/aaronblondeau/mpg2co2/blob/main/.github/workflows/deploy-staging.yml
I based these actions off this article : https://dev.to/goodidea/setting-up-pm2-ci-deployments-with-github-actions-1494
The action:
- Checks out the code
- Configures ssh so the server can be accessed
- Runs PM2 deploy
-
Setup SSH config for GitHub actions
Before you can use the GitHub actions you have to provide a way for GitHub to access your server.
Run this command on your local dev box to create a new ssh key (choose to put the file in ./github_action_key and don't provide a passphrase):
ssh-keygen -t ed25519 -C "github_deploy_action"
Then, ssh into your server and add the contents of github_action_key.pub as new line in ~/.ssh/authorized_keys.
Then, in GitHub, go to Repo Settings > Secrets > Actions > New Repository Secret, put github_action_key file's contents into a repository secret named SSH_PRIVATE_KEY
Next, from your local dev box, connect to server with new key (ssh -i .\github_action_key root@104.238.135.191). You should be prompted to add an entry to your known_hosts file when you do this. If you don't you'll need to remove all existing entries for the address and try again. After you've been prompted to add the connection to known_hosts, open the file (~/.ssh/known_hosts) grab the entry and add it to a new repository secret in GitHub called SSH_KNOWN_HOSTS.
-
Enjoy
Once everything is setup you'll be able to push your code to staging/main branches in git and your code will automatically get deployed!