Skip to content

Instantly share code, notes, and snippets.

@wafiq
Last active November 27, 2023 02:43
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 10 You must be signed in to fork a gist
  • Save wafiq/48b173c71b2c8711cb8c462396dd6baf to your computer and use it in GitHub Desktop.
Save wafiq/48b173c71b2c8711cb8c462396dd6baf to your computer and use it in GitHub Desktop.
How to deploy Rails 5/6 in Ubuntu VM using Mina deployment with Puma webserver and Nginx

Rails 5 and 6 Deployment with Ubuntu, Mina, Puma and Nginx

Based on this tutorial but simplified and inlined. Particularly removed any Rails and 3rd party services part, assumed you just need deployment to any Ubuntu machine.

Prerequisite

  1. A functional Rails app
  2. Hosted Git repository (Github, Bitbucket, Gitlab)
  3. Cloud hosting account (Digital Ocean, Vultr, Linode, Lightsail)
  4. Local SSH account

Outline

  1. Setup Ubuntu system
  2. Install database
  3. Create deploy user
  4. Prepare ruby environment
  5. Setup mina deployment in Rails
  6. Run app server
  7. Configure nginx webserver
  8. BONUS: Configure SSL with certbot

Ubuntu system setup

  1. Set the timezone:
dpkg-reconfigure tzdata
  1. Update all packages and reboot:
apt-get update && apt-get upgrade && apt-get autoremove && reboot
  1. Disable SSH password authentication:
nano /etc/ssh/sshd_config && service ssh reload

Change these:

...
PasswordAuthentication no
...
UsePAM no
...
  1. Check the open ports (should be only SSH):
netstat --listening --tcp
  1. Enable the Ubuntu firewall so that unconfigured services will not be exposed:
ufw allow 22 && ufw logging off && ufw enable && ufw status

The firewall rules are automatically saved and restored on reboot.

PostgreSQL setup

  1. Install PostgreSQL:
apt-get install postgresql postgresql-contrib libpq-dev
  1. Edit the configuration and remove the two lines starting with “host ...” that make PostgreSQL listen on a localhost port - a local socket connection is sufficient for Rails:
nano /etc/postgresql/10/main/pg_hba.conf && service postgresql restart
  1. Create a user and a database for the application:
sudo -u postgres createuser rails-demo
sudo -u postgres createdb rails-demo --owner=rails-demo

(Ignore the “could not change directory to "/root": Permission denied” warnings)

Create deploy user account

  1. Create a user for the app:
APP_NAME=rails-demo
adduser $APP_NAME --disabled-password
  1. Copy your SSH public key to the user home so you can log-in as the app user, for example:
mkdir /home/$APP_NAME/.ssh
cp ~/.ssh/authorized_keys /home/$APP_NAME/.ssh/
chown $APP_NAME.$APP_NAME /home/$APP_NAME/.ssh -R
chmod go-rwx /home/$APP_NAME/.ssh -R
  1. Log-out and log-in as the app user:
ssh rails-demo@serverip
  1. Generate a SSH key pair without password as deployment key:
ssh-keygen && cat ~/.ssh/id_rsa.pub
  1. Add the deployment key to repository.

Ruby on Rails setup

  1. Login back as root user in your Ubuntu machine, Install git, nodejs, rng-tools and ruby build dependencies. Install yarn pakage manager for assets compilation:
curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo add-apt-repository ppa:chris-lea/redis-server
sudo apt-get update
sudo apt-get install git-core curl 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 dirmngr gnupg apt-transport-https ca-certificates redis-server redis-tools nodejs yarn

apt update && apt install yarn
  1. Logout, and login as deploy user to install rbenv
cd
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
exec $SHELL

git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bashrc
exec $SHELL

  1. Log-out and log-in to enable rbenv.

  2. Install Ruby and the bundler gem:

rbenv install 2.6.6
rbenv global 2.6.6
ruby -v

gem update --system
gem install bundler

Setting up deployment with Mina

  1. On your local machine, add Mina, and Mina Puma to your Gemfile:
gem 'mina', require: false
gem 'mina-puma', require: false
  1. Add a config/deploy.rb configuration file to the Rails project:
require 'mina/rails'
require 'mina/git'
require 'mina/rbenv'
require 'mina/puma'

set :application_name, 'appname'
set :domain, '000.000.000.000'
set :user, fetch(:application_name)
set :deploy_to, "/home/#{fetch(:user)}/app"
set :repository, 'git@gitlab.com:user/appname.git'
set :branch, 'master'

