How to run a ruby on rails app with ansible and systemd
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.
Why systemd and Ansible?
systemd provides several advantages for Rails applications:
- Automatic process restart on failure
- Log management and rotation
- Resource limits and security controls
- Integration with system monitoring tools
- Standardized service management across Linux distributions
Ansible offers:
- Infrastructure as code
- Idempotent deployments
- Easy rollback capabilities
- Consistent configuration across environments
- Integration with CI/CD pipelines
Prerequisites
Before getting started, ensure you have:
- A Rails application ready for deployment
- A Linux server with systemd
- Ansible installed on your control machine
- SSH access to your target server
- Ruby and Rails dependencies installed on the target server
Automated Setup with Ansible
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
systemd Service Template
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
Key Configuration Details
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 commands
Security Features:
NoNewPrivileges: Prevents privilege escalationPrivateTmp: Isolates temporary directoriesProtectSystem: Makes system directories read-onlyProtectHome: Protects user home directories
Process 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 gracefully
This setup provides a solid foundation for running Rails applications in production with proper process management, security, and monitoring capabilities.