Deploying Ruby applications
Overview
This document is a hands-on guide to deploying a simple Ruby application intsuru. The example application will be a very simple Rails project associatedto a MySQL service.
Creating the app
To create an app, you use the command app-create:
- $ tsuru app-create <app-name> <app-platform>
For Ruby, the app platform is ruby
! Let’s be over creative and develop anever-developed tutorial-app: a blog, and its name will also be very creative,let’s call it “blog”:
- $ tsuru app-create blog ruby
To list all available platforms, use the command platform-list.
You can see all your applications using the command app-list:
- $ tsuru app-list
- +-------------+-------------------------+-------------+
- | Application | Units State Summary | Address |
- +-------------+-------------------------+-------------+
- | blog | 0 of 0 units in-service | |
- +-------------+-------------------------+-------------+
Application code
This document will not focus on how to write a blog with Rails, you can clonethe entire source direct from GitHub:https://github.com/tsuru/tsuru-ruby-sample. Here is what we did for theproject:
- Create the project (
rails new blog
) - Generate the scaffold for Post (
rails generate scaffold Post title:string body:text
)
Git deployment
When you create a new app, tsuru will display the Git remote that you shoulduse. You can always get it using the command app-info:
- $ tsuru app-info --app blog
- Application: blog
- Repository: git@192.168.50.4.nip.io:blog.git
- Platform: ruby
- Teams: admin
- Address: blog.192.168.50.4.nip.io
- Owner: admin@example.com
- Team owner: admin
- Deploys: 0
- Pool: theonepool
- App Plan:
- +---------------+--------+------+-----------+---------+
- | Name | Memory | Swap | Cpu Share | Default |
- +---------------+--------+------+-----------+---------+
- | autogenerated | 0 MB | 0 MB | 100 | false |
- +---------------+--------+------+-----------+---------+
The Git remote will be used to deploy your application using Git. You can justpush to tsuru remote and your project will be deployed:
- $ git push git@192.168.50.4.nip.io:blog.git master
- Counting objects: 86, done.
- Delta compression using up to 4 threads.
- Compressing objects: 100% (75/75), done.
- Writing objects: 100% (86/86), 29.75 KiB, done.
- Total 86 (delta 2), reused 0 (delta 0)
- remote: Cloning into '/home/application/current'...
- remote: requirements.apt not found.
- remote: Skipping...
- remote: /home/application/current /
- remote: Fetching gem metadata from https://rubygems.org/.........
- remote: Fetching gem metadata from https://rubygems.org/..
- #####################################
- # OMIT (see below) #
- #####################################
- remote: ---> App will be restarted, please check its log for more details...
- remote:
- To git@192.168.50.4.nip.io:blog.git
- * [new branch] master -> master
If you get a “Permission denied (publickey).”, make sure you’re member of ateam and have a public key added to tsuru. To add a key, use the command key-add:
- $ tsuru key-add mykey ~/.ssh/id_rsa.pub
You can use git remote add
to avoid typing the entire remote url every timeyou want to push:
- $ git remote add tsuru git@192.168.50.4.nip.io:blog.git
Then you can run:
- $ git push tsuru master
- Everything up-to-date
And you will be also able to omit the —app
flag from now on:
- $ tsuru app-info
- Application: blog
- Repository: git@192.168.50.4.nip.io:blog.git
- Platform: ruby
- Teams: admin
- Address: blog.192.168.50.4.nip.io
- Owner: admin@example.com
- Team owner: admin
- Deploys: 0
- Pool: theonepool
- Units: 1
- +------------+---------+
- | Unit | State |
- +------------+---------+
- | eab5151eff | started |
- +------------+---------+
- App Plan:
- +---------------+--------+------+-----------+---------+
- | Name | Memory | Swap | Cpu Share | Default |
- +---------------+--------+------+-----------+---------+
- | autogenerated | 0 MB | 0 MB | 100 | false |
- +---------------+--------+------+-----------+---------+
Listing dependencies
In the last section we omitted the dependencies step of deploy. In tsuru, anapplication can have two kinds of dependencies:
- Operating system dependencies, represented by packages in the package managerof the underlying operating system (e.g.:
yum
andapt-get
); - Platform dependencies, represented by packages in the package manager of theplatform/language (in Ruby,
bundler
).
Allapt-get
dependencies must be specified in arequirements.apt
file,located in the root of your application, and ruby dependencies must be locatedin a file calledGemfile
, also in the root of the application. Since wewill use MySQL with Rails, we need to installmysql
package usinggem
,and this package depends on anapt-get
package:libmysqlclient-dev
, sohere is howrequirements.apt
looks like:
- libmysqlclient-dev
And here is Gemfile
:
- source 'https://rubygems.org'
- gem 'rails', '3.2.13'
- gem 'mysql'
- gem 'sass-rails', '~> 3.2.3'
- gem 'coffee-rails', '~> 3.2.1'
- gem 'therubyracer', platforms: 'ruby'
- gem 'uglifier', '>= 1.0.3'
- gem 'jquery-rails'
You can see the complete output of installing these dependencies below:
- $ git push tsuru master
- #####################################
- # OMIT #
- #####################################
- remote: Reading package lists...
- remote: Building dependency tree...
- remote: Reading state information...
- remote: The following extra packages will be installed:
- remote: libmysqlclient18 mysql-common
- remote: The following NEW packages will be installed:
- remote: libmysqlclient-dev libmysqlclient18 mysql-common
- remote: 0 upgraded, 3 newly installed, 0 to remove and 0 not upgraded.
- remote: Need to get 2360 kB of archives.
- remote: After this operation, 9289 kB of additional disk space will be used.
- remote: Get:1 http://archive.ubuntu.com/ubuntu/ quantal/main mysql-common all 5.5.27-0ubuntu2 [13.7 kB]
- remote: Get:2 http://archive.ubuntu.com/ubuntu/ quantal/main libmysqlclient18 amd64 5.5.27-0ubuntu2 [949 kB]
- remote: Get:3 http://archive.ubuntu.com/ubuntu/ quantal/main libmysqlclient-dev amd64 5.5.27-0ubuntu2 [1398 kB]
- remote: Fetched 2360 kB in 2s (1112 kB/s)
- remote: Selecting previously unselected package mysql-common.
- remote: (Reading database ... 41063 files and directories currently installed.)
- remote: Unpacking mysql-common (from .../mysql-common_5.5.27-0ubuntu2_all.deb) ...
- remote: Selecting previously unselected package libmysqlclient18:amd64.
- remote: Unpacking libmysqlclient18:amd64 (from .../libmysqlclient18_5.5.27-0ubuntu2_amd64.deb) ...
- remote: Selecting previously unselected package libmysqlclient-dev.
- remote: Unpacking libmysqlclient-dev (from .../libmysqlclient-dev_5.5.27-0ubuntu2_amd64.deb) ...
- remote: Setting up mysql-common (5.5.27-0ubuntu2) ...
- remote: Setting up libmysqlclient18:amd64 (5.5.27-0ubuntu2) ...
- remote: Setting up libmysqlclient-dev (5.5.27-0ubuntu2) ...
- remote: Processing triggers for libc-bin ...
- remote: ldconfig deferred processing now taking place
- remote: /home/application/current /
- remote: Fetching gem metadata from https://rubygems.org/..........
- remote: Fetching gem metadata from https://rubygems.org/..
- remote: Using rake (10.1.0)
- remote: Using i18n (0.6.1)
- remote: Using multi_json (1.7.8)
- remote: Using activesupport (3.2.13)
- remote: Using builder (3.0.4)
- remote: Using activemodel (3.2.13)
- remote: Using erubis (2.7.0)
- remote: Using journey (1.0.4)
- remote: Using rack (1.4.5)
- remote: Using rack-cache (1.2)
- remote: Using rack-test (0.6.2)
- remote: Using hike (1.2.3)
- remote: Using tilt (1.4.1)
- remote: Using sprockets (2.2.2)
- remote: Using actionpack (3.2.13)
- remote: Using mime-types (1.23)
- remote: Using polyglot (0.3.3)
- remote: Using treetop (1.4.14)
- remote: Using mail (2.5.4)
- remote: Using actionmailer (3.2.13)
- remote: Using arel (3.0.2)
- remote: Using tzinfo (0.3.37)
- remote: Using activerecord (3.2.13)
- remote: Using activeresource (3.2.13)
- remote: Using coffee-script-source (1.6.3)
- remote: Using execjs (1.4.0)
- remote: Using coffee-script (2.2.0)
- remote: Using rack-ssl (1.3.3)
- remote: Using json (1.8.0)
- remote: Using rdoc (3.12.2)
- remote: Using thor (0.18.1)
- remote: Using railties (3.2.13)
- remote: Using coffee-rails (3.2.2)
- remote: Using jquery-rails (3.0.4)
- remote: Installing libv8 (3.11.8.17)
- remote: Installing mysql (2.9.1)
- remote: Using bundler (1.3.5)
- remote: Using rails (3.2.13)
- remote: Installing ref (1.0.5)
- remote: Using sass (3.2.10)
- remote: Using sass-rails (3.2.6)
- remote: Installing therubyracer (0.11.4)
- remote: Installing uglifier (2.1.2)
- remote: Your bundle is complete!
- remote: Gems in the groups test and development were not installed.
- remote: It was installed into ./vendor/bundle
- #####################################
- # OMIT #
- #####################################
- To git@192.168.50.4.nip.io:blog.git
- 9515685..d67c3cd master -> master
Running the application
As you can see, in the deploy output there is a step described as “Restartingyour app”. In this step, tsuru will restart your app if it’s running, or startit if it’s not. But how does tsuru start an application? That’s very simple, ituses a Procfile (a concept stolen from Foreman). In this Procfile, you describehow your application should be started. Here is how the Procfile should look like:
- web: bundle exec rails server -p $PORT -e production
Now we commit the file and push the changes to tsuru Git server, runninganother deploy:
- $ git add Procfile
- $ git commit -m "Procfile: added file"
- $ git push tsuru master
- #####################################
- # OMIT #
- #####################################
- remote: ---> App will be restarted, please check its log for more details...
- remote:
- To git@192.168.50.4.nip.io:blog.git
- d67c3cd..f2a5d2d master -> master
Now that the app is deployed, you can access it from your browser, getting theIP or host listed in app-list
and opening it. For example,in the list below:
- $ tsuru app-list
- +-------------+-------------------------+---------------------+
- | Application | Units State Summary | Address |
- +-------------+-------------------------+---------------------+
- | blog | 1 of 1 units in-service | blog.cloud.tsuru.io |
- +-------------+-------------------------+---------------------+
Deployment hooks
It would be boring to manually run rake db:migrate
after every deployment.So we can configure an automatic hook to always run before or afterthe app restarts.
tsuru parses a file called tsuru.yaml
and runs restart hooks. As theextension suggests, this is a YAML file, that contains a list of commands thatshould run before and after the restart. Here is our example of tsuru.yaml:
- hooks:
- restart:
- before:
- - RAILS_ENV=production bundle exec rake db:migrate
For more details, check the hooks documentation.
tsuru will look for the file in the root of the project. Let’s commit anddeploy it:
- $ git add tsuru.yaml
- $ git commit -m "tsuru.yaml: added file"
- $ git push tsuru master
- #####################################
- # OMIT #
- #####################################
- To git@192.168.50.4.nip.io:blog.git
- a780de9..1b675b8 master -> master
It is necessary to compile de assets before the app restart. To do it we canuse the rake assets:precompile
command. Then let’s add the command tocompile the assets in tsuru.yaml:
- hooks:
- build:
- - RAILS_ENV=production bundle exec rake assets:precompile
- $ git add tsuru.yaml
- $ git commit -m "tsuru.yaml: added file"
- $ git push tsuru master
- #####################################
- # OMIT #
- #####################################
- To git@192.168.50.4.nip.io:blog.git
- a780de9..1b675b8 master -> master
It’s done! Now we have a Rails project deployed on tsuru.
Now we can access your blog app in the URL returned in app-info.
Going further
For more information, you can dig into the tsuru docs, orread the complete instructions on how to use the tsuru command.