Deploy Ruby On Rails on
Ubuntu 16.04 Xenial Xerus

A guide to setting up a Ruby on Rails production environment

This will take about 45 minutes.

Skip all this work and let Hatchbox configure your servers and deploy Rails apps to it.

Check out Hatchbox!

We will be setting up a Ruby on Rails production environment on Ubuntu 16.04 LTS Xenial Xerus.

Since we setup Ubuntu for our development environment, we also want to use it in production. This keeps your application running consistently between development and production. We're using an LTS version of Ubuntu in production because it is supported for several years where a normal version of Ubuntu isn't.

Using Ubuntu LTS in production allows you to continue receiving security updates which is important for your production server(s).

We're going to be setting up a Droplet on Digital Ocean for our server. It costs $5/mo and is a great place to host your applications.

If you sign up with my Digital Ocean referral link, you'll get 2 months ($10) free credit to try it out.

You can use any cloud server hosting company you choose for your Rails application. I've had excellent experience with Digital Ocean and Linode with the servers I have used. If you're looking for alternatives outside the US or otherwise, just google "VPS hosting". A VPS is a virtual private server. It's just like a server you setup at home, only virtualize and running with a suite of other servers in a datacenter.

First, we need to choose which OS to use. We're going to be using Ubuntu 16.04 LTS x64. Your application may require a different OS or version, but if you're not sure this is generally what you should use.

Droplet Image

Since we're using Digital Ocean for our cloud server, the first thing we're going to do is configure a new one. I'm going with the Droplet with 1GB of RAM. You can setup whichever size server you prefer, keep in mind that if you choose a 512MB server you may run into some slowness with a low amount of RAM.

Droplet Size

The next step is to choose your location. Choose one close to you so that you can have better connection speeds.

Droplet Region

Optionally you can add your SSH key into the Droplet so you can SSH in and skip the ssh-copy-id step.

Once Digital Ocean has configured your server, check your email to get your password for the new cloud server.

You should follow the instructions in the email to login via SSH for the very first time and verify it is working.

The first thing we will do on our new server is create the user account we'll be using to run our applications and work from there.

sudo adduser deploy
    sudo adduser deploy sudo
    su deploy

Before we move forward is that we're going to setup SSH to authenticate via keys instead of having to use a password to login. It's more secure and will save you time in the long run.

We're going to use ssh-copy-id to do this. If you're on macOS you may need to run brew install ssh-copy-id but if you're following this tutorial on Linux desktop, you should already have it.

Once you've got ssh-copy-id installed, run the following and replace IPADDRESS with the one for your server:

Make sure you run ssh-copy-id on your computer, and NOT the server.

ssh-copy-id deploy@IPADDRESS

Now when you run ssh deploy@IPADDRESS you will be logged in automatically. Go ahead and SSH again and verify that it doesn't ask for your password before moving onto the next step.

For the next steps, make sure you are logged in as the deploy user on the server!

The first step is to install dependencies for compiling Ruby. Open your Terminal and run the following commands to install them.

sudo apt-get update
sudo apt-get install git-core zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev software-properties-common libffi-dev

Next we're going to be installing Ruby using a version manager called ASDF.

The reason we use ASDF over rbenv, rvm or others is that ASDF can manage other languages like Node.js too.

Installing asdf is a simple two step process. First you install asdf, and then add it to your shell:

cd
git clone https://github.com/excid3/asdf.git ~/.asdf
echo '. "$HOME/.asdf/asdf.sh"' >> ~/.bashrc
echo '. "$HOME/.asdf/completions/asdf.bash"' >> ~/.bashrc
echo 'legacy_version_file = yes' >> ~/.asdfrc
echo 'export EDITOR="code --wait"' >> ~/.bashrc
exec $SHELL

Then we can install ASDF plugins for each language we want to use. For Rails, we can install Ruby and Node.js for our frontend Javascript.

asdf plugin add ruby
asdf plugin add nodejs

Choose the version of Ruby you want to install:

To install Ruby and set the default version, we'll run the following commands:

asdf install ruby 3.3.0
asdf global ruby 3.3.0

# Update to the latest Rubygems version
gem update --system

Confirm the default Ruby version matches the version you just installed.

which ruby
#=> /home/username/.asdf/shims/ruby
ruby -v
#=> 3.3.0

Then we can install the latest Node.js for handling Javascript in our Rails apps:

asdf install nodejs 20.11.0
asdf global nodejs 20.11.0

