Intro

In this article, we will deploy our Ruby on Rails application on a Ubuntu 18.04 server with Nginx (web server) and Puma (application server). We will also use PostgreSQL as our database and Rbenv to help us store sensitive information. Rbenv helps us store sensitive information (such as database passwords) on the server as environment variables. This way if your project is on Github as a public project, you won’t have to reveal that kind of information to the world.

If you have created a brand new server or VM, remember to install all the dependencies that our project needs. I have created a script to help with this (installing rails, ruby, git, etc) in case you need here: https://raw.githubusercontent.com/mt9304/scripts/master/rails.sh. This script can take a while to complete, but you can download and run this on your new server by running: tent/uploads/nginx-logo-e1548640254225.pngIf you have created a brand new server or VM, remember to install all the dependencies that our project needs. I have created a script to help with this (installing rails, ruby, git, etc) in case you need here: https://raw.githubusercontent.com/mt9304/scripts/master/rails.sh. This script can take a while to complete, but you can download and run this on your new server by running:

sudo apt install curl

curl -o https://raw.githubusercontent.com/mt9304/scripts/master/rails.sh rails.sh
sh rails.sh

Installing Dependencies

After our environment is setup, let’s install more dependencies!

sudo apt update
sudo apt-get install autoconf bison build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm-dev
cd ~/.rbenv/plugins
git clone https://github.com/sstephenson/rbenv-vars.git

Now let’s go back to our home directory and clone our project folder here.

cd ~

