Continuous deployment without dedicated CI/CD tools
Published: 2023-01-26 16:38:55

This article may be interesting to people looking for a quick, albeit limited version (imitation) of a true CI/CD pipeline. Once I had to figure out a way to deploy an app while I was still working with the infra people on configuring our Gitlab CI/CD and Kubernetes stuff.
When it comes to deploying apps, the most basic way is to move the project build to the deployment server and start a server process to serve the build to clients (user machines).
But what if you don't want to `ssh` directly into a virtual machine server to deploy an app? Or what if there's a security constraint preventing a direct `ssh` connection to a virtual machine server hosting the application? Maybe there's a gateway server in which you have to use CLI  to go to the server of interest (some variety of gatekeeping access I've seen). Instead you'd have to `ssh` into another "proxy" server first. Tedious manual process hard to nearly impossible to automate...

I needed a way to have the app redeploy automatically every time the main/master branches gets updated. [Eventually I did set up Gitlab CI/CD with Docker and Kubernetes]

I found a workaround.

**Create a `deploy.sh` script** in the project folder of the deployment server.

```
#!/bin/sh

# add missing PATH env vars for crontab
PATH=/pathtoyournodedir/bin:/usr/bin

cd /usr/share/nginx/myprojectdir/;

git fetch;

LOCAL=$(git rev-parse HEAD);
REMOTE=$(git rev-parse @{u});

if [ $LOCAL != $REMOTE ]; then
    #pull and merge changes
    git pull origin main;

    yarn build;
   # touch "$(date +"%Y_%m_%d_%I_%M_%p").log"
    pm2 restart nextapp

    #change back to home directory
    cd ~;

fi
```
`#!/bin/sh` line tells it's a bash script to the interpreter.\

Why `PATH=/pathtoyournodedir/bin:/usr/bin` ?

Here's a good explanation why we had to "import" PATH variables for `crontab`: https://askubuntu.com/a/23438

_Note that it became essential to include PATH for all basic ubuntu utilities like `cd` or `touch`. I think we're not just adding PATH variables, but rather replacing crontab's default PATH "import" with _only _what we need. Because once I added PATH to `node`, it started complaining it didn't know what `cd` is :)_

`cd /usr/share/nginx/myprojectdir/;` - go to your project dir - that's where your hidden `.git` folder is.
`git fetch;` will get the updated list with the latest commits without pulling them in just yet.
```
LOCAL=$(git rev-parse HEAD);
REMOTE=$(git rev-parse @{u});
```
gets the latest commit id's to compare to know whether to rebuild.

`git pull origin main;` - `main` or whatever branch you want to deploy.

`yarn build;` - or however you build your application. Note we can't move the build folder to the server for the above reasons. We do it all on the deployment server.

`pm2 restart nextapp` restarts the Node.js process serving the build (if you use `pm2`) or however you restart your process.

`# touch "$(date +"%Y_%m_%d_%I_%M_%p").log"` if you remove `#` to uncomment the line, it will create a dummy file with the date and time that the script ran to debug cases if the build didn't go through when the script ran.

**Make the script executable** (won't run otherwise):

`sudo chmod u+x rebuild.sh`

**Set up a recurring job (script).**
Run `crontab -e` to edit the file with scheduled commands.
Press `i` to enter vim edit mode.
Add a line 
`*/5 * * * * /usr/share/nginx/myprojectdir/rebuild.sh` (will run every 5 min) and press ENTER to go to a new line. (heard a new line was essential)
Press `ALT+h` to exit vim edit mode.
Press `left SHIFT + :` to bring up command prompt in the bottom left corner of vim editor.
Enter `wq` to write and save (or `qa` if you simply ever wanted to exit without making changes)

**Run `crontab -l` to make sure your job has been saved.**

**Wait for 5 min to see if auto deployment happened** (if needed update interval to 2 min for faster results)

If deployment didn't happen successfully, but `git pull` executed, you need to run `git reset --hard HEAD~1` to drop the most recent commit so that the script's `if` condition will allow it to run again.
After this build a build manually with `yarn build` or similar command to restore the original build if needed (if it got corrupted), so you can await next auto deployment attempt.

**What code change to make to your project to reflect successful deployment and be able to observe results easily?** I would recommend a simple UI text change (misspell a word or make some word uppercase)

Hope this helps! Let me know if you have questions!