Compare

Lang Compare

Ruby

Web

Overview

Dynamic, object-oriented language designed for developer happiness. Known for Ruby on Rails and an emphasis on convention over configuration.

Resources

Popular learning and reference links:

Installation & Getting Started

Ruby is pre-installed on macOS. Use a version manager like rbenv or asdf for managing versions.

# macOS (Homebrew)
brew install ruby

# rbenv — recommended version manager
brew install rbenv ruby-build
rbenv install 3.3.0
rbenv global 3.3.0

# Or use asdf
asdf plugin add ruby
asdf install ruby 3.3.0
asdf global ruby 3.3.0

# Ubuntu / Debian
sudo apt install ruby-full

# Check version
ruby --version
# IRB — interactive Ruby (REPL)
irb

# Pry — enhanced REPL
gem install pry
pry

# Run a script
ruby script.rb

# Quick one-liner
ruby -e "puts 'Hello, Ruby!'"

# Run with warnings
ruby -w script.rb

Project Scaffolding

Use bundler for dependency management and Rails or Sinatra for web projects.

# Basic Ruby project
mkdir my-project && cd my-project
bundle init            # Creates Gemfile

# Ruby on Rails — full-stack web framework
gem install rails
rails new my-app
cd my-app && rails server

# Rails with specific options
rails new my-api --api                  # API-only
rails new my-app --database=postgresql  # PostgreSQL

# Sinatra — lightweight web framework
mkdir my-app && cd my-app
bundle init
# Add: gem 'sinatra' to Gemfile
bundle install
ruby app.rb

# Gem scaffold (create a new gem/library)
bundle gem my-gem
# Creates lib/, spec/, Gemfile, .gemspec

Package Management

RubyGems is the package manager. Bundler manages project dependencies via a Gemfile.

# Install a gem globally
gem install rails
gem install pry

# Bundler — project dependency management
gem install bundler

# Install all dependencies from Gemfile
bundle install

# Add a gem to the project
bundle add sidekiq

# Update dependencies
bundle update
bundle update rails  # Update specific gem

# Execute a command in bundle context
bundle exec rails server
bundle exec rspec
# Gemfile
source 'https://rubygems.org'

ruby '3.3.0'

gem 'rails', '~> 7.1'
gem 'pg'
gem 'puma'
gem 'redis'

group :development, :test do
  gem 'rspec-rails'
  gem 'pry-byebug'
  gem 'factory_bot_rails'
end

group :development do
  gem 'rubocop', require: false
end

Tooling & Formatter/Linter

RuboCop is the standard linter and formatter. Sorbet adds static typing.

# RuboCop — linter & formatter
gem install rubocop
rubocop                    # Lint
rubocop -a                 # Auto-fix safe cops
rubocop -A                 # Auto-fix all cops (including unsafe)

# RuboCop with Rails extensions
gem install rubocop-rails rubocop-rspec rubocop-performance
rubocop --require rubocop-rails

# Standard Ruby — opinionated RuboCop config (zero config)
gem install standard
standardrb                 # Lint
standardrb --fix           # Auto-fix

# Sorbet — static type checker
gem install sorbet sorbet-runtime
srb init                   # Initialize in project
srb tc                     # Type check

# Steep — alternative type checker (uses RBS)
gem install steep
steep check
# .rubocop.yml
require:
  - rubocop-rails
  - rubocop-rspec

AllCops:
  NewCops: enable
  TargetRubyVersion: 3.3

Style/StringLiterals:
  EnforcedStyle: single_quotes

Metrics/MethodLength:
  Max: 15

Build & Compile Model

Interpreted. Ruby code is parsed into an AST, compiled to bytecode, and executed by YARV (Yet Another Ruby VM). JIT compilation available since Ruby 2.6.

# No compile step — run directly
ruby app.rb

# Ruby with JIT (YJIT — default since Ruby 3.3)
ruby --yjit app.rb

# Check if YJIT is enabled
ruby -e "puts RubyVM::YJIT.enabled?"

# MJIT (older JIT, deprecated in 3.3)
ruby --mjit app.rb

# Precompile to bytecode (for inspection)
ruby --dump=insns script.rb

