The idea is to create a repository on each server, set them as two new
remotes and then just push to them to deploy, using the
post-receive git hook which is run after the push has terminated.
Update from the future self: this is obviously quite an old setup which I only leave here for the sake of archival purposes.
This is a strategy I have used to deploy an application of mine in the following context:
- There is a staging environment and a production environment
- The environments are set up on two different servers
- The servers reside in a VPC, hence their consoles are only accessible via a VPN
- There is a build step which will be run on the servers before the actual deployment will take place
Setting up users (remote)
For the sake of simplicity, we’ll consider using a
deployer user with sudo powers as the owner of the repository and the deployed directory.
Setting up the repositories (remote)
I decided to put my remote repositories as subdirectories of
$ cd /var/git
$ sudo mkdir MyProject
$ sudo chown deployer: MyProject
$ cd MyProject
$ git init --bare
A “bare” repository is a repository containing only the git metadata and not the actual files. The actual files will be downloaded by the post-receive hook and put in the directory we want to deploy from (or to serve directly from, if you don’t have a build step).
As it is, the repository is already ready to
receive pushes. Now we need to decide what to do after we’ve received data from a push (the
If you enter the
hooks directory in the repository, you’ll see that there’re already some of them present. Not the
post-receive though, which we need to create. Something like this is already a good start:
git --work-tree=/tmp/MyProject --git-dir=/var/git/MyProject checkout -f
Once the repository is refreshed by a push, the script will use git itself to checkout the current branch (probably
master) in /tmp.
Setting up the development environment (local)
It's now time to try sending our project files to the new remote. This is just a matter of entering your local git repository for the project and running something like:
$ cd ~/Workspace/MyProject
$ git remote add staging ssh://deployer@staging-server/var/git/MyProject
$ git remote add production ssh://deployer@production-server/var/git/MyProject
As mentioned before I am behind a VPN, hence I need to use a private IP to access the server (set in /etc/hosts with the name of
staging-server, in the example).
The problem at this point is that I also need to specify a particular ssh private key for each one of the servers, and this is not possible directly from the git command line. There are 2 or 3 workarounds which involve some creative usage of the git environment variables, but I definitely suggest to use the ssh config file itself.
$ cd ~/.ssh
$ vi config
The content of the file can be something like this:
Note the the
host key can be anything you like while the
hostName key must resolve to an IP address.
Now issuing any git command with the ssh:// schema will use the correct key selecting it by the name of the server you want to connect to. Sweet.
You can now try to push to our remote server (not Github!) and see what happens.
$ git push staging master
Take a look into your server now: the directory
/tmp/MyProject should now contain all the files of the repository.
From this point on, what to do with those files is up to you.
This is what I do, for example:
- once the files are in checked out, I run
- once the npm install is finished, I run my tests with
- if all the test pass, I run
- if the build is successful, I will
/distdirectory in the directory which is served by the web server
Bonus (bullet) points
- stdout output from the
post-receivehook is sent back to the console where the “git push” has been started from. This means that you are able to see what’s going on during the deployment (and use
echostatements to make it even more verbose)
- the user with which the
post-receivescript is run is the owner of the script itself. Keep this in mind when you are setting up the directories and which commands to run from the script
- to be able to run the post-receive script even if there is nothing to push, an interesting option is to delete the master branch on the server (from inside the post-receive script itself:
rm -rf /var/git/MyProject/refs/heads/master(it will add more transfer time, of course)
- in this simple setup there isn’t a robust and automated rollback strategy. My suggestion is to always tag your repo before pushing. If you find problems with the current version, “rolling back” to the previous tag would be just a matter of:
$ cd ~/Workspace/MyProject
$ git reset MyPreviousTag --hard
$ git push -f staging master
And then, after the deploy:
$ git pull origin master
Repeat everything for “production” where you read “staging” and you’ll be set.
Written on May 10, 2016 by Claudio Cicali.
Originally published on Medium