Sometimes you may have a reason to deploy certain code. This normally involves something like the following: you copy some files to a certain server somewhere, and perhaps restart a server. This is all well known territory, but due to the vagaries of SSH, automating it can often be a pain. There are existing tools for this, Fabric and Capistrano, that are fairly well known but -- it seems to me -- underused. Anyway, they're certainly far from standard, and particularly with regard to Fabric (which I like and use on a near-daily basis, I should point out) they can be tricky to get installed and configured in their own right.

I devised this simple, perhaps even simplistic, plan to handle deployments.

  1. Create a UNIX user that will be used for deployments. For this article we'll refer to this user as dply, although the name is immaterial. This user must exist on the hosts that are the target of the deployments.

  2. Distribute SSH keys to the hosts that need to initiate deployments. This will often be a worker node in a CI system but people may also manually initiate deployments.

  3. Each deployment target receives an appropriate sudoers file that allows them to execute one command (and one only): the deployment processor, with the NOPASSWD specifier.

  4. The deployment user dply can write to a mode 700 directory that is used to receive deployment artifacts. Artifacts are written by a simple scp process to this directory, /home/dply or whatever you like.

  5. The deployment processor script, which is distributed identically to all the nodes and lives in /usr/local/bin, knows about all existing deployments, which are hardcoded with plaintext aliases like main-site, backend, etc, and knows to look for the artifacts in /home/dply or whatever.

  6. Nodes simply scp up the deployment archive, ssh to the relevant server and invoke sudo /usr/local/bin/deployment-processor backend. The processor then looks for the files in a hardcoded location and does whatever's needed to actually deploy them. Concretely in this case every handler is just a function in Perl which can then do many tasks. The key is that it doesn't get any input from the user, thus mitigating some security issues. It's easy to do the various things you may need to do, untar an archive, perhaps chmod some files, restart a service, etc.

It's secure in some senses, but not in others. There's no access isolation between nodes so any node can deploy any code. Once a CI worker node is assumed penetrated, a malicious user can indeed wipe out a production site, but they can't do damage to existing servers. (for whatever that's worth...)

It should be noted that no consensus exists around solutions in this space. It has some virtues over Fabric and probably Capistrano to, by being markedly less complicated, because it only relies on the presence of ssh and scp on the client boxes, which are near-universal. If you wanted to formalize it you could develop cross-platform deployment client binaries in Go or something similar, but I haven't found this necessary. Anecdotally I've had many unpleasant problems with fabric, although it remains a very useful piece of software.

I don't like to deploy with Git because I don't see Git as something that's related to deployments, Git is related to source code history which is distinct from something that I might consider a "release artifact". FWIW release artifacts are also built using a separate processing step, which (for me) is often just a "narrowing" of the file tree according to a set of rsync patterns and tarring up this narrowed tree.

Heroku also have an approach that involves creating "slugs" and "releases" where each release corresponds to a deployment, and "to create a release" is synonymous with "to deploy". This is much more featureful than the above approach but it's over-engineered for this case.

There's also WAR deployment which is interesting but specific to a rather small area of Java development. If you're a Java-only shop, this can probably be nice.

Something that was also on my radar in my department is the Perl-based Rex, which I never got the chance to investigate.