So you’ve bought yourself a good book on rails (I recommend Agile Web Development with Rails, Third Edition), and you’ve finished developing, and writing tests for, your application. If you’ve been following Agile development methods, you have even been deploying to your server using something like Capistrano right from the get go.
But now you are thinking about going live to the world, and suddenly realize there is still so much more to be done! This is exactly where I found myself recently with the deployment of brockbouchard.net. Below are some of the tasks that are easy to overlook and how to go about completing them.
1. Keeping Log Files Tidy
While you could have your single production.log file fill up indefinitely on your server, it is better to create a rake task that will automatically truncate and create separate files for logs older than X days. Below is a rake task that I use called “clean_logs” that does just this. Place this code in a ruby file in your [rails_app]/lib/task directory:
require 'ftools'
desc "Truncates and backs up log files. Deletes backup logs older than ENV['CLEAN_LOGS_DAYS_OLD'] days, default is 30. Set RAILS_ENV on command line to target correct log file."
task :clean_logs => :environment do
days_ago = ENV['CLEAN_LOGS_DAYS_OLD'] || 30
environment_name = ENV['RAILS_ENV'].downcase
log_file = "log/#{environment_name}.log"
log_file_backup = "log/#{environment_name}-#{Date.today.to_s}.log"
if File.exist?(log_file)
# move log file
File.move log_file, log_file_backup, true
# delete log files older than days_ago
puts "Deleting log file backups prior to #{Date.today - days_ago}"
Dir.foreach "log" do |filename|
if filename =~ /#{environment_name}-\d\d\d\d-\d\d-\d\d/
backup_date = Date.parse filename.scan(/\d\d\d\d-\d\d-\d\d/)[0]
File.delete "log/#{filename}" if Date.today - backup_date > days_ago
end
end
else
puts "#{log_file_name} does not exist."
end
end
Note here that X defaults to 30, you can change the default to whatever you like, or you can pass in the number of days to keep. With this rake task in place, add an entry for it to your crontab:
0 9 * * * cd /path/to/rails/app && /path/to/rake clean_logs RAILS_ENV=production
Notice that the full path to the rake executable is specified. In my case, it is /usr/bin/rake. However on some systems it may be different.
2. Backing Up Your Database
Even if you run a small site with a small audience, or don’t have access to fancy backup drives or tapes, you should employ some kind of database backup. This is indeed my situation: brockbouchard.net and its MySQL database all run on one affordable VPS hosting plan. Nonetheless I run a database backup every night with the following shell script…
#!/bin/sh
/usr/bin/mysqldump -h localhost database_name -uusername -ppassword > /path/to/db/backup/directory/`date +%Y%m%d`.sql
/bin/gzip /path/to/db/backup/directory/`date +%Y%m%d`.sql
…that is called from cron with the following entry…
0 10 * * * /path/to/db_backup_script
Be sure to verify the exact location of your mysqldump and gzip commands. They could be different on your system. Indeed I was loathe at first to go through setting this up, but just the other day MySQL decided to corrupt my live database upon server restart. Fortunately I had a backup ready and waiting!
3. Handling Errors
The first thing to do here is create custom 404, 422 and 500 error pages. You’ll notice that your Rails app has an html file corresponding to each error number under the [rails_app]/public directory. While the pages created by rails may suffice, it would probably be more helpful to create a page with relevant contact information on it!
The next thing to do is install the Exception Notifier plugin. It’s a pretty straight-forward installation, with the following catch. If you are using a recent version of Rails (I believe 2.2 or later), you’ll need to add a new ruby file to your [rails_app]/config/initializers directory where you will initialize the exception notifier plugin:
ExceptionNotifier.exception_recipients = %w(errors@yourdomain.com)
ExceptionNotifier.sender_address = %(errors@yourdomain.com)
In earlier versions of Rails you could configure ExceptionNotifier right inside of your production.rb configuration file. But apparently something changed in a recent version of Rails such that doing so no longer works.
Finally, in order to actually get error emails, don’t forget to configure your mail server as I talk about below!
4. Configure Your Mail Server
Oh wow did I ever get burnt by this. I spent way too much of my time wondering why emails sent by cron and my Rails app were not arriving. While your mail server situation may be more complex and may already be setup by an IT department, in my case my app is a one man show running on an affordable VPS hosting plan. By default my mail server (exim4) was limiting emails to local domains only. I only discovered this after looking in my /var/log/exim4/mainlog file:
... Mailing to remote domains not supported ...
Once I turned this off everything was fine. If you are in the same boat as me, refer to your mail server’s documentation for info on how to disable this behavior.
5. Permissions
Make sure the directory hosting your application is accessible by the user your web server (Apache, nginx, etc) runs as, and make sure new files placed there pick up those permissions. I host brockbouchard.net on Slice Host (which I highly recommend) and they have a great article about setting up Apache permissions. While it follows the deployment conventions of their tutorials, it is nonetheless useful as one could easily draw parallels to their particular environment:
- http://articles.slicehost.com/2007/9/18/apache-virtual-hosts-permissions
6. Maintaing Files in /public
You may have files in your [rails_app]/public directory that are not part of your Rails app; these could be files you upload manually or files that others upload. However if you are using Capistrano to deploy your Rails app (and you should be), each successive deployment will leave the files in your [rails_app]/public directory that are not under source control in the directory for the previous release! Fortunately this is easy enough to get around by putting something similar to the following in your [rails_app]/config/deploy.rb file:
namespace :deploy do
task :move_uploaded_files, :on_error => :continue do
run "mv #{previous_release}/public/uploaded_files #{current_release}/public"
end
end
after "deploy:update_code", "deploy:move_uploaded_files"
7. Basic Search Engine Optimization (SEO)
While I am not an expert in this field, I knew I had to do something about this for my site! I found a number of good tutorials for this with Rails in mind:
http://www.seoonrails.com/
http://www.bingocardcreator.com/articles/rails-seo-tips.htm
http://www.tonyspencer.com/2007/01/26/seo-for-ruby-on-rails/
http://noobonrails.blogspot.com/2006/09/good-seo-mojo-with-rails.html
- http://www.seoonrails.com/
- http://www.bingocardcreator.com/articles/rails-seo-tips.htm
- http://www.tonyspencer.com/2007/01/26/seo-for-ruby-on-rails/
- http://noobonrails.blogspot.com/2006/09/good-seo-mojo-with-rails.html
I also set out to get my Rails app setup with a Google sitemap and Google webmaster tools. This was a little bit more complicated and I will leave it for my next post!