capistrano-puma drives Puma through systemd instead of Capistrano's own process handling
Puma integration for Capistrano
At a glance
- What is it?
- capistrano-puma is a Capistrano plugin that manages Puma as a systemd service, giving you zero-downtime restarts and, optionally, socket activation. The price is that you give up Capistrano's built-in Puma handling and manage your own puma.rb, and version 6 changed the defaults enough to require migration steps.
- Who is it for?
- capistrano-puma suits a Rails team on Capistrano that wants Puma under systemd so that the application is supervised by the init system rather than by the deploy tool, and that wants socket activation to avoid the cold start on the first request.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 125 days ago.
- What is it written in?
- Mainly Ruby, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the plugin actually does for you
capistrano-puma is described as a Puma integration for Capistrano, providing systemd service management and zero-downtime deployments for Puma 5.1 and later. The primary language is Ruby, and it ships as the capistrano3-puma gem.
The word that matters in that description is systemd. The plugin does not supervise Puma itself in the way Capistrano's own Puma tasks do, where the deploy process starts and stops a background process over SSH. Instead, the plugin writes systemd unit files, and systemd owns the process. The consequence is that the application's lifecycle belongs to the init system, which is the arrangement most operations teams prefer, because the service restarts when the machine reboots and it is visible to every monitoring tool that speaks systemd.
The tasks the plugin exposes make the scope clear:
cap puma:disable # Disable Puma systemd service
cap puma:enable # Enable Puma systemd service
cap puma:install # Install Puma systemd service
cap puma:reload # Reload Puma service via systemd
cap puma:restart # Restart Puma service via systemd
cap puma:restart_socket # Restart Puma socket via systemd
cap puma:smart_restart # Restarts or reloads Puma service via systemd
cap puma:start # Start Puma service via systemd
cap puma:status # Get Puma service status via systemd
cap puma:stop # Stop Puma service via systemd
cap puma:stop_socket # Stop Puma socket via systemd
cap puma:uninstall # Uninstall Puma systemd serviceTwo entries are worth a second look. puma:smart_restart is described as restarting or reloading, which is the zero-downtime primitive: reload where the service supports it, restart where it does not. And puma:status is the one you will reach for when something is wrong, since it goes through systemd rather than trying to introspect a process.
There is a companion example application, capistrano-example-app, which the documentation points to as a complete working example.
Version 6 stopped generating your puma.rb for you
The upgrade notes are the most useful part of the documentation for anyone moving from the 5.x line, because three defaults changed and each one breaks a deployment in a way that is not obvious.
First, manual Puma configuration. The gem no longer generates puma.rb automatically. You must create your own config/puma.rb in your repository and add it to linked_files in your deploy.rb. The stated reason is that the configuration should be managed by the application rather than by the deploy tooling, which is defensible, but it does mean an upgrade is a code change in your repository rather than a gem version bump.
Second, the default puma_bind was removed and you must set the bind address explicitly:
set :puma_bind, "unix://#{shared_path}/tmp/sockets/puma.sock"Third, the systemd services are now user-specific by default. Services are created in the user directory rather than the system directory, which avoids needing root on the deploy host. The consequence is an extra step after upgrading: you must run cap production puma:install again.
The migration steps are given as a sequence. Create your puma configuration if you do not have one, add the linked_files line and the puma_bind line to deploy.rb, run cap production puma:install to update the systemd service, and then deploy as normal.
The linked_files line is the piece that makes the configuration survive deployments:
append :linked_files, 'config/puma.rb'Without it, the file is regenerated on each deploy and your careful configuration is lost.
The install step that decides whether Puma runs at all
The documentation is emphatic about the first-time setup, and the emphasis is justified.
Before the first deployment you must install the Puma systemd service on the server:
$ bundle exec cap production puma:installThe command creates the systemd service files for Puma, enables the service to start on boot, and sets up the proper user permissions. The documentation notes it only needs to be done once per server or stage.
What happens if you skip it is stated plainly: without running this command first, your deployment will succeed but Puma will not start. That is the worst possible failure mode for a deploy tool, because the deploy reports success while the application is down. If you adopt this plugin, run the install step as part of your server provisioning, not as part of your first deploy, so the failure mode never gets a chance to appear.
After that, the normal deploy command is unchanged:
$ bundle exec cap production deployand the deployment process automatically restarts Puma using the installed systemd service.
Manual control is available for the cases where you want to drive the service yourself, for example during an incident:
$ bundle exec cap production puma:start
$ bundle exec cap production puma:stop
$ bundle exec cap production puma:restartand the reverse operation, removing the service entirely, is puma:uninstall.
Socket activation, and the puma_start wrapper for rvm, rbenv and chruby
Two features deserve their own treatment because they change how the application behaves rather than how it is deployed.
The first is systemd socket activation, which starts your app upon the first request if it is not already running. The documentation gives the setting:
set :puma_enable_socket_service, trueThis is the standard systemd pattern where the socket unit is bound before the application is, so the port is already accepting connections when the process starts. For a Rails application under load, that removes the cold start from the request path. The socket can be restarted independently with cap puma:systemd:restart_socket, which also restarts the Puma instance, since the puma service depends on the socket service being active.
The second is about load order. To make the plugin work with rvm, rbenv and chruby, you install it after the corresponding library inclusion, and the documentation is explicit about the order:
# Capfile
require 'capistrano/rbenv'
require 'capistrano/puma'
install_plugin Capistrano::PumaThat ordering constraint is the kind of thing that fails silently. If the plugin is loaded before the version manager, the Ruby that Puma is launched with is not the one your application expects, and you get a version mismatch error at the worst possible moment.
The plugin itself is loaded with the same require and install_plugin pair, and the Systemd variant is installed as a separate plugin:
# Capfile
require 'capistrano/puma'
install_plugin Capistrano::Puma # Default puma tasks
install_plugin Capistrano::Puma::SystemdIf you want the tasks without the hooks, load_hooks: false suppresses them, which is the escape hatch for projects that want to drive the lifecycle themselves.
Configuration options and the reload flag trade-off
The configurable options are listed with their defaults, and the documentation is careful to say these are not required unless you are overriding a default, for instance if you are deploying on a host without sudo or root privileges and need to restrict the path. They go in the deploy.rb file.
set :puma_user, fetch(:user)
set :puma_role, :web
set :puma_bind, "unix://#{shared_path}/tmp/sockets/puma.sock"
set :puma_systemd_watchdog_sec, 10 # Set to 0 or false to disable watchdog
set :puma_service_unit_env_files, []
set :puma_service_unit_env_vars, []
set :puma_service_unit_props, [] # Set extral puma service properties, such as ["MemoryMax=2G","TimeoutAbortSec=30"]
set :puma_systemctl_reload_options, [] # Extra flags passed to `systemctl reload`, e.g. ["--no-block"]The unit-level settings are the interesting ones. puma_service_unit_env_files and puma_service_unit_env_vars inject environment into the systemd unit, and puma_service_unit_props sets arbitrary unit properties, with the given example being MemoryMax and TimeoutAbortSec. That is a systemd resource limit applied to your application process, which is a much stronger guarantee than a limit inside Rails.
The watchdog setting defaults to 10 seconds, and can be set to 0 or false to disable it.
The last option has an explicit trade-off documented. puma_systemctl_reload_options is an array of extra flags appended to the systemctl reload command, empty by default, and the flags are applied to reload only, with the restart fallback used when the service is not running left untouched. The reasoning is spelled out: with the default Type=notify service, systemctl reload waits for the phased restart to finish and for every worker to re-signal readiness before it returns, and passing --no-block makes systemd enqueue the reload and return immediately. The cost is that the deploy no longer waits on the reload, so it will not surface a failed restart.
MIT licensed, a CHANGELOG, and no tagged releases to pin
The licence is MIT, with the LICENSE.txt file at the repository root, which is the uncomplicated case for a deployment plugin.
Upkeep is modest rather than busy. The last push was on 2026-06-21, the default branch is master, and the repository publishes no GitHub releases, so there is no version to pin from the release list. What you get is the gem from RubyGems under the name capistrano3-puma, and the CHANGELOG.md at the repository root is where you check what changed between versions.
Given that the plugin supports Puma 5.1 and later and the README covers a 6.x migration with breaking changes around puma.rb generation, bind addresses and systemd unit placement, the changelog is not optional reading when you upgrade. The breaking changes are the kind that leave a deployment that reports success and serves nothing.
The repository layout is a conventional gem: a lib/ directory with the implementation, a test/ directory, a capistrano3-puma.gemspec, a Rakefile for the test tasks, a Gemfile, a docs/ directory, and a CHANGELOG.md. There is also a .devcontainer/ directory and a mise.toml, which suggests the maintainer develops against pinned tool versions in a container, a common choice now that a gem like this has to be tested against several Ruby and Capistrano combinations.
The example application referenced in the documentation demonstrates the whole shape of a deployment with this plugin: Rails 8.0 with Capistrano, Puma 6.0 with systemd socket activation, zero-downtime deployments, rbenv integration and Nginx configuration examples. Reading that repository is the fastest way to see the plugin working in a realistic configuration rather than in a snippet.
Editorial conclusion
capistrano-puma suits a Rails team on Capistrano that wants Puma under systemd so that the application is supervised by the init system rather than by the deploy tool, and that wants socket activation to avoid the cold start on the first request. It is the wrong choice if you are still on Capistrano's own Puma handling and would rather not own a puma.rb file, and the wrong choice if your deploy target is not systemd based, because the plugin's whole model is systemd units in the user directory. Before adopting it, read the migration notes for version 6, create config/puma.rb and add it to linked_files, and run bundle exec cap production puma:install on the target server, since without that step the deployment succeeds and Puma does not start.
Frequently asked questions
How do I add the capistrano-puma plugin to a Capistrano project?
Add gem 'capistrano3-puma' to your Gemfile, either from the GitHub repository with github: "seuros/capistrano-puma" or from RubyGems with a group: :development option, then run bundle. In your Capfile, require 'capistrano/puma' and install Capistrano::Puma along with Capistrano::Puma::Systemd.
Why does my Capistrano deployment succeed but Puma does not start?
The documentation says that without running bundle exec cap production puma:install first, the deployment will succeed but Puma will not start. That command creates the systemd service files, enables the service on boot and sets the user permissions, and it only needs to be run once per server or stage.
What changed when I upgraded capistrano-puma from 5.x to 6.x?
Version 6 no longer generates puma.rb automatically, so you must create config/puma.rb and add it to linked_files in your deploy.rb. The default puma_bind was removed and must be set explicitly, systemd services are now created in the user directory and are user-specific by default, and you must run cap production puma:install again after upgrading.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/seuros-capistrano-puma)Was this useful?