Deploying a Ruby on Rails application to production requires careful consideration of process management, reliability, and automation. While there are many deployment strategies available, using systemd for process management combined with Ansible for automation provides a robust, maintainable solution that integrates well with modern Linux distributions.
This guide will walk you through setting up a Rails application as a systemd service using Ansible automation, including both automated and manual approaches.
systemd provides several advantages for Rails applications:
Ansible offers:
Before getting started, ensure you have:
Here’s a complete Ansible playbook to set up your Rails application as a systemd service:
---
* name: Deploy Rails Application with systemd
hosts: web_servers
become: yes
vars:
rails_root: "/opt/myapp"
rails_user: "webuser"
rails_group: "webuser"
rails_port: 3000
rails_environment: "production"
app_name: "myapp"
tasks:
* name: Create application directory
file:
path: "{{ rails_root }}"
state: directory
owner: "{{ rails_user }}"
group: "{{ rails_group }}"
mode: '0755'
* name: Create systemd service directory
file:
path: /etc/systemd/system
state: directory
* name: Create Rails systemd service
template:
src: rails-web.service.j2
dest: /etc/systemd/system/{{ app_name }}.service
owner: root
group: root
mode: '0644'
notify: reload systemd
* name: Create log directory
file:
path: "{{ rails_root }}/log"
state: directory
owner: "{{ rails_user }}"
group: "{{ rails_group }}"
mode: '0755'
* name: Create PID directory
file:
path: "{{ rails_root }}/tmp/pids"
state: directory
owner: "{{ rails_user }}"
group: "{{ rails_group }}"
mode: '0755'
* name: Enable and start Rails service
systemd:
name: "{{ app_name }}"
daemon_reload: yes
enabled: yes
state: started
handlers:
* name: reload systemd
systemd:
daemon_reload: yes
Create the template file templates/rails-web.service.j2
:
[Unit]
Description={{ app_name }} Rails Application
After=network.target
Wants=network.target
[Service]
Type=simple
User={{ rails_user }}
Group={{ rails_group }}
WorkingDirectory={{ rails_root }}
Environment=RAILS_ENV={{ rails_environment }}
Environment=BUNDLE_GEMFILE={{ rails_root }}/Gemfile
Environment=PATH={{ rails_root }}/bin:/usr/local/bin:/usr/bin:/bin
# Process management
PIDFile={{ rails_root }}/tmp/pids/server.pid
ExecStart={{ rails_root }}/bin/rails server -b 0.0.0.0 -p {{ rails_port }} -e {{ rails_environment }}
ExecReload=/bin/kill -USR2 $MAINPID
ExecStop=/bin/kill -QUIT $MAINPID
KillMode=mixed
KillSignal=SIGQUIT
TimeoutStopSec=5
# Restart policy
Restart=always
RestartSec=5
StartLimitInterval=60s
StartLimitBurst=3
# Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths={{ rails_root }}/log {{ rails_root }}/tmp {{ rails_root }}/storage
# Resource limits
LimitNOFILE=65536
LimitNPROC=4096
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier={{ app_name }}
[Install]
WantedBy=multi-user.target
Environment Variables:
RAILS_ENV
: Sets the Rails environment (production, staging, etc.)BUNDLE_GEMFILE
: Ensures Bundler uses the correct GemfilePATH
: Includes the application’s bin directory for Rails commandsSecurity Features:
NoNewPrivileges
: Prevents privilege escalationPrivateTmp
: Isolates temporary directoriesProtectSystem
: Makes system directories read-onlyProtectHome
: Protects user home directoriesProcess Management:
Restart=always
: Automatically restarts the service if it failsStartLimitBurst=3
: Limits restart attempts to prevent rapid restart loopsTimeoutStopSec=5
: Gives the process 5 seconds to shut down gracefullyThis setup provides a solid foundation for running Rails applications in production with proper process management, security, and monitoring capabilities.