Deploy instructions for Rails using Puma, Nginx, and Ubuntu 18.04.
Sign up at www.atlantic.net (Select the Ubuntu 18.04 LTS operating system option). I was grandfathered in on Atlantic.net's $1 a month virtual private server (VPS) hosting. Unlike Heroku, you will have full control over the server. At some point in your application's lifetime this may become important to you for making customizations Heroku isn't capable of. Unless future limitations aren't a concern AND you don't want to learn how to set up a virtual private server, I wouldn't suggest using Heroku. The setup process below would be very similar for any of the other big hosting options like Amazon's EC2, DigitalOcean, etc.
Confirmation email will have log in instructions with IP, password and username.
Some of the commands below are run on the remote machine and others on your local development machine. I will prefix each command with one of the following:
remote-root#
remote$
your-machine$
Command prompts with a # indicate commands to be run by the root user and commands with a $ indicate commands to be run with an ordinary, non-root user. You don't type this part, just the command that comes after it. You will usually need to add sudo to the begining of commands having a prompt with a #. The only exception is an example where we temporarily login as a root user via ssh.
Commands below that are supposed to be run as root should be prefixed with sudo (i.e. sudo apt-get install git
instead of apt-get install git
) if your prompt ends with a $ instead of a #.
At first we will log in as the root user and your prompt on the server will end with a #. Later, we will log in as an ordinary user and your prompt on the server will have a $ on the end.
In the examples below, replace with the ip address that you were sent in the email (something like: 123.123.123.123) and replace <username> with the username that you would like to use. I suggest a lower case, single word user name like bob, joe, or perhaps even myappname.
Log in with the credentials provided in the email:
your-machine$ ssh root@<ip address>
We are now logged in as the root user on the remote machine.
Since we want to host our rails app here, we don't want to host it using the root user. If the web app had a security flaw allowing shell access, running as root would open up even more vulnerabilities (like being able to install software without a password). We need to make a new user.
First, let's make an admin group:
remote-root# groupadd admin
Let's create a new user:
remote-root# useradd -G admin <username>
admin here refers to the admin group. We are creating the new user and immediately adding it to the admin group. By default, Ubuntu is set up to allow any users in the admin group to have root privileges (when using sudo and the user's password).
Next, change the password of our new user:
remote-root# passwd <username>
Make a home directory for our new user:
remote-root# mkdir /home/<username>
Set that home directory's permissions:
remote-root# chown <username>:<username> /home/<username>
Copy your public key from your computer to the server (to authenticate securely without needing a password). ssh-copy-id is generally only available if your computer is running Linux.
your-machine$ ssh-copy-id <username>@<ip address>
As an alternative to the previous command (e.g. if you have a mac), and you happen to have a github account with the public key that you want to authenticate with on your server, you can do the following instead:
remote$ mkdir -p ~/.ssh
remote$ cd ~/.ssh
remote$ wget 'https://github.com/<github username>.keys'
remote$ cat <github username>.keys >> authorized_keys
You can now log in to the server with your normal user:
your-machine$ ssh <username>@<ip address>
You shouldn't need to enter a password this time
Install ufw (uncomplicated firewall), postgres, git, build-essential (for compiling ruby), and nodejs (for JavaScript runtime), and nginx:
remote-root# apt-get install postgresql libpq-dev git ufw build-essential nodejs nginx
Enable firewall (make sure to allow port 22 for ssh before enabling the firewall or you'll be locked out):
remote-root# ufw allow 22
remote-root# ufw allow 80
remote-root# ufw allow 443
remote-root# ufw enable
Restart the server for the firewall setting to take effect.
remote-root# reboot
Set bash as the default shell for our new user:
remote-root# chsh -s /bin/bash <username>
Add these lines to the top of /etc/ssh/sshd_config (with vim for example). Comment out or delete any other lines with PermitRootLogin or PasswordAuthentication settings:
PermitRootLogin no
PasswordAuthentication no
AllowUsers <username>
Restart ssh for the settings to take effect:
remote-root# service ssh restart
Update the installed packages:
remote-root# apt-get update
remote-root# apt-get upgrade
Old Note: In the middle of the upgrade process, I was presented with a dialog stating that "The GRUB boot loader was previously installed to a disk that is no longer present..." asking which device I would like to install the boot loader to. I installed to /dev/sda (which generally refers to a hard drive) instead of /dev/sda1 (a partition on that hard same drive). Apparently it's a "BAD" idea to install to the partition (http://askubuntu.com/a/19705/159696).
Install ruby-install for easy ruby installations:
remote$ dir=/tmp/ruby-install
remote$ tarball=/tmp/ruby-install.tar.gz
remote$ wget -O $tarball https://api.github.com/repos/postmodern/ruby-install/tarball
remote$ mkdir $dir
remote$ tar -C $dir -xvzf $tarball --strip-components 1
remote$ cd $dir
remote-root# make install
remote$ ruby-install ruby
Install ruby (rdoc parsing was causing compilation to fail on my tiny instance so disabled it to proceed):
remote$ ruby-install ruby <optional_version_number> -- --disable-install-rdoc
Note: The first time I ran the previous command to install ruby, the compilation failed with a message saying "gcc: internal compiler error". This was due to insufficient memory (my server had 256 megs). The solution to this was creating a swap file so that the extra memory needed could be stored on disk during the installation. First create a big file full of zeros:
remote$ dd if=/dev/zero of=~/swapfile bs=1024 count=1024000
Then turn that file into a proper swap file:
remote$ mkswap ~/swapfile
Then enable the file for swapping:
remote-root# swapon ~/swapfile
You can check to make sure you have swappable memory with this command:
remote$ free
After you are done installing ruby, you can turn the swap off:
remote-root# swapoff ~/swapfile
Then overwrite any data that was put into the swapfile (it could possibly contain sensitive info like passwords that were stored in memory):
remote$ shred ~/swapfile
And delete it:
remote$ rm ~/swapfile
Add ruby bin directory to $PATH variable:
echo "PATH=/home/deploy/.rubies/ruby-<ruby_version>/bin:$PATH" >> ~/.bash_profile
Clone app (in home directory):
remote$ git clone <url>
Install gem dependencies:
cd <app_directory>
gem install bundler
bundle --without development test
Copy up master key from dev repo to production
your-machine$ rsync -av config/master.key <user>@<domain_or_ip>:/home/<username>/<app_name>/config
Create Database YAML File:
cp config/database.yml.example config/database.yml
Edit database.yml:
vim config/database.yml
Replace the contents of config/database.yml with this (replace app name)
production:
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
database: <app_name>_production
Create database user. We will be using postgres's peer authentication which basically means that the database user corresponds to the operatinng system user.
remote$ sudo -u postgres createuser -s <os_username>
Create Database and Load Schema:
remote$ cd ~/<app_dir>
remote$ rails db:create RAILS_ENV=production
Only load schema for new apps (not ones with an existing database)
remote$ rails db:schema:load RAILS_ENV=production
For apps with existing databases copy the database to the server and load it like so (assuming a gziped file):
gunzip < database.sql.gz | psql <app_name>_production"
Precompile assets:
RAILS_ENV=production rails assets:precompile
Note: compiling the assets may require the swapfile turned on as well.
At this point, you can test the rails app by running:
sudo ufw allow <port>
RAILS_ENV=production rails s --binding=<ip_address>:<port>
Test it out and then disable that particular port number:
sudo ufw deny 3000
Register domain at namecheap.com. Go to "Manage Domains", select example.com, select "Advanced DNS". Now we will modify our dns settings so that example.com properly refers to our ip address. Fill out the last two column just like they are shown below (replacing with your corresponding ip address):
Host Name IP Address/URL Record Type
@ <ip address> A (address)
www http://example.com URL Redirect (301)
Replace the content of /etc/nginx/sites-available/default
with:
server {
listen 80;
listen [::]:80;
server_name example.com 69.28.67.250;
root /home/deploy/ethnic_la/public;
location / {
try_files $uri @<app_name>;
}
location @<app_nam> {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Then symlink it as the default server:
remote-root# ln -sf /etc/nginx/sites-available/<app_name> /etc/nginx/sites-enabled/default
restart nginx:
remote-root# service nginx restart
Nginx note. This command is helpful when having trouble getting nginx to start properly:
remote-root# nginx -t
Setup ssl: (instructions came from here: https://certbot.eff.org/lets-encrypt/ubuntuxenial-nginx)
remote-root# apt-get update
remote-root# apt-get install software-properties-common
remote-root# add-apt-repository ppa:certbot/certbot
remote-root# apt-get update
remote-root# apt-get install python-certbot-nginx
remote-root# certbot --nginx
How to start puma?