Setting up a new server and deploy Ruby on Rails project with Capistrano on VPS (Digital Ocean/Hetzner/etc)
This use Rails 5 or 6, RVM and Puma.
If you want to deploy with RBenv and Passenger, take a look at Phyl Smy script https://github.com/philsmy/cable-guy-example/blob/main/SetUpServer.txt and his video where he recorded each step https://www.youtube.com/watch?v=CZtYDplotiI&t=1s
If you need to deploy to a EC2 on AWS, check this video of how to create the server first: https://www.youtube.com/watch?v=M0avxObh8J8
This is based on this article: https://medium.com/@sysrex/my-first-10-minutes-on-a-server-d79ea273809b
ssh root@<ip do servidor>'
Add a root password
curl 'https://www.random.org/passwords/?num=2&len=24&format=plain&rnd=new'
passwd
security upgrades later on:
apt-get update
apt-get upgrade
Create a deployer user: You should never be logging on to a server as root.
useradd deployer
mkdir /home/deployer
mkdir /home/deployer/.ssh
chmod 700 /home/deployer/.ssh
usermod -s /bin/bash deployer
curl 'https://www.random.org/passwords/?num=2&len=24&format=plain&rnd=new'
passwd deployer
addgroup deployer root
adduser deployer sudo
Now access the Sudoers file to give permission to deployer run commands without system ask for password:
vim /etc/sudoers
or
nano /etc/sudoers
or
visudo
Add to last line (/etc/sudoers):
deployer ALL=(ALL) NOPASSWD: ALL
Copy SSH permissions from root to deployer:
rsync --archive --chown=deployer:deployer ~/.ssh /home/deployer
You can add more SSH keys:
vim /home/deployer/.ssh/authorized_keys
Let’s set the right permissions based on the Linux security principal of least privilege:
chmod 400 /home/deployer/.ssh/authorized_keys
chown deployer:deployer /home/deployer -R
Exit server as a root and log in again as a deployer .
exit
ssh [email protected]
Or just type: exec su -l deployer
Remove root login with password (only SSH will work).
Run
sudo vim /etc/ssh/sshd_config
and add/change:
PermitRootLogin no
PasswordAuthentication no
If you have a VPN/STATIC IP, you can restrict login to olny that IP, adding to this file:
AllowUsers deployer@(your-VPN-or-static-IP)
AddressFamily inet
Enable all these rules by restarting the ssh service. You’ll probably need to reconnect (do so by using your deploy user!)
sudo service ssh restart
First we’ll want to make sure that we are supporting IPv6. Just open up the config file.
vim /etc/default/ufw
Set IPv6 to yes.
IPV6=yes
For the rest of the ports that we’re going to open up, we can just use the ufw tool from command line which is very handy.
sudo ufw allow 22
sudo ufw allow 80
sudo ufw allow 443
sudo ufw disable
sudo ufw enable
Or, if you use Static IP:
sudo ufw allow from {your-ip} to any port 22
sudo ufw allow 80
sudo ufw allow 443
sudo ufw disable
sudo ufw enable
I like these. They’re not perfect, but it’s better than missing patches as they come out.
sudo apt-get install unattended-upgrades
acess
sudo vim /etc/apt/apt.conf.d/10periodic
Update this file to match this:
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
APT::Periodic::Unattended-Upgrade "1";
sudo apt-get install fail2ban
Rails dependencies (You can use the Go Rails tutorial. I use Puma and RVM. Go Rails use Passenger and Rbenv)
After that, install dependencies:
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
Utilize uma versao diferente se necessário.
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 nodejs yarn redis-server redis-tools -y
sudo apt install imagemagick -y
sudo apt install nginx -y
Testing Nginx:
systemctl status nginx
Or access your server IP onm browser and see if you get a nginx default message.
sudo chown deployer:deployer /home/deployer
mkdir apps
sudo chown deployer:deployer apps
Install rvm to manage ruby versions:
curl -sSL https://get.rvm.io | bash
source /home/deployer/.rvm/scripts/rvm
Install ruby (change x.x.x for your version version):
rvm install 2.X.X --default
Check if the ruby version is correct:
ruby -v
Install bundler (change x.x.x for your version version):
gem install bundler -v x.x.x --no-document
Install Rails (change x.x.x for your version version):
gem install rails -v 6.X.X.X --no-document
Check your rails version:
rails -v
For Postgres, we're going to start by installing the Postgres server and libpq which will allow us to compile the pg rubygem. Then, we're going to become the postgres linux user who has full access to the database and use that account to create a new database user for our apps. We'll call that user deployer. And finally, the last command will create a database called myapp and make the deployer user owner. Make sure to change myapp_database_name to the name of your application.
sudo apt-get install postgresql postgresql-contrib libpq-dev -y
sudo su - postgres
createuser --superuser --pwprompt deployer
createdb -O deployer **myapp_database_name/changeme**
exit
You can manually connect to your database anytime by running:
psql -U deployer -W -h 127.0.0.1 -d myapp_databas_name
.
Make sure to use 127.0.0.1 when connecting to the database instead of localhost.
Check if you have in your Gemfile at development group:
gem "capistrano", "~> 3.12", require: false
gem "capistrano-rails", require: false
gem "capistrano-yarn"
gem 'capistrano-rvm'
gem 'capistrano3-puma'
At your local machine, in app folder, run:
cap install
Update the created files:
- Capfile
# Load DSL and set up stages
require "capistrano/setup"
# Include default deployment tasks
require "capistrano/deploy"
# Include tasks from other gems included in your Gemfile
# require 'capistrano/rails'
require 'capistrano/bundler' # Rails needs Bundler, right?
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
require 'capistrano/rvm'
require 'capistrano/yarn'
# require "whenever/capistrano"
require 'capistrano/delayed_job'
# Load the SCM plugin appropriate to your project:
require "capistrano/scm/git"
install_plugin Capistrano::SCM::Git
require 'capistrano/puma'
install_plugin Capistrano::Puma
install_plugin Capistrano::Puma::Workers
install_plugin Capistrano::Puma::Nginx
# Whenever gem config
# set :whenever_identifier, ->{ "#{fetch(:application)}_#{fetch(:stage)}" }
#Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
- config/deploy.rb Update the git repo Add at :linked_dirs the folders that have to be shared among the deploy versions. If you use a local Active Storage (without AWS) add a "storage" folder at append :linked_dirs
# config valid for current version and patch releases of Capistrano
lock "~> 3.12.1"
set :application, "NOME-DA-APLICACAO"
# Update repo address
set :repo_url, "[email protected]:xxxxxxxx/xxxxxxxxxxx.git"
# Update folder
set :deploy_to, "/home/deployer/apps/app-name"
#change master.key for secrets.yml, if needed.
append :linked_files, "config/database.yml", "config/master.key"#, "config/local_env.yml", "config/sidekiq.yml"
append :linked_dirs, "log", "tmp"
set :keep_releases, 5
set :migration_role, :app
set :puma_pid, "#{shared_path}/tmp/pids/puma.pid"
set :puma_bind, "unix://#{shared_path}/tmp/sockets/puma.sock"
set :puma_access_log, "#{shared_path}/log/puma_access.log"
set :puma_error_log, "#{shared_path}/log/puma_error.log"
set :nginx_sites_available_path, "/etc/nginx/sites-available"
set :nginx_sites_enabled_path, "/etc/nginx/sites-enabled"
#Change ruby version
set :rvm_type, :user
set :rvm_ruby_version, '2.X.X'
namespace :puma do
desc 'Create Puma dirs'
task :create_dirs do
on roles(:app) do
execute "mkdir #{shared_path}/tmp/sockets -p"
execute "mkdir #{shared_path}/tmp/pids -p"
end
end
desc "Restart Nginx"
task :nginx_restart do
on roles(:app) do
execute "sudo service nginx restart"
end
end
before :start, :create_dirs
after :start, :nginx_restart
end
- config/deploy/production.rb
set :branch, 'master'
# Update user
set :user, 'deployer'
# Update server IP
set :server_address, 'xx.xx.xxx.xxx'
ask(:password, nil, echo: false)
server fetch(:server_address), user: fetch(:user), roles: %w{app db web}
set :nginx_server_name, fetch(:server_address)
set :puma_preload_app, true
Log in on server and generate a SSH key (I usually do that without the password)
ssh-keygen
Copy the SSH key
cat /home/deployer/.ssh/id_rsa.pub
Add this key to your repo at Github, on "deploy keys"
Log out of server and check at your **local machine ** if the deploy is ok:
cap production deploy:check
Capistrano will tell that a linked file does no exist. Let's create the database.yml and the others linked files needed.
Log at server, go to /apps/app-name/shared/config, and create the database.yml:
sudo vim apps/YOUR-APP-NAME/shared/config/database.yml
default: &default
adapter: postgresql
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
username: deployer
password: YOUR DATABASE PASSWORD
production:
<<: *default
database: APP-NAME
Now copy your master.key (or create it):
scp config/master.key deployer@xxxx:/home/deployer/apps/xxxxxx/shared/config/
*if you do not have a master.key you can generate one running: EDITOR=vim rails credentials:edit
Exit server and check deploy again:
cap production deploy:check
Configure Puma:
cap production puma:config
Configure Nginx:
cap production puma:nginx_config
To finalize, run the deploy!!!:
cap production deploy
On remote server Add repository
sudo apt install ca-certificates
When you see the question: 'Press [ENTER] to continue or Ctrl-c to cancel adding it', press ENTER
Update
sudo apt-get update
Install Certbot for Nginx
sudo apt-get install python-certbot-nginx -y
Insert the data on nginx
sudo nano /etc/nginx/sites-available/nomedaaplicacao_production
server_name xxx.xxx.xx.xxx yourdomain.com;
If you use Devise for authentication, to avoid 'ActionController::InvalidAuthenticityToken' error, add below 'proxy_set_header X-Forwarded-Proto http':
proxy_set_header X-Forwarded-Ssl on;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header X-Forwarded-Host $host;
(see: rails/rails#22965)
Restart Nginx
sudo service nginx reload
Get SSL
sudo certbot --nginx -d yourdomain.com
Certbot will ask your email. When asked: "Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access." Choose "2: Redirect" Check on the end of file etc/nginx/sites-avaliable/ if certbot conf were added:
cat /etc/nginx/sites-available/NOMEDAAPLICACAO_production
After Certbot run again the deploy:
cap production deploy
Congrats!!!
Use a S3 bucket https://github.com/huacnlee/gobackup
Run on servidor:
curl -sSL https://git.io/gobackup | bash
test:
gobackup -h
create:
sudo vim ~/.gobackup/gobackup.yml
add on this file:
models:
my-app:
compress_with:
type: tgz
store_with:
type: s3
keep: 120
bucket: <bucket-s3>
region: us-east-1
path: backups<you can change>
access_key_id: <add yours>
secret_access_key: <add yours>
databases:
my-app:
database: <database-name>
type: postgresql
host: localhost
username: deployer
password: <password>
Run with gobackup perform
To schedule:
crontab -e
Add (example for backups at each 6 hours):
# Execute GoBackup at each 6 hours
30 0,6,12,18 * * * /usr/local/bin/gobackup perform >> ~/.gobackup/gobackup.log
Or each 1 hour:
# Execute GoBackup hourly
0 * * * * /usr/local/bin/gobackup perform >> ~/.gobackup/gobackup.log
And after a hour, you can check up the execute status by ~/.gobackup/gobackup.log.
To restore database (test it!)
psql -U postgres -d your-database -f /path/to/your-backup.sql
Set Redis no Ubuntu: https://www.digitalocean.com/community/tutorials/how-to-install-and-secure-redis-on-ubuntu-18-04-pt or https://linuxize.com/post/how-to-install-and-configure-redis-on-ubuntu-20-04/
http://www.enquerer.com/run-sidekiq-in-digital-ocean-production-droplet-for-capistrano-deployed-rails-application/ https://github.com/mperham/sidekiq/blob/master/examples/systemd/sidekiq.service https://www.codewithjason.com/restart-sidekiq-automatically-deployment/
Add a sidekiq.yml to config:
---
:verbose: false
:concurrency: 10
:queues:
- mailers
- default
- low
production:
:pidfile: /home/deployer/apps/application-name/shared/tmp/pids/sidekiq.pid
:concurrency: 25
staging:
:pidfile: /home/deploy/apps/application-name/shared/tmp/pids/sidekiq.pid
:concurrency: 15
At config/deploy.rb add the sidekiq to shared files
append :linked_files, "config/database.yml", "config/master.key", "config/local_env.yml", "config/sidekiq.yml"
create pid on server: sudo touch /home/deployer/apps/my-application-name/shared/tmp/pids/sidekiq.pid sudo chmod 777 /home/deployer/apps/my-application-name/shared/tmp/pids/sidekiq.pid
In Order to start the Sidekiq as service while booting the server we need to make a Sidekiq service in our ubuntu server. So to do so, run the following in ubuntu server:
sudo nano /lib/systemd/system/sidekiq.service
https://github.com/mperham/sidekiq/blob/master/examples/systemd/sidekiq.service
change patch
- 'WorkingDirectory=/home/deployer/apps/my-application-name/current'
- ExecStart=/bin/bash -lc 'bundle exec sidekiq -e production -C config/sidekiq.yml'
#
# This file tells systemd how to run Sidekiq as a 24/7 long-running daemon.
#
# Customize this file based on your bundler location, app directory, etc.
# Customize and copy this into /usr/lib/systemd/system (CentOS) or /lib/systemd/system (Ubuntu).
# Then run:
# - systemctl enable sidekiq
# - systemctl {start,stop,restart} sidekiq
#
# This file corresponds to a single Sidekiq process. Add multiple copies
# to run multiple processes (sidekiq-1, sidekiq-2, etc).
#
# Use `journalctl -u sidekiq -rn 100` to view the last 100 lines of log output.
#
[Unit]
Description=sidekiq
# start us only once the network and logging subsystems are available,
# consider adding redis-server.service if Redis is local and systemd-managed.
After=syslog.target network.target
# See these pages for lots of options:
#
# https://www.freedesktop.org/software/systemd/man/systemd.service.html
# https://www.freedesktop.org/software/systemd/man/systemd.exec.html
#
# THOSE PAGES ARE CRITICAL FOR ANY LINUX DEVOPS WORK; read them multiple
# times! systemd is a critical tool for all developers to know and understand.
#
[Service]
#
# !!!! !!!! !!!!
#
# As of v6.0.6, Sidekiq automatically supports systemd's `Type=notify` and watchdog service
# monitoring. If you are using an earlier version of Sidekiq, change this to `Type=simple`
# and remove the `WatchdogSec` line.
#
# !!!! !!!! !!!!
#
Type=notify
# If your Sidekiq process locks up, systemd's watchdog will restart it within seconds.
WatchdogSec=10
WorkingDirectory=/home/deployer/apps/my-application-name/current
# If you use rbenv:
# ExecStart=/bin/bash -lc 'exec /home/deploy/.rbenv/shims/bundle exec sidekiq -e production'
# If you use the system's ruby:
ExecStart=/bin/bash -lc 'bundle exec sidekiq -e production -C config/sidekiq.yml'
# If you use rvm in production, don't.
# Use `systemctl kill -s TSTP sidekiq` to quiet the Sidekiq process
# !!! Change this to your deploy user account !!!
User=deployer
Group=sudo
UMask=0002
# Greatly reduce Ruby memory fragmentation and heap usage
# https://www.mikeperham.com/2018/04/25/taming-rails-memory-bloat/
Environment=MALLOC_ARENA_MAX=2
# if we crash, restart
RestartSec=1
Restart=on-failure
# output goes to /var/log/syslog (Ubuntu) or /var/log/messages (CentOS)
StandardOutput=syslog
StandardError=syslog
# This will default to "bundler" if we don't specify it
SyslogIdentifier=sidekiq
[Install]
WantedBy=multi-user.target
Step 5: To start Sidekiq when the server boots up, we need to create a symlink. In order to do so, run the below command.
sudo systemctl enable sidekiq.service
Step 6: Now we can start the Sidekiq service.
sudo service sidekiq start
Step 7: You can check the system log for errors.
sudo cat /var/log/syslog
And check if Sidekiq started by
sudo ps aux | grep sidekiq
If the Sidekiq is running you can see something like…
user 2609 0.7 3.3 1352776 136960 ? Ssl 06:18 0:15 sidekiq 5.2.7 application-name[0 of 25 busy]
Here it is the Sidekiq running successfully as service in your ubuntu droplet.
Now whenever the server boots up the Sidekiq server will also restart. You can manually stop, start, or restart the server by,
sudo service sidekiq start/stop/restart
Update the namespace :puma block on capistrano (config/deploy.rb) to restart sidekiq after each deploy:
namespace :sidekiq do
desc "Restart Sidekiq"
task :restart do
on roles(:app) do
execute "sudo service sidekiq restart"
end
end
end
namespace :deploy do
desc "Restart Sidekiq"
task :restart_sidekiq do
on roles(:app) do
execute "sudo service sidekiq restart"
end
end
after :finishing, :restart_sidekiq
end
https://gorails.com/guides/rotating-rails-production-logs-with-logrotate You might be surprised at just how easy to setup logrotate Rails logs is. The reason it is so handy is that a bunch of your operating system software is already using it. We just have to plug in our configuration and we’re set!
The first step is to open up /etc/logrotate.conf using vim or nano. Jump to the bottom of the file an add the following block of code. You’ll want to change the first line to match the location where your Rails app is deployed. Mine is under the deploy user’s home directory. Make sure to point to the log directory with the *.log bit on the end so that we rotate all the log files.
/home/deploy/APPNAME/current/log/*.log {
daily
missingok
rotate 7
compress
delaycompress
notifempty
copytruncate
}
or
# Rotate Rails application logs based on file size
# Rotate log if file greater than 20 MB
/path/to/your/rails/applicaton/log/*.log {
size=20M
missingok
rotate 52
compress
delaycompress
notifempty
copytruncate
}
or
# Rotate Rails application logs weekly
/path/to/your/rails/applicaton/log/*.log {
weekly
missingok
rotate 52
compress
delaycompress
notifempty
copytruncate
}
How It Works This is fantastically easy. Each bit of the configuration does the following:
- daily – Rotate the log files each day. You can also use weekly or monthly here instead.
- missingok – If the log file doesn’t exist, ignore it
- rotate 7 – Only keep 7 days of logs around
- compress – GZip the log file on rotation
- delaycompress – Rotate the file one day, then compress it the next day so we can be sure that it won’t interfere with the Rails server
- notifempty – Don’t rotate the file if the logs are empty
- copytruncate – Copy the log file and then empties it. This makes sure that the log file Rails is writing to always exists so you won’t get problems because the file does not actually change. If you don’t use this, you would need to restart your Rails application each time. Running Logrotate Since we just wrote this configuration, you’ll want to test it.
To run logrotate manually, we just do: sudo /usr/sbin/logrotate -f /etc/logrotate.conf
You’re going to want to run it a second time to make sure the delaycompress option is working and to actually compress the log. Here’s an example of what you’ll see if you ls
the log folder after running logrotate twice:
You can see that the production.log still exists, production.log.1 is a copy of the logs between the first and second run of logrotate, and production.log.2.gz is the 300MB behemoth of a log file that we had before compressed nicely with Gzip. Once we get up to 7 log files, the next time logrotate runs, it will delete the oldest one so that we only have 7 days worth of logs. If you want to keep all the logs around, you can remove the rotate line from the configuration.
Plus, since we just edited the main logrotate.conf file, the cron job will automatically execute the logrotate Rails logs daily!
Create fiel .env sudo vim /etc/.env
and add the vars you need, for example:
SENDGRID_USERNAME=myuser
SENDGRID_PASSWORD=xxxxxxxxxxxxxxxx
Add on bash, run: sudo vim ~/.bashrc
And add on first line:
source /etc/.env
https://dev.to/tcgumus/rails-6-webpacker-settings-for-production-1f1e
Grande Schmitt, tem tutorial para rails 7 e puma 6? :D