ssh root@$PRODUCTION_IP
adduser deploy
# fill and enter
adduser deploy sudo
sudo vi /etc/ssh/sshd_config
# PasswordAuthentication yes
sudo systemctl restart ssh
make sure you can access as deploy
ssh deploy@$PRODUCTION_IP
ssh-copy-id deploy@$PRODUCTION_IP
ssh-copy-id -i ~/.ssh/specific_key.pub deploy@$PRODUCTION_IP
ssh deploy@$PRODUCTION_IP
Install node and yarn as deploy user
# sudo pwd
# 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
# click Enter
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
# click Enter
Installing latest node can be done with n
package
yarn global add n
yarn global bin
sudo /home/ubuntu/.yarn/bin/n latest
install rbenv and ruby as deploy user
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bashrc
git clone https://github.com/rbenv/rbenv-vars.git ~/.rbenv/plugins/rbenv-vars
exec $SHELL
rbenv install 2.6.3
rbenv global 2.6.3
ruby -v
gem install bundler
bundle -v
Add env variable for mastey key and database url
mkdir move_index
vi move_index/.rbenv-vars
# add lines
DATABASE_URL=postgres://deploy:[email protected]/move_index_production
RAILS_MASTER_KEY=1234
RAILS_ENV=production
# check current value of max-old-space-size
# $HOME/.rbenv/bin/rbenv exec bundle exec node -e 'console.log(v8.getHeapStatistics().heap_size_limit/(1024*1024))'
NODE_OPTIONS=--max-old-space-size=460
echo '# this will load master key in env' >> ~/.bashrc
echo 'export $(cat ~/move_index/.rbenv-vars)' >> ~/.bashrc
or add this task to copy master key from shared to config folder. You need to
create shared file vi /var/www/my_app/shared/master.key
.
# config/deploy.rb
namespace :config do
task :symlink do
on roles(:app) do
execute :ln, "-s #{shared_path}/master.key #{release_path}/config/master.key"
after 'deploy:symlink:shared', 'config:symlink'
passenger and nginx
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7
sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger bionic main > /etc/apt/sources.list.d/passenger.list'
sudo apt-get update
sudo apt-get install -y nginx-extras libnginx-mod-http-passenger
sudo vim /etc/nginx/conf.d/mod-http-passenger.conf
# change the line for passenger_ruby
# passenger_ruby /home/deploy/.rbenv/shims/ruby;
sudo service nginx start
configure nginx (replace myapp with your app name, usually github folder name)
sudo vi /etc/nginx/sites-enabled/default
server {
listen 80;
listen [::]:80;
server_name _;
root /home/deploy/myapp/current/public;
passenger_enabled on;
passenger_app_env production;
location /cable {
passenger_app_group_name myapp_websocket;
passenger_force_max_concurrent_requests_per_process 0;
# Allow uploads up to 100MB in size
client_max_body_size 100m;
location ~ ^/(assets|packs) {
expires max;
gzip_static on;
sudo service nginx reload
install postgres
sudo apt-get -y install postgresql postgresql-contrib libpq-dev
sudo su - postgres
createuser --pwprompt deploy
# enter PASSWORD
createdb -O deploy move_index_production
# test if you can access with:
psql -d move_index_production
# change deploy user as superuser so it can create extensions like pgcrypto uuid
sudo su postgres -c "psql -d postgres -c 'ALTER USER deploy WITH SUPERUSER;'"
dump postgresql with
pg_dump move_index_production > m.dump
scp m.dump IP:
ssh IP
psql move_index_production < ~/m.dump
Install capistrano
Capistrano is not server provisioning tool. You need to manually boot up server
and install ssh, apache, git… usually all tasks that requires sudo should be
done manually. Capistrano use single non priviliged user in non interactive ssh
session. Rubber has script for provisioning a server cap rubber:create
and
installing packages cap rubber:bootstrap
Capistrano version 3 is used here. Below is section for capistrano version 2.
cat >> Gemfile << HERE_DOC
# Use Capistrano for deployment
group :development do
gem 'capistrano', '~> 3.14', require: false
gem 'capistrano-rails', '~> 1.6', require: false
gem 'capistrano-puma', require: false
gem 'capistrano-rvm', require: false
# use those if you do not use puma and rvm
gem 'capistrano-passenger', '~> 0.2.0'
gem 'capistrano-rbenv', '~> 2.2'
# cap production rails:c
gem 'capistrano-rails-console', require: false
# cap production postgres:replicate
gem 'capistrano3-postgres', require: false
HERE_DOC
bundle exec cap install
# this will generate Capfile, config/deploy.rb and config/deploy/staging.rb...
cat > Capfile << HERE_DOC
# Load DSL and set up stages
require 'capistrano/setup'
require 'capistrano/deploy'
require 'capistrano/scm/git'
install_plugin Capistrano::SCM::Git
require 'capistrano/rails'
require 'capistrano/rails/console'
# Include tasks from other gems included in your Gemfile
require 'capistrano/bundler'
require 'capistrano/rvm'
require 'capistrano/puma'
# if you use passenger and rbenv
require 'capistrano/passenger'
require 'capistrano/rbenv'
require 'capistrano/rails/console'
require 'capistrano3/postgres'
# Load custom tasks from `lib/capistrano/tasks` if you have any defined
# and add to deploy.rb like `after "deploy:published", "translation:setup"`
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
HERE_DOC
To see all tasks you can run
cap -T
cap -T postgres
To see description rake task
cap -D my-task
Custom tasks have some additional format to plain rake tasks
# lib/capistrano/tasks/rake.cap.rb
desc 'Invoke a rake command on the remote server: cap qa "invoke[db:migrate]"'
task :invoke, [:command] => 'deploy:set_rails_env' do |task, args|
on primary(:app) do
within current_path do
with :rails_env => fetch(:rails_env) do
rake args[:command]
You need to set credentials master key
# /home/deploy/myapp/.rbenv-vars
RAILS_MASTER_KEY=1234
DATABASE_URL=postgresql://deploy:[email protected]/myapp_production
Configure server
# config/deploy.rb
lock '~> 3.16.0'
set :application, 'myapp'
set :repo_url, '[email protected]:duleorlovic/myapp.git'
# Default branch is :master
# ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp
set :branch, :main
# Default deploy_to directory is /var/www/my_app_name
# set :deploy_to, "/var/www/my_app_name"
set :deploy_to, "/home/ubuntu/#{fetch(:application)}"
# config/deploy/production.rb
server 'production-app.xceednet.com',
user: 'ubuntu',
roles: %w[app db web],
ssh_options: {
keys: '~/.ec2/singapore-webapp01-keypair.pem'
To deploy you can
cap production deploy
On digital ocean you can create snapshots and use it to create new droplets
(new droplet can not be less than current hdd size od snapshot).
For redis and sidekiq look below.
Configuration variables
configuration
files can set variables set :my_var_name, "value"
and fetch values fetch
:my_var_name
. There are some variables that are used by default:
:application
name of application
:deploy_to
path on the remote server where the app should be deployed,
initially is -> { "/var/www/#{fetch(:application)}" }
but I like home folder.
Inside that folder there are:
current
symlink to some release /var/www/my_app/releases/20170101010101
releases/
contains timestamped subfolder
repo/
hold git repository
revisions.log
timestaped log for all deploy or rollback actions. You can
rollback with cap production deploy:rollback
shared/
contains linked_files
and linked_dirs
that persists across
releases (db configuration, user storage)
:scm
by default is :git
:repo_url
is url for git. It should be accessible from remote server. For
local set :repo_url, 'ssh://[email protected]/my_project
:linked_files
is symlinked files from shared folder
:default_env
for specific env variables
# config/deploy.rb
lock "3.8.0"
set :application, "myapp"
set :repo_url, "[email protected]:rails/temp/myapp"
set :deploy_to, "/home/deploy/#{fetch(:application)}"
append :linked_dirs, 'log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', '.bundle', 'public/system', 'public/uploads'
# deploy with: cap staging deploy BRANCH=branch, it does not support
# underscores like: my_branch_name
set :branch, ENV['BRANCH'] if ENV['BRANCH']
# rails
set :rails_env, 'production'
# config/deploy/staging.rb
# if you use NAT network
server "127.0.0.1", user: "deploy", roles: %w{app db web}, port: 2222
# or if you use private network
server "192.168.3.2", user: "deploy", roles: %w{app db web}
Preparing the app
preparing
is with cap install
.
Tasks are usually only for specific roles so you server needs to belongs to that
role if you want task to be executed. Three main roles
:web
role is nginx/apache server. When I look at
https://github.com/capistrano/rails/blob/master/lib/capistrano/tasks/assets.rake#L136
I see that assets_roles
is by defailt [:web]
so there we execute tasks
like deploy:asset
. Probably you need to include also :worker
role
# config/deploy.rb
# we need assets on worker since we send emails that uses images (like logo)
set :assets_roles, %i[web worker]
:app
role is for rails app. Capistrano’s built-in tasks deploy:check
,
deploy:published
or deploy:finished
are all run in this role.
https://capistranorb.com/documentation/getting-started/flow/
You can attach your tasks after finish publishing the deploy
# lib/capistrano/tasks/puma.rake
namespace :puma do
desc 'Restart puma service'
task :restart do
on roles(:app) do
execute 'sudo service puma restart'
after 'deploy:published', 'puma:restart'
:db
role is for mysql/postgresql database (requires primary: true
) or just
running migrations. capistrano-rails
plugin provides the deploy:migrate
https://github.com/capistrano/rails/blob/master/lib/capistrano/tasks/migrations.rake
Here we are adding public/assets/
to linked_dirs (which should also
include public/packs
)
You can define in two ways. Properties will be merged.
# using simple syntax
role :web, %w{[email protected] example.com:1234}
# using extended syntax (which is equivalent)
server 'world.com', roles: [:web], user: 'hello'
server 'example.com', roles: [:web], port: 1234
For local vagrant (see below) you can use
# config/deploy.rb
set :repo_url, "[email protected]:rails/temp/myapp"
# config/deploy/staging.rb
server "127.0.0.1", user: "vagrant", roles: %w{app db web}, port: 2222
If you are using private github repo, than permission problems for git will
occurs, so you can use your local keys on server with those option:
set :ssh_options, forward_agent: true
You can check before deploying with cap staging git:check
or cap staging
deploy:check
Tasks
Nice railscasts video but for old
capistrano.
# lib/capistrano/tasks/notify.rake
namespace :my_tasks do
desc "My first task"
task :my_first_task do
on roles(:all) do
puts "Start my first task"
invoke 'my_tasks:notify'
task :notify do
on roles(:all) do |host|
puts "notify #{host}"
To run in specific env, run with: cap staging my_tasks:my_first_task
Task Syntax
desc
and task
are rake methods. Others are taken from
sshkit:
on roles(:all) do |host|
will iterate to eah server host, You can run
locally with run_locally do
. roles(:all)
return list of all hosts
capture
error
with
set ENV variable. only
within
use folder. should be inside on
invoke
run other rake task
execute
used to run command: execute :rake, 'assets:precompile', env: {
rails_env: fetch(:rails_env) }
You can execute task in before or after hooks.
flow has several
points that you can access.
before :finishing, :notify do
You can use puts
and run
to run command in shell. If you use sudo, than you
should enable default_run_options[:pty] = true
so when he ask for password it
prompts in current shell. Also helpfull is ssh_options[:forward_agent] = true
which uses your keys on remote server to download private repositories.
task :hello do
puts "Here are all files"
run "ls"
run "#{sudo} ls /etc"
Upgrading capistrano 2 to 3
documentation
here are differences between cap 2 and cap 3
repository
is renamed to repo_url
environment is set instead RAILS_ENV=vagrant cap -T
to second param cap
vagrant -T
instead of positional argument for role server "123.123.123.123", :web
use
hash roles
argument server "123.123.123.123", roles: [:web]
Sample app on EC2
rails new myapp
cd myapp
git init . && git add . && git commit -m "rails new myapp"
sed -i Gemfile -e '/capistrano/c \
gem "capistrano-rails", group: :development'
# capistrano-rails will also install capistrano, capistrano-bundler
# rvm puma ?
cap install STAGES=virtual
echo "require 'capistrano/rails'" >> Capfile
On AWS create new instance and download new key for example aws_test.pem
.
chmod 400 aws_test.pem
ssh -i "aws_test.pem" [email protected]
Capistrano with custom Vagrant script
You can install server localy using Vagrant scripts.
You can use default NAT address (access virtual box at ssh port 2222) and port
forwarding (config.vm.network :forwarded_port, guest: 80, host: 8080
). In
order to access host machine from virtual box you need to know host IP address.
So my solution is to use Private IP (private_network
) and host is easilly
determined from that.
You need to add public key on host after provision is done so virtual can access
host without password (maybe to try with
vagrant-triggers), so two commands
are needed
ssh-keygen -f "/home/orlovic/.ssh/known_hosts" -R 192.168.3.2
ssh [email protected] cat .ssh/id_rsa.pub >> ~/.ssh/authorized_keys
Here are scripts
vagrant init
# edit Vagrantfile
cat >> Vagrantfile << HERE_DOC
Vagrant.configure("2") do |config|
config.vm.provider "virtualbox" do |vb, vb_config|
# list of all machines can be found https://atlas.hashicorp.com/boxes/search
vb_config.vm.box = "ubuntu/trusty64"
vb.memory = "2048"
vb_config.vm.provision :shell, inline: $script, keep_color: true
vb_config.vm.network :private_network, ip: $server_ip
# $ruby_version = `grep "^ruby" Gemfile | awk "{print $2}"`
$ruby_version = `ruby --version | awk "{print $2}"`.split("p").first # 2.3.1p112
$public_key = `cat ~/.ssh/id_rsa.pub`.strip
$server_ip = "192.168.3.2"
$nginx_config = <<-NGINX_CONFIG
# https://www.digitalocean.com/community/tutorials/deploying-a-rails-app-on-ubuntu-14-04-with-capistrano-nginx-and-puma
upstream app {
# Path to Unicorn SOCK file, as defined previously
server unix:/home/deploy/myapp/shared/tmp/sockets/puma.sock;
server {
listen 80 default_server deferred;
server_name myapp.local;
root /home/deploy/myapp/current/public;
access_log /home/deploy/myapp/current/log/nginx.access.log;
error_log /home/deploy/myapp/current/log/nginx.error.log;
location ^~ /assets/ {
gzip_static on;
expires max;
add_header Cache-Control public;
try_files $uri/index.html $uri @app;
location @app {
proxy_pass http://app;
// this forwards remote ip address to the app
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
access_log /home/deploy/myapp/current/log/nginx.rails.access.log;
// I tried to pass scheme http or http to the app but the problem is nlb
// does not send header to the app. Elb sends headers
// https://docs.aws.amazon.com/elasticloadbalancing/latest/userguide/how-elastic-load-balancing-works.html#request-routing
// but NLB does not set headers
// https://stackoverflow.com/questions/54558742/x-forwarded-proto-not-being-passed-through-aws-alb-sandwich-with-palo-alto-vm-fi
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
// so only solution is to redirect in javascript
error_page 500 502 503 504 /500.html;
client_max_body_size 10M;
keepalive_timeout 10;
if ($request_method !~ ^(GET|HEAD|PUT|PATCH|POST|DELETE|OPTIONS)$ ){
return 405;
if (-f $document_root/system/maintenance.html) {
return 503;
NGINX_CONFIG
$script = <<-SCRIPT
set -e # Any commands which fail will cause the shell script to exit immediately
set -x # show command being executed
L=en_US.UTF-8
update-locale LANG=$L LANGUAGE=$L LC_ALL=$L # needed for database default enc
# or install missing locale with, for example sr_RS
# locale-gen sr_RS
echo "STEP: update"
# apt-get -y update > /dev/null # update is needed to set proper apt sources
if [ "`id -u deploy`" = "" ]; then
echo "STEP: creating user deploy (without sudo access)"
useradd deploy -md /home/deploy --shell /bin/bash
echo deploy:deploy | chpasswd # change password to 'deploy'
echo STEP: generate keys and adding host public key to vb authorized keys
sudo -i -u deploy /bin/bash -c "yes '' | ssh-keygen -N ''"
sudo -i -u deploy /bin/bash -c "echo #{$public_key} >> ~/.ssh/authorized_keys"
# TODO: cap staging git:check will add to known hosts
sudo -i -u deploy /bin/bash -c "ssh-keyscan #{$server_ip[0..-2]+"1"} >> ~/.ssh/known_hosts"
# gpasswd -a deploy sudo # add to sudo group
# echo "deploy ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/deploy # don't ask for password when using sudo
# if [ ! "`id -u vagrant`" = "" ]; then
# usermod -a -G vagrant deploy # adding to vagrant group if vagrant exists
echo "STEP: user deploy already exists"
if [ "`which git`" = "" ]; then
echo "STEP: install development tools: git nodejs ..."
apt-get -y install build-essential curl git nodejs multitail
echo "STEP: development tools already installed"
if [ "`which nginx`" = "" ]; then
echo "STEP: install ngix server"
apt-get -y install nginx
cat << 'NGINX_CONFIG' | sudo tee /etc/nginx/sites-available/default
#{$nginx_config}
NGINX_CONFIG
# TODO: it seems we need to restart nginx later, probable at this moment puma
# sockets do not exists yet
sudo service nginx restart
echo "STEP: nginx already installer"
export DATABASE_URL=postgresql://deploy:deploy@localhost/myapp_production
DATABASE_NAME=${DATABASE_URL##*/}
if [ `echo $DATABASE_URL | cut -f 1 -d ':'` = "postgresql" ]; then
if [ "`which psql`" = "" ]; then
echo "STEP: installing postgres"
apt-get -y install postgresql postgresql-contrib libpq-dev
echo STEP: postgres already installed
if [ "`sudo -u postgres psql postgres -tAc "SELECT 1 FROM pg_roles WHERE rolname='deploy'"`" = "1" ]; then
echo STEP: postgres user 'deploy' already exists
echo STEP: create postgresql database user deploy
sudo -u postgres createuser --superuser --createdb deploy
sudo -u postgres psql -U postgres -d postgres -c "alter user deploy with password 'deploy';"
if sudo -u postgres psql -lqt | cut -d \\\| -f 1 | grep -wq $DATABASE_NAME; then
echo STEP: $DATABASE_NAME already exists
echo STEP: creating $DATABASE_NAME
sudo -u deploy createdb $DATABASE_NAME
echo STEP: create mysql2 database user deploy
if [[ `echo "SELECT user FROM mysql.user WHERE user = 'deploy'" | mysql` = "" ]]; then
echo CREATE USER 'deploy'@'localhost' | mysql --user=root
echo GRANT ALL PRIVILEGES ON * . * TO 'deploy'@'localhost' | mysql --user=root
#FLUSH PRIVILEGES;
echo STEP: mysql user 'deploy' already exists
if [ "`sudo -i -u deploy which bundle`" = "" ]; then
#if [ ! -f /usr/local/rvm/scripts/rvm ]; then
echo "STEP: installing rvm for system so it can download sudo requirements"
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
# multi-user install to /usr/local/rvm, we use vagrant since it is in sudoers list
sudo -i -u vagrant /bin/bash -c "curl -sSL https://get.rvm.io | sudo -i bash -s stable --ruby"
usermod -a -G rvm deploy # adding to rvm group so it can access /usr/local/rvm
usermod -a -G rvm vagrant # adding to rvm group so it can access /usr/local/rvm
if [ ! "#{$ruby_version}" = "" ]; then
echo "STEP: installing ruby version #{$ruby_version}"
# sometime we need to remove old cache
# rm -rf $rvm_path/archives/rubygems-* $rvm_path/user/{md5,sha512}
sudo -i -u vagrant /bin/bash -c "rvm install #{$ruby_version}"
echo "STEP: install bundle for deploy"
sudo -i -u deploy /bin/bash -c "rvm install 2.4"
sudo -i -u deploy /bin/bash -c "gem install bundler"
echo "STEP: rvm and bundler already installed"
SCRIPT
HERE_DOC
vagrant destroy -f # destroy without confirmation
vagrant up --provision # to provision again
vagrant ssh
# or directly
ssh -p 2222 [email protected]
# username/password is deploy/deploy
ssh-keygen -f "/home/orlovic/.ssh/known_hosts" -R [127.0.0.1]:2222
yes | ssh-copy-id -p 2222 [email protected]
ssh -p 2222 [email protected]
See multiple logs (like multilog or multi tail) in one terminal window
vagrant ssh
sudo apt-get install multitail
multitail /var/log/nginx/* /home/deploy/myapp/current/log/*
see logs of capistrano on local machine
less log/capistrano.log
Rubber
This gem is used just as wrapper for common stack, like passenger_postgresql,
targeting EC2 instances and do all provision stepts.
It depends on capistrano 2.
railscast
google group
cat >> Gemfile << HERE_DOC
# deploy and provision tool
gem "rubber"
HERE_DOC
bundle exec rubber vulcanize complete_passenger_postgresql
# bundle exec rubber vulcanize complete_passenger_mysql
Vulcanize will copy
templates
to config/rubber
which you can customize, usually only yml
files.
Capistrano recepies files .rb
usually do not need to change, and also
configuration files role/*.conf
are up-to-date.
You can start configuring config/rubber/rubber.yml
and config/deploy.rb
.
In rubber.yml
add AWS root security credentials (or another IAM user) to
access API and EC2 keypairs to access machine.
User security credentials you can export in env.
Keypair you can download to home ~/.ec2
folder, rename without pem
and
change permissions chmod 600 ~/.ec2/gsg-keypair
.
No need to create public version ssh-keygen -y -f ~/.ec2/aws_test >
~/.ec2/aws_test.pub
). In case of The key pair does not exist
(Fog::Compute::AWS::NotFound)
you should check if keypair is for same region.
Also image_id
should be some of the
http://cloud-images.ubuntu.com/locator/ec2/ You can choose t2.micro since it
Free tier applicable:
# config/rubber/rubber.yml
# this should be one word and app will be in /mnt/myapp-production/releases/...
app_name: myapp
app_user: ubuntu
cloud_providers:
access_key: "#{ ENV['AWS_ACCESS_KEY_ID'] }"
secret_access_key: "#{ ENV['AWS_SECRET_ACCESS_KEY'] }"
account: "#{ ENV['AWS_ACCOUNT_ID'] }"
key_name: us-east-1-gsg-keypair
image_type: t2.micro
# Ubuntu 16
image_id: ami-04169656fea786776
prompt_for_security_group_sync: false
# this is just default prompt
staging_roles: "app,db:primary=true"
We use just web app and db role, not all roles:
apache,app,collectd,common,db:primary=true,elasticsearch,examples,graphite_server,graphite_web,graylog_elasticsearch,graylog_mongodb,graylog_server,graylog_web,haproxy,mongodb,monit,passenger,postgresql,postgresql_master,web,web_tools
Role app
depends on passenger
which depends on apache
.
Role "db:primary=true"
depends on postgresql
and postgresql_master
I had a problem with web
role since it depends on haproxy
(config/rubber/rubber-complete.yml
) which has some errors, so I remove that
dependency:
# config/rubber/rubber-complete.yml
web: []
app: [passenger]
We could remove that role, but we need to assign security groups.
That imlies to change passenger port to 80
# config/rubber/rubber-passenger.yml
passenger_listen_port: 80
passenger_listen_ssl_port: 443
For Ubuntu 16 (which is ) we need to remove package libapache2-mod-proxy-html
for apache
https://groups.google.com/forum/#!topic/rubber-ec2/fut5WZ6TicE
# config/rubber/rubber-apache.yml
roles:
apache:
packages: [apache2, apache2-utils, libcurl4-openssl-dev, libapache2-mod-xsendfile]
web_tools:
packages: [apache2, apache2-utils, libcurl4-openssl-dev, libapache2-mod-xsendfile]
and remove apache2-mpm-prefork, apache2-prefork-dev for passenger and remove
passenger version
# config/rubber/rubber-passenger.yml
packages: [libcurl4-openssl-dev, libapache2-mod-xsendfile, libapache2-mod-passenger]
and make sure it has the same ruby version which if going to be build in
config/rubber/deploy-setup.rb
You can use later ruby-build https://github.com/rbenv/ruby-build/releases and some of ruby versions ruby-build --definitions
# config/rubber/rubber-ruby.yml
ruby_build_version: 20180822
ruby_version: 2.3.3
For rails you need to uncommend gem 'mini_racer', platforms: :ruby
in Gemfile.
To provision you can run
cap rubber:create_staging
# Hit enter at prompts to accept the default value for params
# this is the same as manually
cap rubber:create
cap rubber:bootstrap
cap deploy
# or you can set params in env var like
# ALIAS=web01 ROLES=app cap rubber:create
This will create config/rubber/instance-production.yml
. It will also reboot
after creating. ALIAS
is name ie subdomain and can not contain underscore. If
you run script again and change name it will add another InstanceItem web02
(new EC2 machine). It will also update your /etc/hosts
so you can access in
browser with production.foo.com instead of ip address of newly created instance.
You can remove (terminate) all EC2 instances with
cap rubber:destroy
# type 'web01' or your alias
You can create additional instances:
ALIAS=web01 ROLES=app cap rubber:create
ALIAS=web01 ROLES=app cap rubber:bootstrap # this is idempotent and it is
# reading config/rubber/instance-production.yml for web01 InstanceItem
cap deploy:cold
and you can see current in config/rubber/instance-vagrant.yml
Roles should be defined in that instance file. Note that for first time we are
defining roles using env ROLES on rubber create task.
Note that if current instance- file
does not exists
rubber:create_staging
will create new instance using staging_roles
. If that
instance file exists, than roles from it will be used.
Rails is running on port 7000 inside virtualbox. haproxy routes standard http
80 port to rails 7000, so you can access site on http://default.foo.com/
ERRORS
To see logs you can
ssh -i $PEM_FILE [email protected]
tail -f /var/log/apache2/* /mnt/myapp-production/current/log/*
# in another terminal
curl localhost:7000
logs for other services you can find
https://github.com/rubber/rubber/wiki/Troubleshooting
or you can use existing task:
RAILS_ENV=vagrant cap rubber:tail_logs
If you see only dots, it is probably stuck on passenger
* executing `rubber:passenger:serial_add_to_pool_reload_default'
** Waiting for passenger to startup
* executing "sudo -p 'sudo password: ' bash -l -c 'while ! curl -s -f http://localhost:$CAPISTRANO:VAR$/ &> /dev/null; do echo .; done'"
servers: ["default.foo.com"]
[default.foo.com] executing command
** [out :: default.foo.com] .
** [out :: default.foo.com] .
Please check rails logs (passenger logs is in apache log) to see if there is any
error on index page. Probably for first time you need to create database
cd /mnt/myapp-production/current
bundle exec rake db:setup
For errors like
[email protected]: Permission denied (publickey).
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
solution is
# sometimes you need to start agent, for example you ssh to machine, so for
error like: # Could not open a connection to your authentication agent.
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_rsa
Rubber Vagrant rubber provision
It works only for old version of vagrant 1.7.4 (just remove and install that
version .deb file using
software app)
vagrant -v # should return 1.7.4
vagrant plugin install rubber
It depends on older bundler so run
gem uninstall bunder
gem install bundler -v 1.10.5
rm Gemfile.lock
bundle install
Add vagrant env to secrets, database and env
echo "vagrant: *default" >> config/secrets.yml
cat >> config/database.yml << HERE_DOC
vagrant:
<<: *default
HERE_DOC
cp config/environments/production.rb config/environments/vagrant.rb
To be able to ssh into vagrant you need to use your keys or manually copy
later.
You need to define your ruby version and roles that you need. Note that current
ruby version need to be same, so check that rvm list
returns current same as
your from Vagrantfile.
cat >> Vagrantfile << HERE_DOC
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/trusty64"
config.vm.box_check_update = false
config.vm.network :private_network, ip: "192.168.70.10"
# do not generate new key, copy my key and use it
config.ssh.insert_key = false
config.vm.provision "file", source: "~/.ssh/id_rsa.pub", destination: "~/.ssh/authorized_keys"
config.ssh.private_key_path = ["~/.ssh/id_rsa", "~/.vagrant.d/insecure_private_key"]
# ruby build fails on 512MB so we increase
config.vm.provider "virtualbox" do |v|
v.memory = 1024
config.vm.provision :rubber do |rubber|
rubber.rubber_env = 'vagrant'
# If you remove this line, staging_roles from config/rubber/rubber.yml is used
rubber.roles = 'web,app,apache,hadproxy,monit,passenger,postgresql_master,db:primary=true'
# Only necessary if you use RVM locally.
rubber.rvm_ruby_version = '2.3.1'
HERE_DOC
To create vagrant machine you can run vagrant commands (we use tee to save log)
vagrant up
# when machine already created
vagrant provision
# or to save a log
vagrant destroy -f && vagrant up 2>&1 | tee tmp/log.log
Note that if you make changes to config/rubber/rubber.yml
you need to remove
config/rubber/instance-vagrant.yml
.
I have error
** [out :: default.foo.com] The following packages have unmet dependencies:
** [out :: default.foo.com] libapache2-mod-passenger : Depends: passenger (= 1:5.0.8-1~trusty1) but it is not going to be installed
** [out :: default.foo.com] Unable to correct problems, you have held broken packages.
so I need to remove version parameter from config/rubber/rubber-passenger.yml
Another error
** ERROR: Error installing rubber:
** xmlrpc requires Ruby version >= 2.3.
** /tmp/gem_helper:32:in `block in <main>'
** Unable to install versioned gem rubber:3.2.2
** RuntimeError
I updated ruby version in config/rubber/rubber-ruby.yml
to match 3.2.1
Also if ruby-build is failed
you can increase memory or add option to config/rubber/deploy-setup.rb
Also I need to comment out rsudo "service vboxadd setup"
from
config/rubber/deploy-setup.rb
.
And for rails asset pipeline you need to add nodejs
package to
config/rubber/rubber-ruby.yml
to packages
list.
If you change hostname or key, maybe there is connection error like
. ** timeout in initial connect, retrying
Trying to enable root login
* executing `rubber:_ensure_key_file_present'
* executing `rubber:_allow_root_ssh'
* executing "sudo -p 'sudo password: ' bash -l -c 'mkdir -p /root/.ssh && cp /home/vagrant/.ssh/authorized_keys /root/.ssh/'"
servers: ["192.168.70.10"]
** Failed to connect to 192.168.70.10, retrying
* executing `rubber:_ensure_key_file_present'
* executing `rubber:_allow_root_ssh'
* executing "sudo -p 'sudo password: ' bash -l -c 'mkdir -p /root/.ssh && cp /home/vagrant/.ssh/authorized_keys /root/.ssh/'"
servers: ["192.168.70.10"]
when you try ssh [email protected]
you see
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
SHA256:ur+QgdymSTNZYqImXqmRgw3cZonpVjZjd7gtNyxyR
Please contact your system administrator.
Add correct host key in /home/orlovic/.ssh/known_hosts to get rid of this message.
Offending RSA key in /home/orlovic/.ssh/known_hosts:3
remove with:
ssh-keygen -f "/home/orlovic/.ssh/known_hosts" -R 192.168.70.10
RSA host key for 192.168.70.10 has changed and you have requested strict checking.
Host key verification failed.
Than just remove the key from known_hosts and it will resume without errors.
If you get errors like
Received disconnect from 54.210.7.47 port 22:2: Too many authentication failures
Connection to production.redirection.my-domain.com closed by remote host.
Connection to production.redirection.my-domain.com closed.
it might help to add username ubuntu@
, like ssh -i $PEM_FILE
[email protected]
To add env secrets you can write them in a keys.sh
file and source from
~/.bashrc
before return if not interactivelly. If you are using rubber than
use system wide bash for example /etc/profile
and put keys for example
/root/keys.sh
.
# keys.sh
export SECRET_KEY_BASE=52b57...
To use different domain you need to update domain: my-domain.vagrant
inside
config/rubber/rubber.yml
.
To use different subdomain you need to set up in Vagrant file using define
.