which node
#=> /home/username/.asdf/shims/node
node -v
#=> 20.11.0

# Install yarn for Rails jsbundling/cssbundling or webpacker
npm install -g yarn

Phusion is the company that develops Passenger and they recently put out an official Ubuntu package that ships with Nginx and Passenger pre-installed.

We'll be using that to setup our production server because it's very easy to setup.

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7
    sudo apt-get install -y apt-transport-https ca-certificates

    # Add Passenger APT repository
    sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger xenial main > /etc/apt/sources.list.d/passenger.list'
    sudo apt-get update

    # Install Passenger & Nginx
    sudo apt-get install -y nginx-extras passenger

So now we have Nginx and passenger installed. We can manage the Nginx webserver by using the service command:

sudo service nginx start

Open up the server's IP address in your browser to make sure that nginx is up and running.

The service command also provides some other methods such as restart and stop that allow you to easily restart and stop your webserver.

Next, we need to update the Nginx configuration to point Passenger to the version of Ruby that we're using. You'll want to open up /etc/nginx/nginx.conf in your favorite editor. I like to use vim, so I'd run this command:

sudo vim /etc/nginx/nginx.conf

    # You could also use nano if you don't like vim
    # sudo nano /etc/nginx/nginx.conf

Find the following lines, and uncomment them:

##
    # Phusion Passenger
    ##
    # Uncomment it if you installed ruby-passenger or ruby-passenger-enterprise
    ##

    include /etc/nginx/passenger.conf;

Save and close nginx.conf. Then open /etc/nginx/passenger.conf in your editor to modify the ruby line:

sudo vim /etc/nginx/passenger.conf

    # You could also use nano if you don't like vim
    # sudo nano /etc/nginx/passenger.conf

And change the passenger_ruby line to point to your ruby executable:

passenger_ruby /home/deploy/.rbenv/shims/ruby; # If you use rbenv
    # passenger_ruby /home/deploy/.rvm/wrappers/ruby-2.1.2/ruby; # If use use rvm, be sure to change the version number
    # passenger_ruby /usr/bin/ruby; # If you use ruby from source

The passenger_ruby is the important line here. Make sure you only set this once and use the line from the example that pertains to the version of Ruby you installed.

Once you've changed passenger_ruby to use the right version Ruby, you can run the following command to restart Nginx with the new Passenger configuration.

sudo service nginx restart

Now that we've restarted Nginx, the Rails application will be served up using the deploy user just how we want. In the Capistrano section we will talk about configuring Nginx to serve up your Rails application.

Setting up your production database is pretty easy. Make sure to keep in mind that you should use a different password for your production databases.

Depending on what database you want to use, follow the steps related to the database:

Installing MySQL

All you need to do in order to install MySQL is to run the following command:

sudo apt-get install mysql-server mysql-client libmysqlclient-dev

You can use the root user and password set during installation for your database or you can add a new user to MySQL.

Make sure you also create your app's database now. If you're not sure how to do this, follow this guide to create your mysql database. Take note of your database name and password so you can use this when we setup the database.yml file later on.

Installing PostgreSQL

Postgres 9.3 is available in the Ubuntu repositories and we can install it like so:

sudo apt-get install postgresql postgresql-contrib libpq-dev

Next we need to setup our postgres user (also named "deploy" but different from our linux user named "deploy") and database:

sudo su - postgres
    createuser --pwprompt deploy
    createdb -O deploy my_app_name_production # change "my_app_name" to your app's name which we'll also use later on
    exit

The password you type in here will be the one to put in your my_app/current/config/database.yml later when you deploy your app for the first time.

For Capistrano, make sure you do these steps on your development machine inside your Rails app.

The first step is to add Capistrano to your Gemfile:

gem 'capistrano', '~> 3.7', '>= 3.7.1'
    gem 'capistrano-rails', '~> 1.2'
    gem 'capistrano-passenger', '~> 0.2.0'

    # Add this if you're using rbenv
    # gem 'capistrano-rbenv', '~> 2.1'

    # Add this if you're using rvm
    # gem 'capistrano-rvm'

Once these are added, run the following command to generate your capistrano configuration.

cap install STAGES=production

