This article was published on March 10th 2016 and received its last update on February 5th 2019. It takes about 5 minutes to read.
Deploying your application should be as fast as possible. Only a fast deployment routine encourages you to deploy often and iterate on your applications.
For the following steps to work I assume that you manage your application's codebase with git and use your own deploy routine (preferably using Ansible in order to unleash this article's full potential). However, they can be adjusted to also work with deploy tools like Capistrano or Mina with a little effort — the principles stay the same.
Bundler provides the
bundle check command which is fast and tells us if any new gems have to be installed in order to make our application work. It exits with
0 if all dependencies are satisfied so we only have to run
bundle install when it evaluates to non-zero.
Time saved: Not much (but we are just warming up).
The main question is: How do we find out if we need to precompile our application's assets?
We can skip this step if the commit we want to deploy only changed files outside of our application's
assets directories, so we have to find out which files were changed:
To get the git SHA of the current commit, we run the command
git rev-parse HEAD from our repository's root directory (before we pulled the latest changes). This will give us a git SHA like
266e7a3a6910be5e588bdb9663d5073918c4a49b which we need to remember.
Now after pulling the latest changes from a git remote, we can use the following command to check for changes in one of our application's assets directories:
git diff --name-only 266e7a3a6910be5e588bdb9663d5073918c4a49b HEAD | grep -E "(app|lib|vendor)/assets"
git diff command lists all files that have changed between these two commits so the return code of the above command tells us whether we need to precompile our assets or not: If
$? evaluates to
1 we can skip asset precompilation because no assets have changed.
Time saved: Depending on number and complexity of your assets, skipping unnecessary precompilation can save up to minutes.
In order to know if our database needs migration, we first have to find out what was the last migration that has been applied. The easiest way to do this is by looking it up in our application's database. Each Rails application having a database stores all applied migrations' names in a table called
schema_migrations so that's where we can go looking for it.
Assuming that you run MySQL/MariaDB, running the following command from your application's root directory will give you the latest applied migration's name (do not forget to substitute
MYAPP to match your environment):
mysql --user=USER --password=PASSWORD --batch --skip-column-names --execute="USE MYAPP; SELECT version FROM schema_migrations ORDER BY version DESC LIMIT 1;"
--skip-column-names flags make sure that we get just the migration's name (like
20150330095124). The actual command probably differs if you are using another database system.
Now that we know the last migration which has been applied to our database, we need to find the latest available migration. We can do this by running the following command from our application's root directory:
ls db/migrate | tail -1 | cut -d '_' -f 1
Rails migrations are named in the format
20150913132628_create_customers.rb. Since we only want the timestamp of the latest migration, this command lists all files in our application's
db/migrate directory (
ls db/migrate), reduces the output to just the last file (
tail -1), splits its filename at every
_ and displays the first item of the resulting array.
Now we have everything together to judge whether our application's database needs to be migrated or not. We just compare the two values — if they differ, then we need to migrate. If they do not, we can safely skip this step.
You may have noticed that there is a rake task called
db:migrate:status which lists our application's migrations along with their status. Unfortunately, it always exits with
0 which does not make it suitable for using it inside a script — that's why I think the outlined method is much more reliable than parsing the rake command's output.
Time saved: 1.25 seconds on average.
If you are using Ansible to deploy your Rails application but you do not make use of facts, disable fact gathering by setting
gather_facts: no in your deploy playbook.
Time saved: About 3 seconds.
Enabling pipelining reduces the number of SSH operations required to execute an Ansible module on a remote server by executing many modules without actual file transfer.
To enable pipelining, you can either create a configuration file for your user (
~/.ansible.cfg) or edit Ansible's global configuration by setting
pipelining in the
Alternatively you can temporarily enable pipelining by running
export ANSIBLE_SSH_PIPELINING=1 before you run your deploy playbook (you can turn it back off if you need to with
This is actually the better way because in order for pipelining to work, you have to disable
requiretty in your target machine's sudoers configuration. It is probably better to enable pipelining on demand if you manage machines where disabling
requiretty is not possible.
Time saved: Where pipelining is possible, it results in a really big performance improvement — bringing the time needed to deploy this website from 23 seconds down to 11.
If I were to apply only one of the steps outlined here, I'd go with conditionally skipping asset precompilation — it saves you more time everytime your application's assets get more complex.
However, it definitely pays to squeeze every bit of speed out of your deploy routine. Being able to deploy new releases quickly makes sure that deploying does not get postponed and fixes and new features for your customers are available as soon as possible. Stay agile!
The steps outlined here are direct excerpts from my book Efficient Rails DevOps. If you find this article useful, get the book — it's full of actionable advice like this.