Execution model:

  • Source → Lexer → Parser → AST → YARV bytecode → VM execution
  • YJIT (Ruby 3.1+): Lazy Basic Block Versioning JIT compiler, written in Rust
  • YJIT is enabled by default in Ruby 3.3+ and gives 15-25% speedup for Rails apps
  • mruby: Lightweight Ruby for embedded systems
  • TruffleRuby: GraalVM-based implementation with aggressive JIT
  • JRuby: Ruby on the JVM — true threads, Java interop

Libraries & Frameworks

Testing

RSpec and Minitest are the two main testing frameworks. RSpec is more popular in the Rails community.

# RSpec
RSpec.describe 'Math' do
  it 'adds numbers' do
    expect(1 + 2).to eq(3)
  end

  it 'handles arrays' do
    expect([1, 2, 3]).to include(2)
    expect([1, 2, 3]).to have_attributes(length: 3)
  end

  context 'with negative numbers' do
    it 'returns negative sum' do
      expect(-1 + -2).to eq(-3)
    end
  end
end
# Minitest
require 'minitest/autorun'

class MathTest < Minitest::Test
  def test_addition
    assert_equal 3, 1 + 2
  end

  def test_array_includes
    assert_includes [1, 2, 3], 2
  end
end
# Run tests
bundle exec rspec                # RSpec
bundle exec rspec spec/models/   # Specific directory
ruby -Itest test/math_test.rb    # Minitest
bundle exec rails test           # Rails default (Minitest)

# With coverage
# Add simplecov gem, then: COVERAGE=true bundle exec rspec

Debugging

debug gem (Ruby 3.1+ built-in) and Pry are the primary debugging tools.

# Built-in debug gem (Ruby 3.1+)
require 'debug'

def process(data)
  binding.break  # Breakpoint — opens debugger
  data.map { |x| x * 2 }
end

# Pry — enhanced REPL debugger
require 'pry'

def process(data)
  binding.pry  # Breakpoint — opens Pry session
  data.map { |x| x * 2 }
end

# Basic debugging
puts variable.inspect
p variable              # Shorthand for puts .inspect
pp complex_object       # Pretty print
warn "debug message"    # Print to stderr
# debug gem commands (at breakpoint)
# n / next      — step over
# s / step      — step into
# c / continue  — continue execution
# bt            — backtrace
# info          — show local variables
# eval expr     — evaluate expression

# Start script in debug mode
ruby -r debug script.rb
rdbg script.rb

# VS Code — install "Ruby LSP" extension
# launch.json:
{
  "type": "ruby_lsp",
  "request": "launch",
  "program": "${workspaceFolder}/app.rb"
}

# Byebug (older, still used)
gem install byebug
# require 'byebug'; byebug  # in code

Variables

Variables don’t need type declarations. Naming conventions indicate scope: local, @instance, @@class, $global, CONSTANT.

# Local variables
name = 'Alice'
age = 30
is_active = true

# Constants (uppercase start)
MAX_SIZE = 100
PI = 3.14159

# Instance variables
@name = 'Alice'

# Class variables
@@count = 0

# Global variables (avoid)
$debug = true

# Multiple assignment
a, b, c = 1, 2, 3
first, *rest = [1, 2, 3, 4]  # first=1, rest=[2,3,4]

# Frozen (immutable) strings
name = 'Alice'.freeze
# name << ' Bob'  # => FrozenError

# String interpolation
greeting = "Hello, #{name}!"

Types

Ruby is dynamically typed. Everything is an object — even integers and booleans. Optional type annotations via RBS or Sorbet.

# Core types — all are objects
42.class          # Integer
3.14.class        # Float
'hello'.class     # String
true.class        # TrueClass
nil.class         # NilClass
:symbol.class     # Symbol
[1, 2].class      # Array
{ a: 1 }.class    # Hash
(1..5).class      # Range

# Type checking
42.is_a?(Integer)     # true
42.is_a?(Numeric)     # true (parent class)
'hi'.respond_to?(:upcase)  # true (duck typing)

# Symbols — immutable, interned strings
:name
:status
:'multi word'

# Ranges
(1..5).to_a       # [1, 2, 3, 4, 5] (inclusive)
(1...5).to_a      # [1, 2, 3, 4] (exclusive)

# RBS — type signature files (Ruby 3.0+)
# sig/app.rbs:
# class User
#   attr_reader name: String
#   def initialize: (String name) -> void
# end

Data Structures

