Dead Simple Rails Deployment
Posted: May 31st, 2009 | Author: Jerod | Filed under: Git | Tags: deployment, passenger, rails | View CommentsDeploying a Rails app used to suck. Reverse proxies, Mongrel clusters, Monit, etc. Capistrano helped out a lot (once you set it up the first time), but all in all the process was still pretty painful.
Thankfully, a couple of technologies have come along and made my deployment process a whole lot easier.
- Passenger
- Git
This was the big one. The Phusion guys’ “Hello World” app (as they called it) has really had a positive impact on the Rails community, and me personally. Suddenly my Rails (and Rack) web apps are first class citizens to Apache (and Nginx), which means I can just point a virtual host at the public directory and go. I had almost forgotten how good it feels to just drop some files in a directory and have Apache serve them.
Ok, so maybe Subversion allows a similar workflow, but for some reason Git is one of those tools that is so much fun to use that it makes me think of different ways I can use it.
My Flow
How I deploy these days (when I’m not deploying to Heroku) is dead simple. I host my private Git repos using Gitosis, but the same would work with GitHub or any Git server.
Initial Setup
- Clone the repository on production server.
- Create database.yml and any other production-specific configs
- Configure an Apache virtual host pointing to “public” folder of the repository
Deploys
- locally:
git push origin master
- remotely:
git pull origin master && touch tmp/restart.txt
I know what you’re thinking, “Wow, that is dead simple”. It’s even easier by using Capistrano to execute the remote commands. Here is an example Capistrano task from one of my Rails apps:
task :deploy, :roles => :production do system "git push origin master" cmd = [ "cd #{root_dir}", "git pull", "touch tmp/restart.txt" ] run cmd.join(" && ") end
This task can be extended to automatically install required gems, update Git submodules, migrate the database, and so on.
Other Benefits
Besides the simplicity and ease of deployment in this process, I have also enjoyed the ability to make edits in production and pull them back in to my development environment. And because my production environment has a complete history of code changes, it is trivial to revert commits that cause major problems.
This work flow is by no means a panacea. How do you handle deployment?
you are missing out on a many 'features' of a traditional capistrano/vlad deployment setup.
you can't easily revert to the previous version for example.
Also your deployment is NOT atomic. A Rails requests coming during the deployment can fail etc.
[...] Dead Simple Rails Deployment | blogt✪sk1blog.jerodsanto.net [...]
Rails deploy has come a long way from the pre 1.0 days.
Wow. I couldn’t agree with Vitaly more… Can you explain why you would want to do this over, say, a traditional Capistrano deploy? Sorry to sounds like a debbie downer here, but I can’t think of a single upside to this strategy (and I can see plenty of downsides).
Hi Vitaly-
Wondering if you could explain the “not atomic” piece and tell me how a traditional cap deploy solves this.
With basic capistrano you would have a folder “releases” and a link “current” that links to the active realease within the release folder. So each time you deploy, a new release is created.
So it first pulls the release from the repo and when this is done, it refreshes the link to the new release. In that way you can easily switch between releases in a blink of an eye. Whereas this one would possibly create errors during the “pull”.
Correct me if I'm wrong, but won't Passenger continue to serve the “old” data (in production mode) until it is restarted? I have never received Rails errors during a pull, but the apps I'm deploying aren't sustaining constant requests either.
The production environment only caches code that is loaded.
So if you go to a controller that was not yet visited since the last restart *during* another”pull in progress” you can get an error.
Also consider static files. when you 'git pull' they do not change all at once, so you can have new html pointing to new images that are not yet there. etc.
in short: its not atomic
changing a symlink is.
well, almost ..:) you can still serve some old html and once the request comes for the accompanying images or css files they can already be served from the new release. but thats another story…
The production environment only caches code that is loaded.
So if you go to a controller that was not yet visited since the last restart *during* another”pull in progress” you can get an error.
Also consider static files. when you 'git pull' they do not change all at once, so you can have new html pointing to new images that are not yet there. etc.
in short: its not atomic
changing a symlink is.
well, almost ..:) you can still serve some old html and once the request comes for the accompanying images or css files they can already be served from the new release. but thats another story…
[...] Dead Simple Rails Deployment: Simple instructions to integrate git into Rails deployment scripts [...]
need to check