Rails 7: production deploy from scratch (Ubuntu 22.04 edition)

1klap

1klap

Posted on February 2, 2023

Rails 7: production deploy from scratch (Ubuntu 22.04 edition)

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>
Enter fullscreen mode Exit fullscreen mode

Add non-root user [remote]

$ adduser devops
  # set password 'devops_user_password'
$ adduser devops sudo
$ exit
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Disallow root login via SSH [remote]

$ sudo vim /etc/ssh/sshd_config
  # Set line 'PermitRootLogin yes' to 'PermitRootLogin no'
$ sudo service sshd restart
Enter fullscreen mode Exit fullscreen mode

Set hostname [remote]

$ sudo hostnamectl set-hostname <your-url>
$ sudo hostnamectl # check if it is set
Enter fullscreen mode Exit fullscreen mode

Install system dependencies [remote]

$ sudo apt update
$ sudo apt upgrade
$ sudo apt install git build-essential libssl-dev zlib1g-dev libyaml-dev
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
$ 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
$ bundle install
$ bundle exec cap install
Enter fullscreen mode Exit fullscreen mode

in the file Capfile require the following modules

require "capistrano/rbenv"
require "capistrano/rails"
require "capistrano/passenger"
Enter fullscreen mode Exit fullscreen mode

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')]
}
Enter fullscreen mode Exit fullscreen mode

in the file config/deploy.rb set the following configs

server '<your-url>', user: 'devops', roles: %w{app db web}
set :branch, "main"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

How did your deployment go? Anything you like to do differently? Leave a comment below!

💖 💪 🙅 🚩
1klap
1klap

Posted on February 2, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related