puma

The puma plugin provides a systemd-based solution for starting, stopping, and restarting puma using socket activation for zero-downtime restarts. It is based on the best practices in the official puma documentation.

Tomo’s implementation installs puma as a user-level service using systemctl --user. This allows puma to be installed, started, stopped, and restarted without a root user or sudo. However, when provisioning the host you must make sure to run the following command as root to allow the puma process to continue running even after the tomo deploy user disconnects:

# run as root
$ loginctl enable-linger <DEPLOY_USER>

Stdout and stderr of the puma process will be routed to syslog, as is the convention for systemd services. For Rails, it is recommended that you set RAILS_LOG_TO_STDOUT=1 so that all Rails logs are handled this way (tomo init configures this by default).

The tomo puma plugin assumes that your puma server will listen on a single TCP port for HTTP (not HTTPS) traffic. In other words, HTTPS termination will be handled by e.g. Nginx or a separate load balancer.

Settings

Name Purpose Default
puma_check_timeout The number of seconds that the puma:check_active task will wait for puma to respond before timing out. 15
puma_port TCP port that puma should listen on 3000
puma_systemd_service Name of the systemd service that manages the puma server "puma_%{application}.service"
puma_systemd_socket Name of the systemd socket that is used for socket activation of the puma service "puma_%{application}.socket"
puma_systemd_service_path Path on the remote host where the systemd puma service configuration file will be created ".config/systemd/user/%{puma_systemd_service}"
puma_systemd_socket_path Path on the remote host where the systemd puma socket configuration file will be created ".config/systemd/user/%{puma_systemd_socket}"
puma_systemd_service_template_path Local path of the ERB template to use to create the the systemd puma service configuration file service.erb
puma_systemd_socket_template_path Local path of the ERB template to use to create the the systemd puma socket configuration file socket.erb

Tasks

puma:setup_systemd

Configures systemd to manage puma. This means that puma will automatically be restarted if it crashes, or if the host is rebooted. This task essentially does three things:

  1. Installs a puma.socket systemd unit
  2. Installs a puma.service systemd unit that depends on the socket
  3. Enables these units using systemctl --user enable

Note that these units will be installed and run for the deploy user. You can use :puma_systemd_socket_template_path and :puma_systemd_service_template_path to provide your own templates and customize how puma and systemd are configured.

puma:setup_systemd is intended for use as a setup task. It must be run before puma can be started during a deploy.

puma:restart

Restarts the puma service via systemd. This starts puma if it isn’t running already. The systemd socket remains running while puma itself is restarted. In other words, incoming requests will continue to connect and queue while puma restarts. This is a “zero-downtime restart”.

Puma will be configured to listen on :puma_port, with the config/puma.rb file within the Rails app providing the remainder of the configuration. The default port is 3000. Puma is started using this command:

bundle exec --keep-file-descriptors puma -C config/puma.rb -b tcp://0.0.0.0:3000

puma:restart is intended for use in a deploy, immediately following core:symlink_current to ensure that the new version of the Rails app is activated.

puma:check_active

This task queries systemd and executes a curl test to verify that puma is active and listening on :puma_port. Because puma is run in the background, it is not immediately obvious after starting or restarting puma via systemd as to whether it booted successfully, or if it crashed. This is where the puma:check_active task can help. If puma is not working it will fail and show puma’s log output for easier troubleshooting.

puma:check_active is intended for use as a deploy task, immediately following puma:restart to verify that puma restarted successfully.

puma:start

Starts the puma socket and service via systemd, if they aren’t running already. Equivalent to:

systemctl --user start puma.socket puma.service

puma:stop

Stops the puma socket and service via systemd. Equivalent to:

systemctl --user stop puma.socket puma.service

puma:status

Reports the status of the puma socket and service via systemd. Equivalent to:

systemctl --user status puma.socket puma.service

Sample output:

$ tomo run puma:status
tomo run v0.10.0
→ Connecting to deployer@app.example.com
• puma:status
systemctl --user status puma_example.socket puma_example.service
● puma_example.socket - Puma HTTP Server Accept Sockets for example
   Loaded: loaded (/home/deployer/.config/systemd/user/puma_example.socket; enabled; vendor preset: enabled)
   Active: active (running) since Thu 2019-10-24 09:41:53 UTC; 1 weeks 2 days ago
   Listen: 0.0.0.0:3000 (Stream)

● puma_example.service - Puma HTTP Server for example
   Loaded: loaded (/home/deployer/.config/systemd/user/puma_example.service; enabled; vendor preset: enabled)
   Active: active (running) since Fri 2019-11-01 15:46:10 UTC; 1 day 10h ago
 Main PID: 14513 (bundle)
   CGroup: /user.slice/user-1000.slice/user@1000.service/puma_example.service
           └─14513 puma 4.2.1 (tcp://0.0.0.0:3000) [20191101154450]

puma:log

Uses journalctl (part of systemd) to view the log output of the puma service. This task is intended for use as a run task and accepts command-line arguments. The arguments are passed through to the journalctl command. For example:

$ tomo run -- puma:log -f

Will run this remote script:

journalctl -q --user-unit=puma.service -f