# Rails 5 and 6 Deployment with Ubuntu, Mina, Puma and Nginx Based on this [tutorial](https://www.ralfebert.de/tutorials/rails-deployment/) 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 ``` 2. Update all packages and reboot: ``` apt-get update && apt-get upgrade && apt-get autoremove && reboot ``` 3. Disable SSH password authentication: ``` nano /etc/ssh/sshd_config && service ssh reload ``` Change these: ``` ... PasswordAuthentication no ... UsePAM no ... ``` 4. Check the open ports (should be only SSH): ``` netstat --listening --tcp ``` 5. 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 ``` 2. 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 ``` 3. 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 ``` 2. 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 ``` 3. Log-out and log-in as the app user: ``` ssh rails-demo@serverip ``` 4. Generate a SSH key pair without password as deployment key: ``` ssh-keygen && cat ~/.ssh/id_rsa.pub ``` 5. 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 ``` 2. Logout, and login as deploy user to install [rbenv](https://github.com/rbenv/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 ``` 3. Log-out and log-in to enable rbenv. 4. 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 ``` 2. 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 ``` 3. Let mina create the app folder structure on the server: ``` mina setup ``` 4. 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 ``` 5. **Rails 6**: Upload `master.key` from your `config` into `shared/config/` in remote server either by FTP or SSH. 6. 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 ``` 2. Disable the default page: ``` rm /etc/nginx/sites-enabled/default ``` 3. 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; } ``` 4. Enable the site configuration: ``` ln -s /etc/nginx/sites-available/rails-demo /etc/nginx/sites-enabled/rails-demo ``` 5. Reload nginx if the nginx configuration is ok: ``` nginx -t && service nginx reload ``` 6. Enable port 80 in the firewall: ``` ufw allow 80 ``` 7. 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 ``` 2. Make a visible change in the app, commit the change and re-deploy: ``` mina deploy ``` ### Configure SSL with certbot *Coming soon*