How to Deploy Rails Applications to Microsoft Azure with Nanobox

8 January 2018

Ruby on Rails (RoR or Rails) came into the world and won the hearts and minds of developers with its elegant, readable syntax and ease of use. It has since become the go-to framework for many web developers powers many of today's web applications. Microsoft Azure is Microsoft's cloud offering built for deploying and managing applications on their global network of data centers.

In this article, I'm going to walk through deploying a Rails application to Azure using Nanobox. Nanobox uses Docker to build local development and staging environments, as well as scalable, highly-available production environments on Azure.

Start the Local Dev Environment

With the boxfile.yml in place, you can fire up a virtualized local development environment. I recommend adding a DNS alias just so the app will be easier to access from a browser.

# Add a convenient way to access the app from a browser
nanobox dns add local rails.local
# Start the dev environment
nanobox run

Nanobox will provision a local development environment, spin up a containerized Postgres database, mount your local codebase into the VM, load your app's dependencies, then drop you into a console inside the VM.

Generate a New Rails Project

If you have an existing Rails project, you can skip this section. To generate a new Rails project from scratch, run the following from inside the Nanobox console:

Update the Database Connection

When Nanobox spins up a Postgres database, it generates environment variables for the necessary connection credentials. Update the database connection in your config/database.yml to use the provided environment variables.

Run Sidekiq Locally

When using Nanobox locally, web and worker processes need to be started manually. To run Rails and Sidekiq together, start Rails in one terminal session and Sidekiq in another.

Terminal 1

nanobox run rails s -b 0.0.0.0

Terminal 2

nanobox run sidekiq

The two run sessions run inside the same local container, just in separate sessions.

Prepare Rails for Deploy

There are just a few things you need to do before you deploy Rails with Nanobox.

Configure Nginx & Puma

When deployed, Nanobox is going to start an Nginx reverse proxy and serve Rails through Puma as specified in the boxfile.yml above. Add the following nginx.conf and puma.rb into the config directory in your project. Rails may have already generated apuma.rb. If so, you don't need to replace it.

# Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers a minimum and maximum.
# Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum, this matches the default thread size of Active Record.
#
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
threads threads_count, threads_count
# Specifies the `port` that Puma will listen on to receive requests, default is 3000.
#
port ENV.fetch("PORT") { 3000 }
# Specifies the `environment` that Puma will run in.
#
environment ENV.fetch("RAILS_ENV") { "development" }
# Specifies the number of `workers` to boot in clustered mode.
# Workers are forked webserver processes. If using threads and workers together
# the concurrency of the application would be max `threads` * `workers`.
# Workers do not work on JRuby or Windows (both of which do not support
# processes).
#
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }
# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory. If you use this option
# you need to make sure to reconnect any threads in the `on_worker_boot`
# block.
#
# preload_app!
# The code in the `on_worker_boot` will be called if you are using
# clustered mode by specifying a number of `workers`. After each worker
# process is booted this block will be run, if you are using `preload_app!`
# option you will want to use this block to reconnect to any threads
# or connections that may have been created at application boot, Ruby
# cannot share connections between processes.
#
# on_worker_boot do
# ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
# end
# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart

Add a Database Setup or Migrate Rake Task

The boxfile.yml above includes a rake db:setup_or_migrate deploy hook that is designed to check the status of your database. If it doesn't exist yet, it'll run the db:setup task. If it does, it'll run the db:migrate task.

Add this task to lib/tasks/db.rake:

namespace :db do
desc 'Setup the db or migrate depending on state of db'
task setup_or_migrate: :environment do
begin
if ActiveRecord::Migrator.current_version.zero?
Rake::Task['db:migrate'].invoke
Rake::Task['db:seed'].invoke
end
rescue ActiveRecord::NoDatabaseError
Rake::Task['db:setup'].invoke
else
Rake::Task['db:migrate'].invoke
end
end
end

The purpose of this rake task is to make your app as portable as possible across different environments without overwriting existing data, or starting rails with an un-seeded database.

Alright! Now to the fun part!

Setup Your Azure Account

If you haven't already, create a Microsoft Azure account. In your Azure Portal, click on "More Services" at the bottom of the left nav and filter for "Subscriptions". Click on your Subscription ID.

Register Resource Providers

Inside your Subscription ID options, filter for and select "Resource providers". Register "Microsoft.Compute", "Microsoft.Network", and "Microsoft.Storage" by clicking on the "Register" button to the right of each option. These are the resources necessary for Nanobox to build and provision your application on Azure.

Get Your Active Directory ID

In the left nav, select "Azure Active Directory" and go to "Properties". Copy the "Directory ID".

Launch a New App

Go to the home page of your Nanobox dashboard and click the "Launch New App" button. Select your Azure provider from the dropdown and choose the region in which you'd like to deploy your app.

Confirm and click "Let's Go!" Nanobox will order an server on Azure under your account. When the server is up, Nanobox will provision platform components necessary for your app to run:

Load-Balancer: The public endpoint for your application. Routes and load-balances requests to web nodes.

Monitor: Monitors the health of your server(s) and application components.

Logger: Streams and stores your app's aggregated log stream.

Message Bus: Sends app information to the Nanobox dashboard.

Warehouse: Storage used for deploy packages, backups, etc.

Once all the platform components are provisioned and running, you're ready to deploy your app.

Stage Your App Locally

Nanobox provides "dry-run" functionality that simulates a full production deploy on your local machine. This step is optional, but recommended. If the app deploys successfully in a dry-run environment, it will work when deployed to your live environment.

Deploy

Add Your New App as a Remote

From the root of your project directory, add your newly created app as a remote.

nanobox remote add app-name

This connects your local codebase to your live app. More information about theremotecommand is available in the Nanobox Documentation.

Deploy to Your Live App

With your app added as a remote, you're ready to deploy.

nanobox deploy

Nanobox will compile and package your application code, send it up to your live app, provision all your app's components inside your live server, network everything together, and voila! Your app will be live.

Manage & Scale

Once your app is deployed, Nanobox makes it easy to manage and scale your production infrastructure. In your Nanobox dashboard you'll find health metrics for all your app's servers/containers. Your application logs are streamed in your dashboard and can be streamed using the Nanobox CLI.

Although every app starts out on a single server with containerized components, you can break components out into individual servers and/or scalable clusters through the Nanobox dashboard. Nanobox handles the deep DevOps stuff so you don't have to. Enjoy!

Additional Links

FAQ

Why 0.0.0.0?

Nanobox uses Docker to containerize your application within its own private network. Using 127.0.0.1 or localhost (the "loopback" IP) inside of a container will loopback to the container, not the host machine. In order for requests to reach your application, your application needs to listen on all available IP's.

Why Port 8080?

Nanobox provides your application with a router that directs traffic through a private network created for your app. The router listens on ports 80 and 443, terminates SSL, and forwards all requests to port 8080.

Note: Your app/framework can listen on a port other than 8080, but you will need to implement a proxy that listens on 8080 and forwards to your custom port.

Due to the dynamic nature of containerized applications, it's hard to predict the host IP of running services. These IP's are subject to change as your infrastructure changes. When creating your infrastructure, Nanobox knows what these values are and creates evars for necessary connection details.