How to deploy a Rails application with Mina, Nginx, and Puma

What is Mina?
Mina is a deployment tool similar to Capistrano. Its big advantage is speed. Mina is especially fast because it's a deploy Bash script generator: it generates the entire procedure as a Bash script and runs it remotely on the server. It also runs commands intelligently: if you haven't changed your assets (CSS, JS, images), it won't trigger asset precompilation. The same goes for migrations.
Setting up Mina
Let's look at how to set up Mina with Puma. First you need to add Mina and mina-puma to the Gemfile, in the development group. Mina recently changed maintainers; the new repository is here, and it's best to install it from Git.
gem 'mina', require: falsegem 'mina-puma', require: false, github: 'untitledkingdom/mina-puma'
If you're using a Rails version earlier than 5, add the Puma gem to the global section.
Then install the gems and run Mina's initial command to generate the config/deploy.rb file.
bundlemina init
Here's an example of Mina 1.0.0's default configuration. Click here to see the full configuration.
Detailed breakdown of the Mina deploy file
#Set the domain or ip address of the remote server.set :domain, 'yourdomain.ru'#Set the folder of the remote server where Mina will deploy your application.set :deploy_to, 'path/to/directory'#Set a link to the repository. Example: git@bitbucket.org/yourteam/myapp.gitset :repository, 'git@…'#Set the name of a branch you plan to deploy as default master.set :branch, 'master'#Fill in the names of the files and directories that will be symlinks to the shared directory.#All folders will be created automatically on Mina setup.#Don’t forget to add a path to the uploads folder if you are using Dragonfly or CarrierWave.#Otherwise, you will lose your uploads on each deploy.set :shared_dirs, fetch(:shared_dirs, []).push('log', 'tmp/pids', 'tmp/sockets', 'public/uploads')set :shared_files, fetch(:shared_files, []).push('config/database.yml', 'config/secrets.yml', 'config/puma.rb')#Username of ssh user for access to the remote server.set :user, 'username'#This is not a required field. You can use it to set an application name for easy recognition.set :application_name, 'MyApplication'#Set ruby version. If you have RVM installed globally, you’ll also need to set an RVM path,#like: set :rvm_use_path, '/usr/local/rvm/scripts/rvm'.#You can find the RVM location with the rvm info command.task :environment do invoke :'rvm:use', 'ruby-2.4.0@default'end
By default, Mina creates all the folders listed in shared_dirs and shared_files. Even so, in the setup task you can add automatic creation of empty files to fill in later.
task :setup do command %[touch "#{fetch(:shared_path)}/config/database.yml"] command %[touch "#{fetch(:shared_path)}/config/secrets.yml"] command %[touch "#{fetch(:shared_path)}/config/puma.rb"] comment "Be sure to edit '#{fetch(:shared_path)}/config/database.yml', 'secrets.yml' and puma.rb."end
Once that's done, you can run mina setup to prepare the whole file structure needed on the remote server.
The deploy section of your deploy.rb should look like this:
task :deploy do deploy do comment "Deploying #{fetch(:application_name)} to #{fetch(:domain)}:#{fetch(:deploy_to)}" invoke :'git:clone' invoke :'deploy:link_shared_paths' invoke :'rvm:load_env_vars' invoke :'bundle:install' invoke :'rails:db_migrate' command %{#{fetch(:rails)} db:seed} invoke :'rails:assets_precompile' invoke :'deploy:cleanup' on :launch do invoke :'puma:phased_restart' end endend
Setting up Puma
Create or fill in the puma.rb file in the config folder.
environment "production"bind "unix:///{path_to_your_app}/shared/tmp/sockets/puma.sock"pidfile "/{path_to_your_app}/shared/tmp/pids/puma.pid"state_path "/{path_to_your_app}/shared/tmp/sockets/puma.state"directory "/{path_to_your_app}/current"workers 2threads 1,2daemonize trueactivate_control_app 'unix:///{path_to_your_app}/shared/tmp/sockets/pumactl.sock'prune_bundler
You need to provide the path to pumactl.sock in activate_control_app. Otherwise, mina puma:stop and restart won't work.
Fill in database.yml and secrets.yml
Go to the shared folder and make sure to fill in these two files.
Setting up nginx
Create the myapp.conf file in the /nginx/etc/conf.d folder with content similar to this.
upstream mysite { server unix:///home/admin/mysite/shared/tmp/sockets/puma.sock fail_timeout=0;}server { listen 80; listen [::]:80; server_name mysite.com; root /home/admin/mysite/current/public; location ~ ^/assets/ { expires max; gzip_static on; gzip_vary on; add_header Cache-Control public; break; } location / { proxy_pass http://mysite; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location ~ ^/(500|404|422).html { root /home/admin/mysite/current/public; } error_page 500 502 503 504 /500.html; error_page 404 /404.html; error_page 422 /422.html; client_max_body_size 4G; keepalive_timeout 10;}
Generate ssh keys
Generate new ssh keys on your server with the ssh-keygen command. Then add them to your project's deploy keys on GitHub or Bitbucket.
Mission accomplished
That's it. You can now deploy your application. If you've already run mina setup, run mina deploy and wait.
Troubleshooting
If you run into issues with git clone, check that you've added the ssh public key to your repository and try cloning the repository directly from the server.
On the first boot, use mina puma:start instead of phased_restart.