Still Learning About Rails and Deployment
I had been feeling pretty good about my Rails skills and had been able to create and deploy a number of simple applications. Since simple applications are just about all I’m going to write, I was ok with things. But I was hanging on to earlier versions of things and it was starting to bug me. One big one was the capistrano gem. I knew there were big changes between version 2 and 3, and I had a pretty good setup using version 2. However, I know that things constantly change so I decided to dive in and start using capistrano version 3, 3.4.0 to be exact.
The first thing I found out is that version 3 is all about multistage deployment, where first you’d go to a staging server and then your production server. I’m fairly confident that this is something that I’m never going to do. I just don’t work on systems that require that much uptime. And my things are pretty simple, so there’s really no need for a staging server.
The other thing is that it’s really different from version 2. Very little from my old setup was going to be transferable to the new version. The good news is that I had a pretty good understanding of what needed to be done. The bad news was that I really didn’t have any idea of how to do some of it. So, in no particular order, here are some of the things I learned. This is going to be a long post, but I wanted to get my notes down someplace.
To get started, instead of running capify ., you run cap install.
$ cap install mkdir -p config/deploy create config/deploy.rb create config/deploy/staging.rb create config/deploy/production.rb mkdir -p lib/capistrano/tasks create Capfile Capified
I never used to edit the old Capfile, but now I do.
# Load DSL and set up stages require 'capistrano/setup' # Include default deployment tasks require 'capistrano/deploy' # Include tasks from other gems included in your Gemfile # # For documentation on these, see for example: # # https://github.com/capistrano/rvm # https://github.com/capistrano/rbenv # https://github.com/capistrano/chruby # https://github.com/capistrano/bundler # https://github.com/capistrano/rails # https://github.com/capistrano/passenger # # require 'capistrano/rvm' # require 'capistrano/rbenv' # require 'capistrano/chruby' require 'capistrano/bundler' --uncommented require 'capistrano/rails/assets' --uncommented # require 'capistrano/rails/migrations' require 'capistrano/passenger' --uncommented # Load custom tasks from `lib/capistrano/tasks` if you have any defined Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
Here by uncommenting require ‘capistrano/bundler’, when you run cap deploy, the bundle install command will automatically run. So I no longer had to write a task for that.
The require ‘capistrano/passenger’ line automatically adds a deploy:restart command to the deployment. Another task I no longer need.
Require ‘capistrano/rails/assets’ gives the tasks:
cap deploy:cleanup_assets
cap deploy:compile_assets
cap deploy:normalize_assets
cap deploy:rollback_assets
Here compile_assets is the task that I’d mainly use.
I didn’t uncomment the migrations line because I like to run those manually. I don’t change the database that often, so when I do I’m pretty careful about it.
Since I only have a single server, it’s just production. I had to edit the file config/deploy/production.rb. The example file that was created was much longer than what I needed. Here is all that I ended up with in mine:
set :user, ENV["USER"] server 'high-school-registration.example.com', user: fetch(:user), roles: %w{web app db}, primary: true
Here you can see one of the new things in capistrano 3. To get the variable user, you have to use the fetch command.
Once those files are ready to go, now I could start working on the basic deploy.rb file. With capistrano 2, I tried to set up my deployments to always use the same commands where all I had to change was the name of the application to have things set. So I want to do the same thing here. Here’s what I ended up with.
# config valid only for current version of Capistrano lock '3.4.0' set :application, "high-school-registration" set :repo_url, "put the repo location here" # Need this to upload special files set :local_home, "/Users/candh/Documents/ruby/#{fetch(:application)}" set :deploy_to, "/local/data1/web/app/#{fetch(:application)}" set :scm, :git set :pty, true set :linked_files, %w{config/database.yml config/secrets.yml} set :linked_dirs, %w{public/system} namespace :setup do desc "Upload files not in repo" task :upload_files do on roles(:all) do |host| execute :mkdir, "-p", ("#{shared_path}" + "/config") execute :mkdir, "-p", ("#{shared_path}" + "/system") upload! "#{fetch(:local_home)}/config/database.yml", "#{shared_path}/config/database.yml" upload! "#{fetch(:local_home)}/config/secrets.yml", "#{shared_path}/config/secrets.yml" end end end namespace :deploy do namespace :web do desc 'Disable server' task :disable do on roles(:all) do |host| template = ERB.new(File.read("#{fetch(:local_home)}/config/deploy/templates/maintenance.html.erb")).result(binding) upload! StringIO.new(template), "#{release_path}/public/system/maintenance.html" execute :chmod, "644", "#{release_path}/public/system/maintenance.html" end end desc 'Enable server' task :enable do on roles(:all) do |host| execute :rm, "#{release_path}/public/system/maintenance.html" end end end after :restart, :clear_cache do on roles(:web), in: :groups, limit: 3, wait: 10 do # Here we can do anything such as: # within release_path do # execute :rake, 'cache:clear' # end end end end
There’s a lot going on in this file. Since ALL of my rails apps are in my ~/Documents/ruby directory, I like to set the variable application with the name of the app. I then use this same name for the repo on github and the location for the final deployment. Local_home is a new variable that I set because I couldn’t find anything in capistrano 3 that gave me the local Rails.root. By setting this, I can run my tasks that upload certain files (like database.yml and secrets.yml) that aren’t in my repo. It’s also used in my disable/enable tasks.
The linked_files and linked_dirs variables are ones that are automatically linked to the original files in shared. So this is another task that I didn’t have to write. Note though that you will get an error if you run deploy without having your own copies of those files/directories already present.
That’s basically it. The other big change is that you have to tell the cap program which stage you’re running on. So all of my old cap whatever commands are now cap production whatever commands. I haven’t found a way to just default to production always.
$ cap production deploy:check (makes required directories) $ cap production setup:upload_files (puts my files on in the repo on the server and makes some other directories) $ cap production deploy
At this point, I’d then log onto the server and run my migration and seed the database manually. Then, as things change the sequence of commands would be:
$ cap production deploy:web:disable $ cap production deploy $ cap production deploy:web:enable
That basically copies my capistrano 2 workflow to capistrano 3. I still wonder if I should just go back to capistrano 2, since I only have the one stage. But, at least, I’ve figured out how to do it.