(Remember to replace [YOUR_USER] and [YOUR_PROJECT_NAME] with the values that match your own project Github. In my case, it would be: https://github.com/TestingAndLearning/rails_template)

git clone https://github.com/[YOUR_USER]/[YOUR_PROJECT_NAME]

Now change directory into your application’s folder and run:

sudo apt-get install libsqlite3-dev
bundle install

Note:

If you don’t install libsqlite3-dev and your application references sqlite, then you may get an error like:

An error occurred while installing sqlite3 (1.3.13), and Bundler cannot continue.

Make sure that `gem install sqlite3 -v ‘1.3.13’` succeeds before bundling.

Configuration Changes

Now we will go into the /config/database.yml file and make some changes to the Production environment, since this is the environment we will be deploying in (Remember to replace the values in quotes with your own).

adapter: postgresql
encoding: unicode
database: RT_prod
pool: 5
username: <%= ENV['NAME_OF_YOUR_USER'] %>
password: <%= ENV['YOUR_DATA_BASE_PASSWORD'] %>

Your file might look something like this: https://gist.github.com/mt9304/2c8014ffed16adfe072970050f8643ae

These <%= ENV[‘VALUE’] %> tags are the environment variables that Rbenv will use.

Now run the following to get a unique value for your application:

rake secret

Make sure to copy this down somewhere. Now we can create our rbenv file and add these values:

nano .rbenv vars
or any other text editor like vi or vim. Then edit the contents in this file to be something like: 
SECRET_KEY_BASE=YOURRAKESECRETOUTPUT
NAME_OF_YOUR_USER=YOURACTUALUSER
YOUR_DATA_BASE_PASSWORD=YOURACTUALPASSWORD

Remember to replace the values with your own. These should match the ones you entered in the database.yml file. For the user value, I would recommend using the value you see when you run:

echo $USER

This way you can avoid some permission issues with the database in the future.

Git Ignore

Also, if you haven’t already, let’s create a .gitignore file and make sure we ignore this file so that it doesn’t end up in the git repository. In the application folder, create the file by running:

nano .gitignore

Then make sure one of the lines says

.rbenv-vars

Save the file and run the following to make sure the .gitignore works properly.

git rm -r --cached .

Now you can push into your repository and test if the .rbenv-vars file ends up in there.

More Configurations

Run the following command and remember the number it outputs (this gets the number of CPUs available to use in your server):

grep -c processor /proc/cpuinfo

We can now edit our /config/puma.rb file. Remove the contents in there and make sure it looks something like this: https://gist.github.com/mt9304/7a444e84098e49ce5d0f92e263f95e21

Remember to change the YOURCPUOUTPUT value to the number of CPUs we just found.

Now let’s go back to your application’s root directory and run the following to create some required folders:

mkdir -p shared/pids shared/sockets shared/log

For the next bit, we will use some scripts I found on the internet that will allow you to start/stop the Puma server easily. We won’t go into this in too much detail, but feel free to go to https://www.digitalocean.com/community/tutorials/how-to-deploy-a-rails-app-with-puma-and-nginx-on-ubuntu-14-04 for a similar guide. Go back to your home directory and download the scripts by running this:

cd ~
wget https://raw.githubusercontent.com/puma/puma/master/tools/jungle/upstart/puma-manager.conf
wget https://raw.githubusercontent.com/puma/puma/master/tools/jungle/upstart/puma.conf
sudo cp puma.conf puma-manager.conf /etc/init

Make sure to edit the YOURUSER and YOURAPPLICATIONNAME values so that it leads to your application folder.

cat << EOF | sudo tee -a /etc/puma.conf
/home/YOURUSER/YOURAPPLICATIONNAME
EOF

Note:

The “cat” command here helps us write a line, the “tee” command takes the output of what we write and writes it in the /etc/puma.conf file. The -a argument makes sure it appends to the end of the file so that it doesn’t overwrite anything. The “<< EOF” part tells the “cat” command to take anything in the next lines as output for this, until the word EOF is found on its own line. In ths case, it is the path to your application’s directory. Then the EOF at the end is detected and it stops writing as output.

Puma Configuration

For your convenience, you can run the following to set your variables in this session (Make sure to replace appfoldername with your application’s folder name. Also, if you are using the default user, then you don’t have to set this. You can check the values of these by typing echo $USER or echo APP_NAME once you have set them):

USER=ubuntu
APP_NAME=appfoldername

Now we can run the following

cat << EOF | sudo tee /etc/systemd/system/puma.service
   #Modify the path to the app directory and the user and group. 
   [Unit]
   Description=Puma HTTP Server
   After=network.target
   # Uncomment for socket activation (see below)
   # Requires=puma.socket
   [Service]
   # Foreground process (do not use --daemon in ExecStart or config.rb)
   Type=simple
   # Preferably configure a non-privileged user
   User=${USER}
   Group=${USER}
   # Specify the path to your puma application root
   WorkingDirectory=/home/${USER}/${APP_NAME}
   # Helpful for debugging socket activation, etc.
   # Environment=PUMA_DEBUG=1
   # EnvironmentFile=/home/${USER}/${APP_NAME}/.env
   # The command to start Puma
   # ExecStart=/sbin/puma -b tcp://0.0.0.0:9292 -b ssl://0.0.0.0:9293?key=key.pem&cert=cert.pem
   # ExecStart=/usr/local/bin/bundle exec --keep-file-descriptors puma -e production
   # ExecStart=/usr/local/bin/puma -C /home/${USER}/${APP_NAME}/config/puma.rb
   ExecStart=/home/${USER}/.rbenv/shims/bundle exec puma -e production -C ./config/puma.rb config.ru
   PIDFile=/home/${USER}/${APP_NAME}/shared/tmp/pids/puma.pid
   Restart=always
   [Install]
   WantedBy=multi-user.target
EOF

Then restart and enable Puma.

sudo systemctl daemon-reload
sudo systemctl enable puma

Nginx

Almost done, now we can finally install Nginx:

sudo apt-get install nginx

And we can replate some default files with our own by running:

sudo rm /etc/nginx/sites-available/default
cat << EOF | sudo tee /etc/nginx/sites-available/default
   upstream app {
       # Path to Puma SOCK file, as defined previously
       server unix:/home/${USER}/${APP_NAME}/shared/sockets/puma.sock fail_timeout=0;
   }
   server {
       listen 80;
       server_name localhost;
       root /home/${USER}/${APP_NAME}/public;
       try_files $uri/index.html $uri @app;
       location @app {
           proxy_pass http://app;
           proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
           proxy_set_header Host \$http_host;
           proxy_redirect off;
       }
       error_page 500 502 503 504 /500.html;
       client_max_body_size 4G;
       keepalive_timeout 10;
   }
EOF

PostgreSQL Database

Now let’s create the users for our database and give it the Superuser role. In the below steps, we basically change to use the postgres user, then enter the pg console with psql, which allows us to run SQL commands for our database. Make sure to replace the user and password values with your own, and include the semi colon at the end:

sudo -iu postgres
psql
CREATE USER username WITH PASSWORD 'yourpassword';
"ALTER USER username WITH SUPERUSER;
\q
exit

Note:

“\q” exits you from psql and “exit” takes you back into the default user. While in psql you can also type “\l” to list all the databases, “\c databasename” to connect to a database then \dt to list all the tables in that database. You can then run SQL commands such as “SELECT * FROM tablename;” to interact with your database.

The below commands also accomplishes the same thing in case you just want to save time (replace the YOURPASSWORD value with your password):

DB_PASS=YOURPASSWORD
sudo -u postgres bash -c "psql -c \"CREATE USER $USER WITH PASSWORD '$DB_PASS';\""
sudo -u postgres bash -c "psql -c \"ALTER USER $USER WITH SUPERUSER;\""

Now that we have the users avaiable for the database, we can set up the Production environment’s database. Change directory into your application’s folder and run the following (make sure your Gemfile has the pg gem in it. There should be a line that says something like gem ‘pg’, ‘~> 0.18’:

RAILS_ENV=production rake db:drop
RAILS_ENV=production rake db:create
RAILS_ENV=production rake db:migrate
RAILS_ENV=production rake db:seed
RAILS_ENV=production rake assets:precompile

Restarting the Server

Finally, we can restart Puma and Nginx and for the changes to take affect:

sudo systemctl stop puma
sudo systemctl start puma

or

sudo systemctl restart puma
sudo systemctl stop nginx
sudo systemctl start nginx

or

sudo systemctl restart nginx

You can run the following to check the status of these servers to make sure that they are action and running:

sudo systemctl status nginx
sudo systemctl status puma

Some of those scripts that we downloaded and used above allow for Puma to be interacted with the systemctl command.

We can now visit localhost:80 to check the web application. In development, you would usually visit a Rails application through port 3000, however, Nginx makes it possible to connect it through port 80, which is the default port for Production websites. This essentially means you can just type localhost in the URL without the port number and it should take you to the site. This makes it easier to point your domains and such to your server when you decide to go public with your website.

Note:

If you find that your CSS and JavaScript files are not loading, you can open the /config/environments/production.rb file and change the following line to true, then restart Puma.

config.public_file_server.enabled = true

Your website should now be ready to go public!

Navigation

The next article can be found here. Previous article is here.