Skip to content

Instantly share code, notes, and snippets.

@CharlieYe0205
Last active May 5, 2022 03:14
Show Gist options
  • Save CharlieYe0205/4dcc7132c4aa8d2e25cd542f250080ad to your computer and use it in GitHub Desktop.
Save CharlieYe0205/4dcc7132c4aa8d2e25cd542f250080ad to your computer and use it in GitHub Desktop.
Deploy Rails 5 to Ubuntu 18.04

Create user deploy

sudo adduser deploy
sudo adduser deploy sudo
visudo
deploy  ALL=(ALL:ALL) ALL
su deploy

SSH Setup

generate ssh key

ssh-keygen -t rsa -b 4096 -C "xingyu.ye@outlook.com"

copy .ssh/id_rsa.pub to server .ssh/id_rsa.pub

cd ~
cat .ssh/id_rsa.pub
vi .ssh/authorized_keys
chmod 600 .ssh/authorized_keys


vim ~/.ssh/config

Host bitbucket.org
Hostname  altssh.bitbucket.org
Port  443

ssh -T git@bitbucket.org

SSH Security

sudo apt-get update
sudo apt-get upgrade -y && sudo apt full-upgrade -y
sudo apt-get install -y fail2ban
cd /etc/fail2ban
sudo vi jail.local

[DEFAULT]
bantime = 3600

banaction = iptables-multiport

[sshd]
enabled = true
port = 22
filter = sshd
logpath = /var/log/auth.log
maxretry = 9


sudo apt install -y iptables-persistent

sudo service fail2ban start
sudo fail2ban-client status sshd # check fail2ban status

sudo ufw status
sudo cp /etc/default/ufw /etc/default/ufw.0
cat /etc/default/ufw

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
sudo ufw enable
sudo ufw status

Install Requirements

Install Curl

sudo apt-get install -y curl imagemagick

Install Git

sudo apt-get install -y git
sudo apt install -y curl wget git zsh
echo $SHELL
chsh -s $(which zsh) 

logout
log back in
choose 2

sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

# server side
ZSH_THEME="simple"  

# pve
wget -O $ZSH_CUSTOM/themes/hyperzsh.zsh-theme https://raw.githubusercontent.com/tylerreckart/hyperzsh/master/hyperzsh.zsh-theme
ZSH_THEME="hyperzsh"

vi $ZSH_CUSTOM/themes/hyperzsh.zsh-theme
ZSH_THEME_GIT_PROMPT_PREFIX="%{$fg[magenta]%}"
ZSH_THEME_GIT_PROMPT_CLEAN=" %{$fg[green]%}✓%{$reset_color%} "


source .zshrc

Install Ruby via rvm

sudo apt install -y gnupg2
curl -sSL https://rvm.io/mpapis.asc | gpg2 --import
curl -sSL https://rvm.io/pkuczynski.asc | gpg2 --import
cd /tmp
curl -sSL https://get.rvm.io -o rvm.sh
cat /tmp/rvm.sh | bash -s stable --rails
source /home/{{user}}/.rvm/scripts/rvm


rvm install 2.6.3
rvm use 2.6.3
rvm list
ruby -v
ruby -ropenssl -e 'puts OpenSSL::OPENSSL_VERSION'

Install Bundler

echo "gem: --no-ri --no-rdoc" > ~/.gemrc
gem install bundler

set bundle config. ex.sidekiq account

Install PostgreSQL

sudo apt-get install -y postgresql postgresql-contrib libpq-dev

Create Postgres User

sudo su - postgres
createuser deploy --pwprompt
exit

Create Database

sudo su
su postgres
psql
create database {{database_name}} with owner = deploy;
ALTER ROLE deploy WITH CREATEDB;
ALTER ROLE deploy SUPERUSER; //to use uuid
\q
exit
exit

If there is an error regarding to encoding(utf8) compatibility. check link below https://gist.github.com/Sunnyztj/14c554e5694711433c5a1fad4e10892b

test the database with

psql --user deploy --password [database_name]
\q

Install Nginx

sudo apt-get install -y nginx
sudo service nginx start

Rails Project Setting

Git Ignore

# Ignore bundler config.
/.bundle

# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep

# Ignore uploaded files in development
/storage/*
!/storage/.keep

/node_modules
/yarn-error.log

/public/assets
.byebug_history

# Ignore master key for decrypting credentials and more.
/config/master.key

/config/database.yml
/config/secrets.yml
/config/application.yml
/config/racecar.yml
/config/racecar.rb
/config/sidekiq.yml

/config/kafka_ssl/*
.idea/*

dump.rdb

/db/*.sql

/spec/public/system/
/public/system/

.DS_Store

Gemfile

gem "capistrano-db-tasks", require: false
gem 'mini_racer', platforms: :ruby

group :development do
  gem 'capistrano', '~> 3.10'
  gem 'capistrano-rails', '~> 1.4'
  gem 'capistrano-rvm'
  gem 'capistrano-bundler'
  gem 'capistrano3-unicorn'
  gem 'capistrano-sidekiq'
end

gem 'unicorn'

Setup Unicorn

create config/unicorn/production.rb

root = "/home/deploy/{{project_name}}/current"
working_directory root

pid "#{root}/tmp/pids/unicorn.pid"

stderr_path "#{root}/log/unicorn_error.log"
stdout_path "#{root}/log/unicorn_output.log"

# worker_processes Integer(ENV['WEB_CONCURRENCY'])

worker_processes 2
timeout 10
preload_app true

listen "#{root}/tmp/sockets/unicorn.edocapi.sock", backlog: 64

before_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
    Process.kill 'QUIT', Process.pid
  end

  defined?(ActiveRecord::Base) and
      ActiveRecord::Base.connection.disconnect!
end

after_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
  end

  defined?(ActiveRecord::Base) and
      ActiveRecord::Base.establish_connection
end

# Force the bundler gemfile environment variable to
# reference the capistrano "current" symlink
before_exec do |_|
  ENV['BUNDLE_GEMFILE'] = File.join(root, 'Gemfile')
end

Capistrano

bundle exec cap install

config/deploy/production.rb

set :port, 22
set :user, 'deploy'
set :deploy_via, :remote_cache
set :use_sudo, false

server '{{server_ip}}', port: fetch(:port), user: fetch(:user), roles: %w{app db web}

set :stage, :production
set :rails_env, :production
set :unicorn_env, :production
set :unicorn_rack_env, 'production'
set :branch, "master"

config/deploy.rb

# config valid for current version and patch releases of Capistrano
lock "~> 3.11.0"

# if you want to remove the local dump file after loading
set :db_local_clean, true

# if you want to remove the dump file from the server after downloading
set :db_remote_clean, true

# if you want to exclude table from dump
set :db_ignore_tables, []

# if you want to exclude table data (but not table schema) from dump
set :db_ignore_data_tables, []

# if you are highly paranoid and want to prevent any push operation to the server
set :disallow_pushing, true

set :rvm_map_bins, %w{gem rake ruby rails bundle}

# configure location where the dump file should be created
set :db_dump_dir, -> { File.join(current_path, 'db') }

set :application, "{{project_name_in_lower_case}}"
set :repo_url, "{{project_remote_git_url}}"
set :branch, 'master'

set :deploy_to, "/home/deploy/{{project_name}}"

# Default value for :pty is false
# set :pty, true

append :linked_files, "config/database.yml", "config/application.yml", "config/secrets.yml", "config/sidekiq.yml"
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system", "public/uploads"

set :keep_releases, 5

set :assets_manifests, ['app/assets/config/manifest.js']

set :rails_assets_groups, :assets

set :keep_assets, 2


namespace :deploy do

  desc 'Restart application'
  task :restart do
    on roles(:web), in: :sequence, wait: 5 do
      execute :touch, release_path.join('tmp/restart.txt')
    end
  end

end


# after 'deploy:restart', 'unicorn:duplicate'

after 'deploy:publishing', 'deploy:restart'
namespace :deploy do
  task :restart do
    begin
      invoke! 'unicorn:stop'
    rescue
      p 'unicorn stop error'
    end

    begin
      invoke! 'unicorn:stop'
    rescue
      p 'unicorn stop error'
    end
    
    invoke 'unicorn:start'
  end
end

Capfile

# Load DSL and set up stages
require "capistrano/setup"

# Include default deployment tasks
require "capistrano/deploy"

# Load the SCM plugin appropriate to your project:
#
# require "capistrano/scm/hg"
# install_plugin Capistrano::SCM::Hg
# or
# require "capistrano/scm/svn"
# install_plugin Capistrano::SCM::Svn
# or
require "capistrano/scm/git"
install_plugin Capistrano::SCM::Git

# Include tasks from other gems included in your Gemfile
#
# For documentation on these, see for example:
#
#   https://github.com/capistrano/rvm
#   https://github.com/capistrano/rbenv
#   https://github.com/capistrano/chruby
#   https://github.com/capistrano/bundler
#   https://github.com/capistrano/rails
#   https://github.com/capistrano/passenger
#
require "capistrano/rvm"
# require "capistrano/rbenv"
# require "capistrano/chruby"
require "capistrano/bundler"
require "capistrano/rails/assets"
require "capistrano/rails/migrations"
# require "capistrano/passenger"
require 'capistrano3/unicorn'
require 'capistrano-db-tasks'

require 'capistrano/sidekiq'
#require 'capistrano/sidekiq/monit'

# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }

Deploy Check

cap production deploy:check

it will generate all the folders cd into config create database.yml, application.yml

vi application.yml
vi databse.yml
vi sidekiq.yml

rake secret
vi secrets.yml

check cap deploy again, it should all pass the list

Setup Nginx

go to server

cd /etc/nginx/sites-available
sudo vi default

change the default to the following

upstream unicorn {
  server unix:/home/deploy/{{project_name}}/current/tmp/sockets/unicorn.edocapi.sock fail_timeout=0;
}

server {
	listen 80 default_server;
	listen [::]:80 default_server;

	# SSL configuration
	#
	# listen 443 ssl default_server;
	# listen [::]:443 ssl default_server;
	#
	# Note: You should disable gzip for SSL traffic.
	# See: https://bugs.debian.org/773332
	#
	# Read up on ssl_ciphers to ensure a secure configuration.
	# See: https://bugs.debian.org/765782
	#
	# Self signed certs generated by the ssl-cert package
	# Don't use them in a production server!
	#
	# include snippets/snakeoil.conf;

	root /home/deploy/{{project_name}}/current/public;
    server_name {{project_server_name}};
    location ^~ /assets/ {
        gzip_static on;
            expires max;
            add_header Cache-Control public;
    }

    location ~ ^/(robots.txt|sitemap.xml.gz)/ {
        root /home/deploy/{{project_name}}/current/public;
    }

    try_files $uri/index.html $uri @unicorn;

	location @unicorn {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass http://unicorn;
    }

    error_page 500 502 503 504 /500.html;
    client_max_body_size 4G;
    keepalive_timeout 10;

	# pass PHP scripts to FastCGI server
	#
	#location ~ \.php$ {
	#	include snippets/fastcgi-php.conf;
	#
	#	# With php-fpm (or other unix sockets):
	#	fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
	#	# With php-cgi (or other tcp sockets):
	#	fastcgi_pass 127.0.0.1:9000;
	#}

	# deny access to .htaccess files, if Apache's document root
	# concurs with nginx's one
	#
	#location ~ /\.ht {
	#	deny all;
	#}
}

stop and restart the nginx service

sudo service nginx stop
sudo service nginx start

install redis

sudo apt-get -y install redis-server
sudo systemctl enable redis-server.service

change :pty in deploy.rb to true when first setup sidekiq, after first time set it back to false

set :pty, true
cap production deploy
set :pty, false

//? multi process sidekiq? https://github.com/seuros/capistrano-sidekiq

Deploy

deploy the project with

cap production deploy

useful command

cap production unicorn:start
cap production unicorn:stop
cap production sidekiq:start
cap production sidekiq:stop

Wicked PDF

sudo wget https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.bionic_amd64.deb
sudo dpkg -i wkhtmltox_0.12.5-1.bionic_amd64.deb
sudo apt-get install -f
sudo ln -s /usr/local/bin/wkhtmltopdf /usr/bin
sudo ln -s /usr/local/bin/wkhtmltoimage /usr/bin

Sidekiq in systemd

https://dev.to/kevinluo201/start-sidekiq-6-as-daemon-in-production-environment-on-ubuntu-20-04-4m7b


cd ~
mkdir -p .config/systemd/user
cd .config/systemd/user
vi sidekiq.service



[Unit]
Description=sidekiq
After=syslog.target network.target

[Service]
# notify can be used only after Sidekiq 6.0.6
# if the version is under 6.0.6, use Type=simple
Type=notify
WatchdogSec=10

WorkingDirectory=/path/to/your/app/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=/usr/local/bin/bundle exec sidekiq -e production
# If you use rvm in production without gemset and your ruby version is 2.6.5
# ExecStart=/home/deploy/.rvm/gems/ruby-2.6.5/wrappers/bundle exec sidekiq -e production
# If you use rvm in production with gemset and your ruby version is 2.6.5
# ExecStart=/home/deploy/.rvm/gems/ruby-2.6.5@gemset-name/wrappers/bundle exec sidekiq -e production
# If you use rvm in production with gemset and ruby version/gemset is specified in .ruby-version,
# .ruby-gemsetor or .rvmrc file in the working directory
ExecStart=/home/deploy/.rvm/bin/rvm in /path/to/your/app/current do bundle exec sidekiq -e production
ExecReload=/usr/bin/kill -TSTP $MAINPID

# 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
StandardOutput=syslog
StandardError=syslog

# This will default to "bundler" if we don't specify it
SyslogIdentifier=sidekiq

[Install]
WantedBy=default.target




systemctl --user daemon-reload 
systemctl --user enable sidekiq.service

# you can use systemctl to control sidekiq
systemctl --user {start,stop,restart} sidekiq


sudo cat /var/log/syslog

# Gemfile
gem 'capistrano-sidekiq', group: :development

# Capfile
require 'capistrano/sidekiq'
# Default sidekiq tasks
install_plugin Capistrano::Sidekiq
# We specify that we want to use systemd to control sidekiq
install_plugin Capistrano::Sidekiq::Systemd

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