Arrays, Hashes, Sets, Structs, and Data (Ruby 3.2+) are the core data structures.

# Arrays — ordered, mixed types
arr = [1, 'two', :three]
arr.push(4)          # or arr << 4
arr.pop              # Remove last
arr.shift            # Remove first
arr.unshift(0)       # Prepend
arr.include?(1)      # true
arr.flatten          # Flatten nested arrays
arr.compact          # Remove nils
arr.uniq             # Remove duplicates

# Hashes — key-value pairs
hash = { name: 'Alice', age: 30 }
hash[:email] = 'a@b.com'
hash.delete(:age)
hash.keys            # [:name, :email]
hash.values
hash.fetch(:name, 'default')
hash.merge(other_hash)

# Set
require 'set'
set = Set.new([1, 2, 3, 3])  # {1, 2, 3}
set.add(4)
set.include?(2)      # true
set & other_set       # Intersection
set | other_set       # Union

# Struct — lightweight data class
User = Struct.new(:name, :age, keyword_init: true)
user = User.new(name: 'Alice', age: 30)

# Data (Ruby 3.2+) — immutable value object
Point = Data.define(:x, :y)
p = Point.new(x: 1, y: 2)
# p.x = 3  # => NoMethodError (immutable)

# OpenStruct — dynamic attributes
require 'ostruct'
obj = OpenStruct.new(name: 'Alice')
obj.age = 30

Functions

Methods are defined with def. Ruby also supports blocks, procs, and lambdas.

# Method definition
def greet(name)
  "Hello, #{name}!"  # Implicit return (last expression)
end

# Default parameters
def power(base, exp = 2)
  base ** exp
end

# Keyword arguments
def create_user(name:, age:, role: 'user')
  { name: name, age: age, role: role }
end
create_user(name: 'Alice', age: 30)

# Splat (rest) parameters
def sum(*nums)
  nums.reduce(0, :+)
end

# Double splat (keyword rest)
def config(**options)
  options
end

# Blocks
def with_logging
  puts 'Start'
  yield  # Execute the block
  puts 'End'
end
with_logging { puts 'Working...' }

# Procs and Lambdas
double = ->(x) { x * 2 }    # Lambda
double.call(5)                # 10
double.(5)                    # 10 (shorthand)

square = proc { |x| x ** 2 } # Proc
square.call(4)                # 16

Conditionals

Ruby has if/elsif/else, unless, case/when, and pattern matching (Ruby 3.0+).

# If / elsif / else
if x > 0
  puts 'positive'
elsif x == 0
  puts 'zero'
else
  puts 'negative'
end

# Inline if / unless
puts 'positive' if x > 0
puts 'not zero' unless x.zero?

# Ternary
label = x > 0 ? 'positive' : 'non-positive'

# Case / when
case color
when 'red'
  puts '#f00'
when 'blue', 'navy'
  puts '#00f'
else
  puts 'unknown'
end

# Pattern matching (Ruby 3.0+)
case [1, 2, 3]
in [Integer => a, Integer => b, *rest]
  puts "a=#{a}, b=#{b}"
end

case { name: 'Alice', age: 30 }
in { name: String => name, age: (18..) => age }
  puts "#{name} is #{age}"
end

# Guard clauses (idiomatic Ruby)
def process(data)
  return if data.nil?
  return unless data.valid?
  # ... process data
end

Loops

Ruby favors iterators and blocks over traditional loops. each, map, select are idiomatic.

# each — the Ruby way
[1, 2, 3].each { |x| puts x }

# each with index
['a', 'b', 'c'].each_with_index do |item, i|
  puts "#{i}: #{item}"
end

# times
5.times { |i| puts i }

# upto / downto
1.upto(5) { |i| puts i }
5.downto(1) { |i| puts i }

# Ranges
(1..5).each { |i| puts i }

# while / until
n = 0
while n < 3
  n += 1
end

until n == 0
  n -= 1
end

# loop (infinite)
loop do
  break if some_condition
end

# Iterators (functional-style)
[1, 2, 3].map { |x| x * 2 }       # [2, 4, 6]
[1, 2, 3, 4].select { |x| x.even? } # [2, 4]
[1, 2, 3].reduce(0) { |sum, x| sum + x }  # 6
[1, 2, 3].reduce(:+)                # 6 (shorthand)

Generics & Type System

Ruby has no native generics. It relies on duck typing. Static type checking is available via RBS (built-in) and Sorbet (third-party).

# Duck typing — no generics needed
def first(items)
  items.first
end
first([1, 2, 3])    # 1
first('hello')      # 'h'

# respond_to? — check capabilities
def process(obj)
  if obj.respond_to?(:each)
    obj.each { |x| puts x }
  else
    puts obj
  end
end

# Comparable / Enumerable — mixin-based polymorphism
class Temperature
  include Comparable

  attr_reader :degrees

  def initialize(degrees)
    @degrees = degrees
  end

  def <=>(other)
    degrees <=> other.degrees
  end
end

temps = [Temperature.new(30), Temperature.new(20)]
temps.sort  # Works via Comparable
temps.min   # Works via Comparable
# RBS — type signatures (Ruby 3.0+)
# sig/app.rbs
# class Array[unchecked out Elem]
#   def first: () -> Elem?
#   def map: [U] () { (Elem) -> U } -> Array[U]
# end

# Sorbet — static type checker
# typed: strict
class Box
  extend T::Sig
  extend T::Generic

  Elem = type_member

  sig { params(value: Elem).void }
  def initialize(value)
    @value = T.let(value, Elem)
  end
end

Inheritance & Composition

Single inheritance with modules (mixins) for composition. Ruby strongly favors composition over inheritance.

# Class inheritance
class Animal
  attr_reader :name

  def initialize(name)
    @name = name
  end

  def speak
    "#{name} makes a sound"
  end
end

class Dog < Animal
  def speak
    "#{name} barks"
  end
end

# Super
class Puppy < Dog
  def initialize(name)
    super(name)
    @young = true
  end
end

# Modules (mixins) — composition
module Serializable
  def to_json
    JSON.generate(to_h)
  end
end

module Loggable
  def log
    puts "#{self.class}: #{inspect}"
  end
end

class User < Animal
  include Serializable  # Instance methods
  include Loggable
  extend ClassMethods   # Class methods

  def to_h
    { name: name }
  end
end

# Abstract-like pattern (no built-in abstract)
module Abstract
  def self.included(klass)
    klass.instance_method(:perform)
  rescue NameError
    raise NotImplementedError, "#{klass} must implement #perform"
  end
end

# Open classes — extend existing classes
class String
  def shout
    upcase + '!!!'
  end
end
'hello'.shout  # "HELLO!!!"

Functional Patterns

Ruby has strong functional programming support with blocks, procs, lambdas, and Enumerable methods.

# Higher-order functions
def apply(fn, x)
  fn.call(x)
end
double = ->(x) { x * 2 }
apply(double, 5)  # 10

# Map, select, reduce
nums = [1, 2, 3, 4, 5]
squared = nums.map { |x| x ** 2 }
evens = nums.select(&:even?)
sum = nums.reduce(:+)

# Method reference with &
['hello', 'world'].map(&:upcase)  # ["HELLO", "WORLD"]

# Closures
def counter(start = 0)
  count = start
  -> { count += 1 }
end
inc = counter
inc.call  # 1
inc.call  # 2

# Currying
add = ->(a, b) { a + b }
add5 = add.curry.(5)
add5.(3)  # 8

# Chaining with Enumerable
result = (1..10)
  .select(&:odd?)
  .map { |x| x ** 2 }
  .reject { |x| x > 50 }
  .sum

# Lazy evaluation
(1..Float::INFINITY)
  .lazy
  .select(&:odd?)
  .map { |x| x ** 2 }
  .first(5)  # [1, 9, 25, 49, 81]

# Proc composition (Ruby 2.6+)
double = ->(x) { x * 2 }
add_one = ->(x) { x + 1 }
double_then_add = add_one << double  # compose
double_then_add.(5)  # 11

Concurrency

GIL (GVL) limits true parallelism in CRuby. Ractors (Ruby 3.0+) provide actor-based parallelism. Fibers enable cooperative concurrency.

# Threads — concurrent but GVL-limited for CPU
threads = 5.times.map do |i|
  Thread.new { puts "Thread #{i}" }
end
threads.each(&:join)

# Mutex — thread safety
mutex = Mutex.new
counter = 0
10.times.map do
  Thread.new { mutex.synchronize { counter += 1 } }