set :shared_dirs, fetch(:shared_dirs, []).push('log', 'storage', 'tmp/pids', 'tmp/sockets')
set :shared_files, fetch(:shared_files, []).push('config/database.yml', 'config/puma.rb', 'config/master.key')

task :remote_environment do
  invoke :'rbenv:load'
end

task :setup do
  
  in_path(fetch(:shared_path)) do
    command %[mkdir -p config]
    command %[touch "#{fetch(:shared_path)}/config/database.yml"]
    command %[touch "#{fetch(:shared_path)}/config/puma.rb"]
    command %[chmod -R o-rwx config]
  end

end

desc "Deploys the current version to the server."
task :deploy do
  invoke :'git:ensure_pushed'
  deploy do
    invoke :'git:clone'
    invoke :'deploy:link_shared_paths'
    invoke :'bundle:install'
    invoke :'rails:db_migrate'
    invoke :'rails:assets_precompile'
    invoke :'deploy:cleanup'

    on :launch do
      # invoke :'puma:restart'
    end
  end
end

  1. Let mina create the app folder structure on the server:
mina setup
  1. On the server, if needed, edit the created configuration files:
nano app/shared/config/database.yml
nano app/shared/config/puma.rb

Sample of database.yml content:

production:
  database: rails-demo
  adapter: postgresql
  pool: 5
  timeout: 5000

Sample of puma.rb content:

environment "production"

bind "unix:/home/rails-demo/app/shared/tmp/sockets/puma.sock"
pidfile "/home/rails-demo/app/shared/tmp/pids/puma.pid"
state_path "/home/rails-demo/app/shared/tmp/sockets/puma.state"
directory "/home/rails-demo/app/current"

workers 2
threads 1,4

daemonize true

stdout_redirect "/home/rails-demo/app/shared/log/puma.stdout.log", "/home/rails-demo/app/shared/log/puma.stderr.log"

activate_control_app 'unix:/home/rails-demo/app/shared/tmp/sockets/pumactl.sock'

prune_bundler
  1. Rails 6: Upload master.key from your config into shared/config/ in remote server either by FTP or SSH.

  2. To deploy the app to the server, run locally:

mina deploy

Running app server

Run from your local machine (not server):

$ mina puma:start
$ mina puma:status
$ mina puma:restart
$ mina puma:stop

Web server setup

The puma server is not made to serve HTTP requests directly, so let’s put a nginx web server in front of it:

  1. Login to server using root access to install nginx:
apt-get install nginx
  1. Disable the default page:
rm /etc/nginx/sites-enabled/default
  1. Create a nginx site for the application:
nano /etc/nginx/sites-available/rails-demo

Example configuration:


upstream rails-demo {
	server unix:/home/rails-demo/app/shared/tmp/sockets/puma.sock fail_timeout=0;
}

server {
	listen 80;
	server_name example.com;

	root /home/rails-demo/app/current/public;
	
	location ~ ^/(assets)/  {
		gzip_static on; # to serve pre-gzipped version
		expires max;
		add_header Cache-Control public;
	}

	location / {
		try_files $uri @app;
	}

	location @app {
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header X-Forwarded-Proto $scheme;
		proxy_set_header Host $http_host;
		proxy_redirect off;
		proxy_pass http://rails-demo;
	}
}

server {
	listen 80;
	server_name www.example.com;
	return 301 http://example.com$request_uri;
}
  1. Enable the site configuration:
ln -s /etc/nginx/sites-available/rails-demo /etc/nginx/sites-enabled/rails-demo
  1. Reload nginx if the nginx configuration is ok:
nginx -t && service nginx reload
  1. Enable port 80 in the firewall:
ufw allow 80
  1. Check if the application responds as expected, check the log files otherwise:
tail /var/log/nginx/error.log /home/rails-demo/app/shared/log/*

Re-deploying the app

  1. Edit config/deploy.rb in the Rails application and enable the service restart command:
on :launch do
  invoke :'puma:restart'
end
  1. Make a visible change in the app, commit the change and re-deploy:
mina deploy

Configure SSL with certbot

Coming soon

@juansecaro
Copy link

Amazing resource! Thank you
Looking forward for the SSL part ;)

@sebackend
Copy link

I have a problem trying to start puma, on the nginx/error.log i got this message:

shared/tmp/sockets/puma.sock failed (2: No such file or directory) while connecting to upstream

For some reason, puma.sock was not created and i dont know how to make this work, any ideas?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment