Overview
This will take about 45 minutes.
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.
Creating A Virtual Private Server
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.
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.
The next step is to choose your location. Choose one close to you so that you can have better connection speeds.
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!
Installing Ruby
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.4
asdf global ruby 3.3.4
# 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.4
Then we can install the latest Node.js for handling Javascript in our Rails apps:
asdf install nodejs 20.17.0
asdf global nodejs 20.17.0
which node
#=> /home/username/.asdf/shims/node
node -v
#=> 20.17.0
# Install yarn for Rails jsbundling/cssbundling or webpacker
npm install -g yarn
Installing Nginx
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.
MySQL and PostgreSQL Database Setup
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.
Capistrano Setup
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.4'
# If you are using rvm add these lines:
# require 'capistrano/rvm'
# set :rvm_type, :user
# set :rvm_ruby_version, '3.3.4'
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.
Next Steps
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!