end.each(&:join)

# Fibers — cooperative concurrency
fiber = Fiber.new do
  Fiber.yield 'first'
  Fiber.yield 'second'
  'third'
end
fiber.resume  # 'first'
fiber.resume  # 'second'
fiber.resume  # 'third'
# Ractors (Ruby 3.0+) — true parallelism
ractors = 4.times.map do |i|
  Ractor.new(i) do |n|
    (1..10000).sum * n
  end
end
results = ractors.map(&:take)

# Fiber Scheduler (Ruby 3.0+) — async I/O
# Used by async gem for non-blocking I/O
require 'async'

Async do |task|
  task.async { fetch_url('https://example.com/a') }
  task.async { fetch_url('https://example.com/b') }
end

# Sidekiq — background job processing
class HardWorker
  include Sidekiq::Job
  def perform(name, count)
    # Runs in a separate process
  end
end
HardWorker.perform_async('Bob', 5)

Modules & Imports

Ruby uses require to load files and modules for namespacing. Bundler and autoloading (Zeitwerk in Rails) manage dependencies.

# require — load a file once
require 'json'
require 'net/http'

# require_relative — load relative to current file
require_relative 'lib/helper'
require_relative '../config/database'

# Modules as namespaces
module MyApp
  module Models
    class User
      attr_accessor :name
    end
  end
end
user = MyApp::Models::User.new

# Include module methods
module Greetable
  def greet
    "Hello, #{name}!"
  end
end

class User
  include Greetable
  attr_reader :name
  def initialize(name) = @name = name
end
# Zeitwerk autoloading (Rails default)
# File: app/models/user.rb → class User
# File: app/services/auth/login.rb → class Auth::Login
# No require needed — autoloaded by convention

# Bundler — loads gems from Gemfile
require 'bundler/setup'
Bundler.require(:default)

# load — always re-executes file (useful for reloading)
load 'config.rb'

Error Handling

Ruby uses begin/rescue/ensure for exception handling. raise throws exceptions.

# begin / rescue / ensure
begin
  data = JSON.parse(input)
rescue JSON::ParserError => e
  puts "JSON error: #{e.message}"
rescue StandardError => e
  puts "Error: #{e.message}"
ensure
  puts 'always runs'
end

# Inline rescue (simple cases)
value = Integer(input) rescue 0

# Method-level rescue
def divide(a, b)
  a / b
rescue ZeroDivisionError
  puts 'Cannot divide by zero'
  nil
end

# Raising exceptions
def validate(age)
  raise ArgumentError, 'Age must be positive' if age < 0
  raise 'Something went wrong'  # RuntimeError
end

# Custom exceptions
class ValidationError < StandardError
  attr_reader :errors

  def initialize(errors, message = 'Validation failed')
    @errors = errors
    super(message)
  end
end

# Retry
attempts = 0
begin
  attempts += 1
  make_request
rescue Net::TimeoutError
  retry if attempts < 3
end

Memory Management

Garbage collected. CRuby uses a generational, incremental, compacting GC. Objects are allocated on the heap with automatic cleanup.

# Memory is automatically managed
data = (1..10000).to_a  # Allocated
data = nil               # Eligible for GC

# Check memory usage
puts "RSS: #{`ps -o rss= -p #{Process.pid}`.strip.to_i / 1024} MB"

# ObjectSpace — inspect live objects
require 'objspace'
ObjectSpace.count_objects
# => { TOTAL: 30000, FREE: 5000, T_OBJECT: 1200, ... }

ObjectSpace.memsize_of_all  # Total memory of all objects

# GC control
GC.start          # Force garbage collection
GC.disable         # Disable GC (use carefully)
GC.enable
GC.stat            # GC statistics

# WeakRef
require 'weakref'
obj = Object.new
weak = WeakRef.new(obj)
weak.weakref_alive?  # true

Ruby GC details:

  • Generational: Young objects collected more frequently than old ones
  • Incremental: GC work is spread across multiple steps to reduce pauses
  • Compacting (Ruby 2.7+): GC.compact reduces memory fragmentation
  • Variable Width Allocation (Ruby 3.2+): Objects can be embedded in heap slots
  • Tune via env vars: RUBY_GC_HEAP_INIT_SLOTS, RUBY_GC_MALLOC_LIMIT, etc.
  • Jemalloc: Alternative allocator that reduces memory fragmentation (LD_PRELOAD=libjemalloc.so)

Performance Profiling

ruby-prof, stackprof, and benchmark are the main profiling tools. rack-mini-profiler for web apps.

# Benchmark (stdlib)
require 'benchmark'

Benchmark.bm do |x|
  x.report('sort') { (1..10000).to_a.shuffle.sort }
  x.report('sort!') { (1..10000).to_a.shuffle.sort! }
end

# Benchmark.measure
result = Benchmark.measure { expensive_operation }
puts result  # user, system, total, real time

# Benchmark/ips — iterations per second
require 'benchmark/ips'
Benchmark.ips do |x|
  x.report('map') { [1,2,3].map { |i| i * 2 } }
  x.report('each') { r = []; [1,2,3].each { |i| r << i * 2 }; r }
  x.compare!
end
# StackProf — sampling profiler
gem install stackprof
# In code: StackProf.run(mode: :cpu, out: 'tmp/stackprof.dump') { work }
stackprof tmp/stackprof.dump --text

# ruby-prof — deterministic profiler
gem install ruby-prof
ruby-prof script.rb

# rack-mini-profiler — web request profiling
gem 'rack-mini-profiler'
# Shows timing badge on every page

# memory_profiler — memory allocation tracking
gem install memory_profiler
# MemoryProfiler.report { work }.pretty_print

# derailed_benchmarks — Rails memory/speed benchmarks
gem install derailed_benchmarks
bundle exec derailed exec perf:mem
bundle exec derailed exec perf:ips

Interop

Ruby interops with C via native extensions, FFI, and system commands. JRuby provides Java interop.

# FFI — call C libraries
require 'ffi'

module LibM
  extend FFI::Library
  ffi_lib 'libm'  # Or FFI::Library::LIBC
  attach_function :sqrt, [:double], :double
  attach_function :ceil, [:double], :double
end

LibM.sqrt(16.0)  # 4.0
LibM.ceil(1.3)   # 2.0

# System commands
output = `ls -la`               # Backticks
output = %x(ls -la)             # %x syntax
system('echo', 'hello')         # Returns true/false
result = IO.popen('ls') { |io| io.read }

# Open3 — capture stdout, stderr, status
require 'open3'
stdout, stderr, status = Open3.capture3('git', 'status')
# C extensions (native)
# extconf.rb:
require 'mkmf'
create_makefile('my_extension')

# JRuby — Java interop
# java_import java.util.ArrayList
# list = ArrayList.new
# list.add('hello')

# Inline C (rice gem for C++ / fiddle for C)
require 'fiddle'
libm = Fiddle.dlopen('libm.so.6')
sqrt = Fiddle::Function.new(
  libm['sqrt'],
  [Fiddle::TYPE_DOUBLE],
  Fiddle::TYPE_DOUBLE
)
sqrt.call(16.0)  # 4.0

# JSON / YAML / CSV — stdlib
require 'json'
require 'yaml'
require 'csv'
data = JSON.parse(File.read('data.json'))

Packaging & Distribution

Publish gems to RubyGems.org. Deploy apps via Docker, Heroku, or platform services.

# Create a gem
bundle gem my-gem
# Edit my-gem.gemspec, add code to lib/

# Build and publish
gem build my-gem.gemspec
gem push my-gem-1.0.0.gem

# Or with bundler
bundle exec rake release
# Tags, builds, and pushes to RubyGems.org
# my-gem.gemspec
Gem::Specification.new do |spec|
  spec.name          = 'my-gem'
  spec.version       = '1.0.0'
  spec.authors       = ['Your Name']
  spec.summary       = 'A useful gem'
  spec.files         = Dir['lib/**/*']
  spec.require_paths = ['lib']

  spec.add_dependency 'httparty', '~> 0.21'
  spec.add_development_dependency 'rspec', '~> 3.12'
end
# Docker deployment
FROM ruby:3.3-alpine
RUN apk add --no-cache build-base postgresql-dev
WORKDIR /app
COPY Gemfile Gemfile.lock ./
RUN bundle install --without development test
COPY . .
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

# Heroku
heroku create my-app
git push heroku main

# Kamal — Rails deployment tool (Docker-based)
gem install kamal
kamal setup
kamal deploy