Simple deployment strategy using only git + ssh + vpn
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 /var/git
.
$ 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 post-receive
hook).
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:
#!/usr/bin/env bash
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:
Host staging-server
HostName staging-server
Identityfile /home/claudioc/.ssh/staging.pem
Host production-server
HostName production-server
IdentityFile /home/claudioc/.ssh/production.pem
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
npm install
andnpm prune
- once the npm install is finished, I run my tests with
npm test
- if all the test pass, I run
gulp build
- if the build is successful, I will
rsync
the/dist
directory in the directory which is served by the web server
Bonus (bullet) points
- stdout output from the
post-receive
hook 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 useecho
statements to make it even more verbose) - the user with which the
post-receive
script 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.
Further readings
How To Set Up Automatic Deployment with Git with a VPS | DigitalOcean
https://blog.tankywoo.com/git/2014/04/15/git-use-specified-ssh-key.html
Setting up Push-to-Deploy with git
Written on May 10, 2016 by Claudio Cicali.
Originally published on Medium