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 Gemfile
  • PATH: Includes the application’s bin directory for Rails commands

Security Features:

  • NoNewPrivileges: Prevents privilege escalation
  • PrivateTmp: Isolates temporary directories
  • ProtectSystem: Makes system directories read-only
  • ProtectHome: Protects user home directories

Process Management:

  • Restart=always: Automatically restarts the service if it fails
  • StartLimitBurst=3: Limits restart attempts to prevent rapid restart loops
  • TimeoutStopSec=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.