Example: Ruby Application Deployment
Ruby Application Deployment
Deploy a Ruby web application with Puma, managed by systemd, with log rotation and Nginx integration.
Directory Structure
cookbooks/
ruby-app/
default.rb
templates/
puma.service.erb
puma.rb.erb
env.erb
nodes/
app01.json
Node Attributes
{
"app": {
"name": "myapp",
"user": "deploy",
"uid": 1001,
"deploy_to": "/var/www/myapp",
"repository": "https://github.com/example/myapp.git",
"revision": "main",
"ruby_version": "3.2.2",
"puma_workers": 2,
"puma_threads_min": 1,
"puma_threads_max": 5,
"puma_port": 3000,
"environment": "production",
"secret_key_base": "abc123..."
}
}
Recipe
# cookbooks/ruby-app/default.rb
app = node['app']
user = app['user']
home = "/home/#{user}"
deploy_to = app['deploy_to']
group user do
gid app['uid']
end
user user do
uid app['uid']
gid app['uid']
home home
shell '/bin/bash'
create_home true
end
%w[releases shared shared/log shared/tmp shared/config].each do |dir|
directory "#{deploy_to}/#{dir}" do
owner user
group user
mode '0755'
end
end
package 'build-essential' do
action :install
end
gem_package 'bundler' do
version '2.4.0'
end
git "#{deploy_to}/releases/current" do
repository app['repository']
revision app['revision']
user user
end
template "#{deploy_to}/shared/config/puma.rb" do
source 'templates/puma.rb.erb'
owner user
group user
mode '0644'
variables(
deploy_to: deploy_to,
workers: app['puma_workers'],
threads_min: app['puma_threads_min'],
threads_max: app['puma_threads_max'],
port: app['puma_port']
)
notifies :restart, 'service[puma]'
end
template "#{deploy_to}/shared/.env" do
source 'templates/env.erb'
owner user
group user
mode '0600'
variables(
environment: app['environment'],
secret_key_base: app['secret_key_base'],
port: app['puma_port']
)
notifies :restart, 'service[puma]'
end
template '/etc/systemd/system/puma.service' do
source 'templates/puma.service.erb'
owner 'root'
group 'root'
mode '0644'
variables(
user: user,
deploy_to: deploy_to,
app_name: app['name']
)
notifies :restart, 'service[puma]'
end
execute 'bundle install' do
command "cd #{deploy_to}/releases/current && bundle install --deployment --without development test"
user user
not_if "cd #{deploy_to}/releases/current && bundle check"
end
link "#{deploy_to}/current" do
to "#{deploy_to}/releases/current"
end
execute 'systemctl daemon-reload' do
command 'systemctl daemon-reload'
end
service 'puma' do
action [:enable, :start]
end
Templates
puma.rb.erb
directory "<%= @deploy_to %>/releases/current"
bind "tcp://0.0.0.0:<%= @port %>"
workers <%= @workers %>
threads <%= @threads_min %>, <%= @threads_max %>
stdout_redirect "<%= @deploy_to %>/shared/log/puma.stdout.log", "<%= @deploy_to %>/shared/log/puma.stderr.log", true
pidfile "<%= @deploy_to %>/shared/tmp/puma.pid"
state_path "<%= @deploy_to %>/shared/tmp/puma.state"
puma.service.erb
[Unit]
Description=Puma HTTP Server for <%= @app_name %>
After=network.target
[Service]
Type=simple
User=<%= @user %>
WorkingDirectory=<%= @deploy_to %>/releases/current
ExecStart=/usr/local/bin/bundle exec puma -C <%= @deploy_to %>/shared/config/puma.rb
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
env.erb
RAILS_ENV=<%= @environment %>
SECRET_KEY_BASE=<%= @secret_key_base %>
PORT=<%= @port %>
Running
itamae ssh -j nodes/app01.json -h app01.example.com cookbooks/ruby-app/default.rb