1klap
Posted on February 2, 2023
Here's how i setup my rails 7 deployments, starting from a fresh ubuntu 22.04 install and root user ssh access.
Part of the stack will be rbenv, nginx, passenger, postgres and capistrano, with options to set up yarn and node as well.
Replace vim
with nano
if you prefer to use the worse text editor.
Prereq
- DNS is set up
- you have a SSH key generated (if not, simply run ssh-keygen)
User setup
Connect as root [local]
$ ssh root@<your-url>
Add non-root user [remote]
$ adduser devops
# set password 'devops_user_password'
$ adduser devops sudo
$ exit
Connect as devops (for all steps below) [local]
# Run the first command below only once
$ ssh-copy-id devops@<your-url>
$ ssh devops@<your-url>
Disallow root login via SSH [remote]
$ sudo vim /etc/ssh/sshd_config
# Set line 'PermitRootLogin yes' to 'PermitRootLogin no'
$ sudo service sshd restart
Set hostname [remote]
$ sudo hostnamectl set-hostname <your-url>
$ sudo hostnamectl # check if it is set
Install system dependencies [remote]
$ sudo apt update
$ sudo apt upgrade
$ sudo apt install git build-essential libssl-dev zlib1g-dev libyaml-dev
Install rbenv, ruby, bundler [remote]
$ git clone https://github.com/rbenv/rbenv.git ~/.rbenv
$ echo 'eval "$(~/.rbenv/bin/rbenv init - bash)"' >> ~/.bashrc
$ exec $SHELL
$ git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build
$ git clone https://github.com/rbenv/rbenv-vars.git "$(rbenv root)"/plugins/rbenv-vars
$ rbenv install 3.2.0
$ rbenv global 3.2.0
# Test complete install with rbenv-doctor
$ curl -fsSL https://github.com/rbenv/rbenv-installer/raw/main/bin/rbenv-doctor | bash
$ ruby -v # Shows ruby version number if correctly installed
$ gem install bundler # You might be prompted to update some gems, see command below
$ gem update --system 3.4.6 # Check if this is recommended after bundler installation
$ bundle -v # Shows bundler version number if correctly installed
Install yarn, nvm, node (skip if not required) [remote]
If you generated your rails project with default javascript configuration (i.e. importmaps), then you might not need yarn, nvm and node at all.
$ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/yarn-archive-keyring.gpg
$ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
$ sudo apt install yarn
$ yarn -v # Shows yarn version number if correctly installed
$ curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash
$ exec $SHELL
$ nvm install 16
$ node -v # Shows node version number if correctly installed
$ # npm install -g esbuild # Uncomment if you package javascript with esbuild
$ # esbuild --version
$ # npm install -g typescript # If needed
$ # npm install -g sass # If needed
Install nginx, passenger [remote]
$ curl https://oss-binaries.phusionpassenger.com/auto-software-signing-gpg-key.txt | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/phusion-keyring.gpg >/dev/null
$ sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger jammy main > /etc/apt/sources.list.d/passenger.list'
$ sudo apt update
$ sudo apt install nginx-extras libnginx-mod-http-passenger
$ sudo vim /etc/nginx/conf.d/mod-http-passenger.conf
# Change the path to rbenv ruby
passenger_ruby /home/devops/.rbenv/shims/ruby;
$ sudo service nginx restart
$ sudo service nginx status
$ sudo /usr/bin/passenger-config validate-install # Looks ok?
$ sudo /usr/sbin/passenger-memory-stats # Looks ok?
$ sudo rm /etc/nginx/sites-enabled/default
$ sudo vim /etc/nginx/sites-available/<your-url>
# Example conf
server {
server_name <your-url>;
# server_name _;
root /home/devops/<your-url>/current/public;
#client_max_body_size 25m; # Set max upload size
passenger_enabled on;
passenger_app_env production;
# Uncomment if you use ActionCable and/or Turbo Streams
# location /cable {
# passenger_app_group_name APPNAME_ws;
# passenger_force_max_concurrent_requests_per_process 0;
# }
location ~ ^/assets {
expires max;
gzip_static on;
}
listen 80;
listen [::]:80;
}
$ sudo ln -s /etc/nginx/sites-available/<your-url> /etc/nginx/sites-enabled/<your-url>
$ sudo service nginx restart
# Check error log if required (like after deploy)
$ sudo vim /var/log/nginx/error.log
Install SSL cert
$ sudo apt install snapd
$ sudo snap install core; sudo snap refresh core
$ sudo snap install --classic certbot
$ sudo certbot --nginx
# Follow instructions
Enable HTTP2 on nginx
$ sudo vim /etc/nginx/sites-available/<your-url>
# Add http2 to listen command, see below
# listen [::]:443 ssl http2 ipv6only=on;
# listen 443 ssl http2;
$ sudo service nginx restart
Install postgresql [remote]
$ sudo apt install postgresql postgresql-contrib libpq-dev
$ sudo service postgresql@14-main status
$ sudo su - postgres
# execute the following as postgres user
$ createuser --pwprompt postgres_production
# Enter password
$ createdb -O postgres_production <your-app-name>_production
# check db existence with 'psql' then '\l', quit with '\q'
$ exit
Set env vars [remote]
$ mkdir ~/<your-url>
$ cd ~/<your-url>
$ vim .rbenv-vars
# Required:
RAILS_MASTER_KEY=<master.key content>
SECRET_KEY_BASE=<some random sequence> # use local bundle exec rake secret
RAILS_ENV=production
capistrano setup (once per project) [local]
Add capistrano gems to development
group in your Gemfile
group :development do
...
# Deployment
gem 'capistrano', "~> 3.17", require: false
gem 'capistrano-rails', "~> 1.6", ">= 1.6.2", require: false
gem 'capistrano-passenger', "~> 0.2", ">= 0.2.1", require: false
gem 'capistrano-rbenv', "~> 2.2", require: false
gem 'ed25519', '>= 1.2', '< 2.0', require: false
gem 'bcrypt_pbkdf', '>= 1.0', '< 2.0', require: false
...
end
$ bundle install
$ bundle exec cap install
in the file Capfile
require the following modules
require "capistrano/rbenv"
require "capistrano/rails"
require "capistrano/passenger"
in the file config/deploy.rb
set the following configs
add your public SSH key to github
set :rbenv_ruby, '3.2.0'
set :application, "<your-url>"
set :repo_url, "git@github.com:<your_github_handle>/<your_github_repo>.git"
set :deploy_to, "~/#{fetch :application}"
# Issue with propshaft as asset pipwlinw
# See: https://github.com/capistrano/rails/issues/257
# Workaround
set :assets_manifests, -> {
[release_path.join("public", fetch(:assets_prefix), '.manifest.json')]
}
in the file config/deploy.rb
set the following configs
server '<your-url>', user: 'devops', roles: %w{app db web}
set :branch, "main"
Deploy [local]
# if ssh-key is not recognized run the following two commands
$ eval `ssh-agent`
$ ssh-add ~/.ssh/id_rsa
# Deployment
$ cap production deploy
Security (optional but recommended) [remote]
Set up firewall
$ sudo ufw default allow outgoing
$ sudo ufw default deny incoming
$ sudo ufw allow ssh
$ sudo ufw allow 80/tcp comment 'accept http'
$ sudo ufw allow 443/tcp comment 'accept https'
$ sudo ufw enable
$ sudo ufw status
Set up fail2ban
$ sudo sudo apt update; sudo apt upgrade
$ sudo apt install fail2ban
$ # sudo apt install sendmail # do this only if you want to config sending fail2ban reports
$ sudo systemctl enable fail2ban
$ sudo systemctl start fail2ban
# .local files overwrite values from .conf files
$ sudo cp /etc/fail2ban/fail2ban.conf /etc/fail2ban/fail2ban.local
$ sudo vim /etc/fail2ban/fail2ban.local
$ sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
# This is the main config file, defaults for sshd are ok
$ sudo vim /etc/fail2ban/jail.local
$ sudo fail2ban-client status
# detail of sshd jail
$ sudo fail2ban-client status sshd
How did your deployment go? Anything you like to do differently? Leave a comment below!
Posted on February 2, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.