Automating web site deployment

3 minute read ,

I don’t like doing something by hand if it can be automated, so I set up my Jekyll and static websites to be automatically deployed with a git push.

The Jekyll deployment instructions were used as a starting point.

Deployment Environment Details

The server that runs this blog is a Digital Ocean droplet running Docker.

If you use the default Digital Ocean Docker droplets, make sure you configure a firewall. The default installations are wide open on the public internet with no firewall. Since Docker likes opening up a tonne of ports for “convenience”, do your homework to tighten things up unless you like being hacked.

The nginx instance that serves static content runs inside a Docker container, created from the official nginx image.

The website configuration and document root directories are Docker volumes because they need to persist across container lifespans.

For database needs, PostgreSQL is used, running inside a Docker container created from the official PostgreSQL image.

The PostgreSQL data directory is also a Docker volume, because it needs to persist across container lifespans.

LetsEncrypt is installed on the Docker host, and the SSL certificate and key directories are mounted as a Docker volume for containers that need access to them.

Docker Compose is used to describe the environment and dependencies between containers, so that all services can be started in dependency order with a docker-compose up -d command, and shut down with a docker-compose down command.

Automated deployment of Jekyll sites

The concept is that a Git remote is added to the Git repository containing the Jekyll site or existing static HTML content.

This is done on any machine that will be triggering deployments.

Deployment is a git push to this remote, which triggers the actual work via a Git repository hook on the receiving side.

Setting up the source repository

A remote was added to the Git repository on my laptop, called deploy, referencing the receiving repository on the Digital Ocean <SERVER>.

$ git remote add deploy deploy@<SERVER>:~/<SITE>.git

Setting up the receiving repository

As a best practice, a low privilege user named deploy was created on <SERVER>, given write access only to the temporary directories and HTML document root directories.

Password login was disabled for this user, only SSH authorized key logins permitted.

The receiving repository was then created :

$ ssh deploy@<SERVER>
$ mkdir <SITE>.git
$ cd <SITE>.git
$ git --bare init
$ rm -f hooks/*.sample

The script hooks/post-receive was added to this repository:

$ touch hooks/post-receive
$ chmod +x hooks/post-receive

This script gets called after a push to this repository.

Adding a -e option to your hash-bang command-line in the script is recommended, so that the script exits if any command returns a non-zero exit code.

Adding trap to ensure cleanup happens no matter how the script terminates is also recommended. For example, trap cleanup EXIT will call a shell function named cleanup whenever the script exits, however it exits.

The script does the following:

  • Cleans up any temporary directories that may have been left over
  • Clones <SITE>.git into a temporary directory
  • Executes jekyll build to produce the static HTML content
  • Moves the existing static HTML content for the site to a backup <SITE>/previous directory, for quick roll-backs.
  • Moves the newly generated static HTML content to a <SITE>/current directory
  • Ensures some well-known directories I depend on in the Nginx configuration are in place
  • Ensures a <SITE>/root symlink points to <SITE>/current
  • Cleans up all temporary directories that were created

Nginx is configured to serve up content from <SITE>/root, whatever that symlink points to.

Performing a deploy

From a machine with the source repository, execute git push deploy master. That’s it.

Triggering a deploy if the script failed

If there were bugs in the post-receive script, running it manually to finish the deploy is simple:

$ ssh deploy@<SITE> <SITE>.git/hooks/post-receive

Automated deployment of static content sites

This is identical to Jekyll sites in every way, with the exception that Jekyll is not called by the post-receive script, since the static content is already available.

All the script has to do is move it into place.