Using Git to deploy Node.js sites to Ubuntu

I was introduced to version control on my first corporate assignment. It was a Microsoft shop, and the company's e-commerce codebase was managed more like a software product than a living web site. Enhancements and fixes were developed and deployed to an internal staging environment for full regression testing before being certified for deployment into production. I remember waiting a week for a typo fix to roll live.

We used Microsoft's Visual Source Safe, and I quickly found it to be very powerful and deceptively simple to use. (I have spent far too many hours in my career trying to explain how VSS works to developers new to it.)

For personal projects and small teams I continued to use VSS for version control, but more importantly, as a project organization and production deployment tool. Even in situations that required less formal testing, versions were carefully "labeled" and deployed to production. This helped the team share development code, kept to source code reliable, and most importantly, helped me keep my sanity ;)

Funny how times change, but many of the same magic steps seem to persist, only with different approaches and lingo. Where we had the ceremonious "pulling of the label" to production with VSS, today I "push the master branch" to production using Git.

Using Git for deployment

The article explains one way to use Git to deploy Node.js sites to an Ubuntu Linux server. I do not get into the version control and development management aspects of Git. I also assume you are developing on a Mac, but most of the development side should work with Git on Windows as well.

This approach keeps a remote Git repository on the production server that you will push your updates to. A Git "hook" on the server will automatically execute whenever you push a code update to the server. The hook will stop the Node app service, deploy the updated files from the Git repository to the the actual Node app directory, and then restart the Node app again.

Prerequisites:

  • You need to have Git installed on your development machine: git-scm.com or Homebrew, etc.
  • Configure your Ubuntu server as I describe in my Setting Up a Node.js Website post. This will:
    • Create a git user on the server who owns the app files
    • Create an Upstart service for your Node app

Setup Git for development

Go to the directory of your project on your development machine cd ~/mysite and run git init to create a local Git repository for the site.

You may have some files in your project that you do not want to track with Git or deploy to production. To have Git ignore these files, use a .gitignore file to filter out all files except those you want to manage and deploy to production. (Such files might include log files, environment settings files, passwords, keys, etc.)

Note: Git only tracks files, not directories. So if you want to include an empty directory as part of your project deployment, be sure to create a readme.md file or something so the directory has at least one file. (For example, I have a /log directory that needs to be created as part of the app.)

To create a .gitignore file for your project, go to your ~/mysite project root directory on your development machine, and create a new file called .gitignore and use something like this:

env.json
*.log
public/images/uploads/*.*
!public/images/uploads/readme.md
ssl/
.DS_Store

This will instruct Git to:

  • Ignore any env.json files
  • Ignore any files with a .log extension
  • Ignore all files in public/images/uploads/
  • But DO include public/images/uploads/readme.md
    • This will pull in the uploads directory
  • Ignore any files in ssl/ (where I keep my keys)
  • Ignore those pesky Mac .DS_Store files

You can test your .gitignore file by running this in your ~/mysite directory:

git add .
git commit -m "init commit"
git ls-tree --full-tree -r HEAD

This will add all the trackable files and the last command show you the files that were added and are now tracked by Git. You should only see the files you expect. If not, edit your .gitignore file until it works as required for your needs. To "un-track" a file in Git that was added by accident, use git rm --cached <file> for each you need to remove.

Install Git on your server

ssh into your VM, then install Git:

apt-get update
apt-get install git

Setup your Node site for Git

I assume your Node site is set up as I explain here. The git user owns to app files, which are located at /home/git/myite.

Log in to your server as the git user so we create the git repository with permissions that the git user will be able to use.

su git

First, add a bare git repository on the server for your site:

cd /home/git
mkdir mysite.git
cd mysite.git
git --bare init

Next, create the post-receive Git hook file that will deploy updates to the /home/git/mysite directory:

cd /home/git/mysite.git/hooks
cat > post-receive

Then paste in this file content modified for your node app path if needed:

#!/bin/sh
GIT_WORK_TREE=/home/git/mysite git checkout -f

(ctrl-d to exit the cat process)

Change permissions on the file to allow it to be executed:

chmod +x post-receive

Now go back to your local development mac and add the remote git repository you just created:

cd ~/mysite
git remote add mysite_label git@yourproductionserver.com:mysite.git

Git is going to use mysite_label as a name for the repository. You will be typing mysite_label a lot, so use something short that still describes your site ;)

Test the deployment process on your mac. (Add your site files to Git and make an initial commit if you did not do so when you first set up the local git repo for this project: git add . and git commit -m "init commit").

cd ~/mysite
git push mysite_label master

The git push command will prompt you for the password for the git user you created on the production server (unless you're using an ssh key to connect.)

This should push your committed code changes to the server, and the git hook on the server should automatically deploy the updated files to the node app directory. ssh to the production server and verify that your code updates have now indeed been deployed to /home/git/mysite.

Troubleshooting: Be sure you can ssh to your server as git: ssh git@yourproductionserver.com. Once you log in as git user, you should be in the /home/git directory and your mysite.git repository should be right in that directory.

There is a limitation to our deployment process at this point. If we make updates to the node app or its code dependancies, the node app needs to be restarted in order for the updates to take effect. We will address this next when we enable git user to start and stop the node service, and update the Git post-receive hook to start and stop the service.

Enable Node app restart on deployment

For this step, you need to have your node app set up as an Upstart service as explained here.

We can leverage the node service commands to enhance our Git deployment process to restart the Node app.

First we need to change the sudoers file to allow the git user to sudo as root to start and stop your blog service. (Only root-access users can stop and start Upstart services.) We also need to prevent the sudo process from prompting for a password when using sudo for this task, otherwise the automated git hook script will fail due to the prompting.

Add one line to the end of the sudoers file. I like to use vi, but you can use the editor of your choice. To use vi: export EDITOR="vi".

Here is an overview and command reference for using vi to edit files: Basic vi Commands.

Now to edit the sudoers file:

visudo

Visudo is a special vi editor that parses the file before allowing you to save changes, since mistakes in the sudoers file can make your server impossible to access.

It is important to add the following line to the end of the file:

git ALL = (root) NOPASSWD: /sbin/stop mysite, /sbin/start mysite

This added line will allow git user to sudo as root with no password prompt, but only when running the start and stop commands for your service. Note: mysite refers the service name you created for your Node app- the same name of your Upstart .conf file. Also note that you need to use the full path for the start/stop commands. To double check your path, use which start to see the full path.

Note: You need to logout and log into the server again for the sudoers file change to take effect in your ssh session.

To test, ssh as git@yourserver.com.

You should be able to stop/start the service as git user as follows:

sudo /sbin/stop mysite
sudo /sbin/start mysite

Now update the git post-receive hook to stop and start the service:

cd /home/git/mysite.git/hooks
cat > post-receive

Then paste this file contents in, modified for your site if need be:

#!/bin/sh
sudo /sbin/stop mysite
GIT_WORK_TREE=/home/git/mysite git checkout -f
sudo /sbin/start mysite

(ctrl-d to finish)

Now when you deploy updates via Git, your mysite Node service will restart as well. Make a small change to a file in your Node site on your Mac, commit the change, and deploy:

cd ~/mysite
git add .
git commit -m "test deployment process"
git push mysite_label master

You should see the new process id # returned to confirm the restart of the service - something like this:

git push mysite_label master

git@yoursite.com password: xxxxx

Counting objects: 11, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 499 bytes | 0 bytes/s, done.
Total 6 (delta 2), reused 0 (delta 0)
remote: mysite_label stop/waiting
remote: mysite_label start/running, process 3469
To git@yoursite.com:mysite.git
   60f6037..ece0ca8  master -> master

Seeing the process # gives us nice comfort that the service has indeed been restarted.

Conclusion

Now you can develop your Node.js app, stage and commit the changes via Git, and then deploy the updates to production with just one command :)

For a full introduction and index to this blog: Node.js: One New Approach

Next post I talk about Understanding Node.js.

Cheers!

comments powered by Disqus