This article was published on March 10th 2016 and received its last update on February 5th 2019. It takes about 5 minutes to read.
5 steps to cut your deploy time in half
Deploying your application should be as fast as possible. Only a fast deployment routine encourages you to deploy often and iterate on your applications.
Table of contents
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.
1. Skip bundle installation
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).
2. Skip asset precompilation
Asset precompilation can take quite some time — even if there are no new assets to precompile. Since not every change to your application's codebase involves changes to its CSS- and JavaScript-files, it pays to conditionally skip this step.
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"
The 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.
3. Skip database migrations
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 USER
, PASSWORD
and 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;"
The --batch
and --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.
4. Disable Ansible's facts gathering
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.
5. Enable Ansible's pipelining
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 ssh_connection
section:
[ssh_connection]
pipelining=True
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 export ANSIBLE_SSH_PIPELINING=0
).
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.
Conclusion
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.
Get in the loop
Join my email list to get new articles delivered straight to your inbox and discounts on my products.
No spam — guaranteed.
Got it, thanks a lot!
Please check your emails for the confirmation request I just sent you. Once you clicked the link therein, you will no longer see these signup forms.