Deploying Mephisto with Capistrano to DreamHost
November 30th, 2008
This post describes how to use Capistrano to deploy a Mephisto based blogging engine to a DreamHost account running Phusion Passenger. The blog code is deployed from a local git repository. gitting Started with Mephisto describes how the repository was set-up. With some modifications the script and commands presented here should be adaptable to other hosting services and other Phusion based Rails applications.
This was my first use of Capistrano. To get a foundation I watched the PeepCode, Capistrano 2 screencast, read the From the Beginning page at the Capistrano site, and watched the Capistrano Tasks Railscast. All of those were good resources, the Railscast episode from Ryan Bates was particularly applicable due to Ryan's use of git and coverage of assets. The best coming up to speed tip that I can provide is to open up the gem source code and use it as a reference. Pay particular attention to the lib/capistrano/recipes/deploy.rb file. It contains the default tasks, contains a large chunk of the built-in variables and makes the deployment execution order clear.
Server Prerequisites
If you haven't set up a user account with SSH access that Capistrano can use for deployment then do it now. Any existing user account will do but one minor point that may affect the username you choose is that your web directory will eventually reside somewhere under one of your users home path, i.e. it'll end up some place like /home/username/mywebapp.com/current/public. I created a user called rsblog to house and deploy my blog application. Setting up a separate deployment user would also be a reasonable choice but DreamHost doesn't provide superuser (sudo) access. So if you go that way you'll need to configure group permissions to allow you to deploy files to the application user's directory tree.
Capistrano supports a :password variable that will allow you to put a password in your script but it's more secure to setup a public key instead. If you haven't done that then DreamHost SSH Access with Leopard.html walks you through the setup if you have Mac Leopard. For other systems you can start with the DreamHost SSH Wiki.
You'll also need to create a database for Mephisto on the DreamHost server. To do that open up your control panel to the Goodies > Manage MySQL section and create one using the Create a new MySQL database: section. The database name has to be unique so the default name in the database.yml file, mephisto_production, probably won't work. A common approach is to prefix the name you want with your domain name, for example, I used robseaman_mephisto_production. When you create the database it's a good idea to create a new user that only has database access rather than using a shell account. You can do this by selecting Create a new user now... from the First User: drop down in the Create a new MySQL database: section. The concept is that separate accounts will limit your vulnerability if one of your accounts is compromised (you know, stolen, hacked into, etc...). In general creating separate accounts for different roles with each account having just enough permissions to do its job is a common and solid practice. That (along with plagiarism) is the basis for why there's a separate deploy user account in so many of the Capistrano examples. If this is a new concept to you and you start applying it then be sure to use some common sense in your role breakdowns, too many accounts create administration headaches. Ah, but I digress ... back to the subject.
Capistrano Setup
The current version of Capistrano is 2.5.2 as I'm writing this. Upgrade if you have an older version. If you haven't installed Capistrano yet then the gem is installed in the usual way:
[mac]$ sudo gem install capistrano
Great, easy as Ruby, now it's time to "capify" the application. This will create the Capfile and config/deploy.rb files and keep our repository clean:
[mac]$ cd mephisto [mac]$ capify . [mac]$ git add . [mac]$ git commit -a -m "Initial versions from capify."
Capfile doesn't need any changes. config/deploy.rb needs extensive overhauling. There's a config/deploy.example.rb that Mephisto provides but I didn't use it and if you're following along and using the same or similar environment then I think you'll end up with a fewer modifications if you just use mine. I'll show it in chunks so I can describe its contents. Here's part one:
1 2 3 4 5 6 7 8 9 10 11 |
# Settings that change from app to app set :user, "rsblog" set :application, "blog.robseaman.com" set :scm, :git set :branch, "robseaman" # "master" would be more typical set :repository, "file:///Users/Rob/work/mephisto/.git" set :deploy_via, :copy set :git_shallow_clone, 1 set :copy_exclude, [".git", ".gitignore"] set :deploy_to, "/home/#{user}/#{application}" |
You'll need to change some of this first part of deploy.com to your own settings. Here's an overview: The user2 is your SSH user account that you have setup to do the deployment. The application3 is the domain name. I created a separate git 4 branch5 from master to keep my changes separate from the open source version of Mephisto. Replace it with whatever branch you want to deploy. Since I'm using a local file repository6 from my laptop I chose the copy7 deployment strategy. :git_shallow_clone 8sets the --depth to 1 to make the clone faster, we're not passing the git repository anyway. Eliminating the .git/.gitignore9 on the server results in a significant reduction in compression time. With this Mephisto repository it takes us from a 8.9 MB transfer down to 3.2 MB. My choice of deploy_to10 follows DreamHost defaults but if you created a separate user for deploying then you'll need to change #{user}10 to the name of DreamHost user that you want to contain the application. You can also change the #{application}10 part of the path to something different, i.e. mephisto or whatever you want. You could also set :copy_cache to true; I tried it but I didn't notice a significant difference either way so I left it out.
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# App specific tasks: namespace :deploy do desc <<-DESC Bootstrap the Mephisto production database. Runs the Mephisto \ db:bootstrap rake task for the production database. Normally run \ one time as part of the initial deployment: $ cap deploy:setup $ cap deploy:update $ cap deploy:bootstrap WARNING! This task will reinitialize your database. If you run \ it after the initial deployment all of your content will be lost. DESC task :bootstrap, :roles => [:db], :only => { :primary => true } do run "cd #{current_path}; rake RAILS_ENV=production db:bootstrap" end end |
I decided to create a separate section (starting a line 12 above) at the top of my deploy.rb file for application specific tasks. In this case I added code to run Mephisto's db:bootstrap task on the production server. The task description in lines 14-25 says it all. If you don't want a task to do the bootstrap from the client than you can leave this code out; it's no problem to ssh to the server and run rake db:bootstrap when you need to. The task is something I added early on while I was just figuring out how things worked and saw several implementations with the equivalent functionality. It saves a little typing but it's rarely done and the manual steps are so simple it doesn't really provide that much of a benefit.
31 32 33 34 35 36 37 38 39 40 |
# The rest is generic for DH a shared hosting deployment, assumming: # - Phusion Passenger is used # - An assets dir in public that's not part of the scm repository. # - The database.yml is not in the repository set :use_sudo, false role :app, application role :web, application role :db, application, :primary => true |
Lines 31-40 contain pretty standard stuff. We can't use_sudo35 on DreamHost and the roles37-39 are all on the same server.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
namespace :deploy do desc "Restart passenger." task :restart do run "touch #{current_path}/tmp/restart.txt" end desc "Initial setup of the shared_path." task :setup_shared do run "mkdir -p #{shared_path}/config #{shared_path}/assets" end desc "Gets the shared_path ready for use with the release." task :prepare_shared do run "ln -nfs #{shared_path}/assets #{release_path}/public/assets" run "ln -nfs #{shared_path}/config/database.yml "+ "#{release_path}/config/database.yml" end end after 'deploy:setup', 'deploy:setup_shared' before 'deploy:finalize_update', 'deploy:prepare_shared' |
Lines 41-60 contain the deploy namespace tasks. Phusion Passenger is restarted42-45 in the usual way. :setup_shared47-50 is intended to create shared directories or files needed as part of the initial setup. In this case that's only the assets directory49, which holds article assets so it's left out of the repository and a single copy is kept and shared between releases. It needs to exist on initial deployment when it's symlinked54 for the first time. :prepare_shared52-57 is intended for any setup required to prepare the shared path for use. In this case that just means symlink creation. Tasks like :prepare_shared are typically done after deploy:update_code. I chose to do it before deploy:finalize_update60 instead of the usual time after reading a comment in Cached Externals about touching order and looking at the :finalize_update task code. Doing it right before :finalize_update will allow :normalize_asset_timestamps to work properly with anything that needs timestamping in the shared path. Although it won't matter in this deployment, doing it before :finalize_update seems like the correct execution order and I expect to reuse this code in other projects where it could make a difference. Because the database.yml file contains the production password, I opted to only keep it on the production server. I'll give instructions about how to create it before it's linked55 to later, but if you'd rather just keep your production information in your local database.yml and copy it up as part of the deployment then insert:
50 |
top.upload("config/database.yml", "#{shared_path}/config/database.yml") |
into :setup_shared. This will upload the file whenever you run cap deploy:setup, which is safe to run anytime. Alternatively, you could add it to the beginning of :prepare_shared to upload it with each deployment.
That's enough to give us a complete deployment script so it's a good time to commit the changes. And, since were about to deploying a release to production, I'd also recommend tagging it with a version or some other type label:
[mac]$ git commit -a -m "First customized version of deploy.rb" [mac]$ git tag -m "First production blog release." v0.0.1
Initial Deployment
Originally cap deploy:cold was intended to be a single step initial deployment. It still exists but it is no longer recommended. It was recognized that generically automating initial deployments for web applications just isn't something that works well. For example, this blog deployment is pretty simple but db:bootstrap is used for database and server setup, migrations aren't needed, and I've made a security decision not to automate deployment of the database.yml. At the very least deploy:cold would need to be rewritten to get it to work. It might be worth exploring whether or not you can get deploy:cold to work if you have some type of cookie cutter deployment but it's not worth it if the deployment environment varies or for the one time setup of a web application. As it turns out even without cap deploy:cold Capistrano makes it easy for a first time deploy. The first step is to prepare the directory structure on the server:
[mac]$ cap deploy:setup
I chose to only keep separate database.yml files on the client and server in order to keep database account settings off the deployment client. Unless you added the top.upload() code in :setup_shared or you did something similar to get your database.yml file to the server then we'll need to create the database.yml on the server. It doesn't matter how you get it there, you can edit a yaml file locally and put it on the server then delete it, create it on the server or whatever. I scp'd the Mephisto example database yaml file and edited on the server with vim :
[mac]$ scp config/database.example.yml \ > rsblog@robseaman.com:blog.robseaman.com/shared/config/database.yml [mac]$ ssh rsblog@robseaman.com [dh]$ cd blog.robseaman.com/shared/config [dh]$ vim database.yml # edit production settings and save [dh]$ logout
Your production section contents will vary but my DreamHost version is close to this:
32 33 34 35 36 37 38 |
production:
adapter: mysql
database: robseaman_mephisto_production
username: robsblogdbuser
password: YeahSure
host: mysql.robseaman.com
encoding: utf8
|
BTW: If you did decide to put the top.upload() code in the :setup_shared deployment task but ran cap deploy:setup before updating the production section of your database.yml then just run cap deploy:setup again after the database.yml update is complete.
Now move the application to the server with:
[mac]$ cap deploy:update
At this point the database exists but lacks a schema so it's time for our Mephisto specific task. Run the task for that:
[mac]$ cap deploy:bootstrap
If you see a line that looks like this:
*** [err :: robseaman.com] mkdir -p /home/rsblog/xxxx/releases/20081129044823/log
don't worry about it. It's occurs because we already created the assets directory. If you didn't add the deploy:bootstrap to deploy.rb then just ssh to the server, change to the current directory and run rake db:bootstrap. For other applications this would be the point where you'd usually run rake RAILS_ENV=production db:schema:load.
Now the web application needs to be started. For DreamHost this means changing the hosting settings for the domain to use Phusion Passenger and to point to the application. Go to Domain > Manage Domains in your control panel and select Edit in the Web Hosting column for the domain. Set the FTP user / CGI-runs-as user to your application user, for me that was rsblog. The web directory should default to the domain name, for me it was /home/username/blog.robseaman.com. If you used the same :deploy_to variable in deploy.rb as me just add /current/public, for example I ended up with /home/username/blog.robseaman.com/current/public. Now check the box next to Ruby on Rails Passenger (mod_rails)?. If you want other changes go ahead and make them. When you're done press the Change fully hosting settings now! button.
Give it a minute or two and you should be able to pint your browser to http://your_domain/admin, i.e. http://blog.robseaman.com/admin for me. Login with Login: admin and Password: test, change the password, and change the site settings. I did notice at this point that I needed to go to Settings > Caches (admin/cached_pages) and clear the cache to see my changes but I'm not yet sure if that's always necessary. Now you should be able to start blogging.
Subsequent Deploys
Subsequent deployments is where Capistrano really shines. When you want to make a change you can do it in your development environment and after you make sure it works and git commit it you can move it to your web server and restart the application with a single command:
[mac]$ cap deploy
It's probably a good idea to add a git tag step, like we did before the initial deploy above, before each deploy but that's up to you. If you can't remember what version you used last, just by type git tag with no options and you'll get a list.
Is that it?
At this point we have a working maintainable blogging system. The blog software and its templates can be tracked and updated and it's easy to deploy changes from a development machine. From a workflow standpoint it would be very helpful if it were easy to copy the production database into the development environment to see what impact changes will have on existing content before an update is deployed. I'll cover that in the Production Data to Development post. Once that's in place I'd consider it a functionally complete working environment but doesn't it seem inefficient that with every update Rails and all that stuff in the vendor directory that hardly ever changes gets compressed and uploaded? That issue can be addressed using Cached Externals, which I've covered in another post.
Sorry, comments are closed for this article.