Next we need to make some additions to our Capfile to include bundler, rails, and rbenv/rvm (if you're using them). Edit your Capfile and add these lines:

require 'capistrano/rails'
    require 'capistrano/passenger'

    # If you are using rbenv add these lines:
    # require 'capistrano/rbenv'
    # set :rbenv_type, :user
    # set :rbenv_ruby, '3.3.0'

    # If you are using rvm add these lines:
    # require 'capistrano/rvm'
    # set :rvm_type, :user
    # set :rvm_ruby_version, '3.3.0'

After we've got Capistrano installed, we can configure the config/deploy.rb to setup our general configuration for our app. Edit that file and make it like the following replacing "myapp" with the name of your application and git repository:

set :application, "my_app_name"
    set :repo_url, "git@example.com:me/my_repo.git"

    set :deploy_to, '/home/deploy/my_app_name'

    append :linked_files, "config/database.yml", "config/secrets.yml"
    append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "vendor/bundle", "public/system", "public/uploads"

Now we need to open up our config/deploy/production.rb file to set the server IP address that we want to deploy to:

# Replace 127.0.0.1 with your server's IP address!
    server '127.0.0.1', user: 'deploy', roles: %w{app db web}

If you have any trouble with Capistrano or the extensions for it, check out Capistrano's Github page.

Thankfully there aren't a whole lot of things to do left!

Adding The Nginx Host

In order to get Nginx to respond with the Rails app, we need to modify it's sites-enabled.

Open up /etc/nginx/sites-enabled/default in your text editor and we will replace the file's contents with the following:

server {
            listen 80;
            listen [::]:80 ipv6only=on;

            server_name mydomain.com;
            passenger_enabled on;
            rails_env    production;
            root         /home/deploy/my_app_name/current/public;

            # redirect server error pages to the static page /50x.html
            error_page   500 502 503 504  /50x.html;
            location = /50x.html {
                root   html;
            }
    }

This is our Nginx configuration for a server listening on port 80. You need to change the server_name values to match the domain you want to use and in root replace "myapp" with the name of your application.

Connecting The Database

One optional thing I would recommend is to remove your config/database.yml and config/secrets.yml git and only store example copies in your git repo. This way we can easily copy the files for setting up development, but our production environment can symlink files on the server so that our production secrets and passwords are only stored on the production server.

First we'll move these files to their example names in the git repo.

echo "config/database.yml\nconfig/secrets.yml" >> .gitignore
    git add .gitignore
    git mv config/secrets.yml config/secrets.yml.example
    git mv config/database.yml config/database.yml.example
    git commit -m "Only store example secrets and database configs"
    cp config/secrets.yml.example config/secrets.yml
    cp config/database.yml.example config/database.yml

You can run cap production deploy to deploy your application, but it's going to fail this first time because we haven't created either of these files on the server which we will do in just a second.

linked file /home/deploy/build.gorails.com/shared/config/database.yml does not exist on IP_ADDRESS

One last time, ssh into your server as the deploy user and this time we need to create two files. First is the database.yml that uses the password for the postgres user you created earlier.

# /home/deploy/my_app_name/shared/config/database.yml
    production:
      adapter: postgresql
      host: 127.0.0.1
      database: my_app_name_production
      username: deploy
      password: YOUR_POSTGRES_PASSWORD
      encoding: unicode
      pool: 5

Now we create secrets.yml. We need to run rake secret on your development machine and take the secret key output of that and paste that in as YOUR_SECRET_KEY on the server.

# /home/deploy/my_app_name/shared/config/secrets.yml
    production:
      secret_key_base: YOUR_SECRET_KEY

You can run cap production deploy one last time to get your full deployment to run. This should completed successfully and you should see your new site live! You can just run Capistrano again to deploy any new changes you've pushed up to your Git repository.

Restarting The Site

One last thing you should know is that restarting just the Rails application with Passenger is very easy. If you ssh into the server, you can run touch my_app_name/current/tmp/restart.txt and Passenger will restart the application for you. It monitors the file's timestamp to determine if it should restart the app. This is helpful when you want to restart the app manually without deploying it.

Conclusion

And there you have it, a very long-winded explanation of all the different things you need to do while setting up an application to be deployed. There is a lot of system administration pieces that can expand upon this, but that's for another time. Please let me know if you have any questions, comments, or suggestions!

Want to stay up-to-date with Ruby on Rails?

Join 81,842+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.

    Screencast tutorials to help you learn Ruby on Rails, Javascript, Hotwire, Turbo, Stimulus.js, PostgreSQL, MySQL, Ubuntu, and more.

    © 2024 GoRails, LLC. All rights reserved.