I have a Rails 5 app, running on puma 3.12.1, MRI 2.6.2, and Ubuntu 18.04. It used to run with pumactl
and a custom control script but I want to configure it properly with systemd, using socket activation to have zero-downtime deployment.
The problem is that the socket holds the port and puma wants to bind on the same one so it gives this error:
/opt/myapp/shared/vendor/ruby/2.6.0/gems/puma-3.12.1/lib/puma/binder.rb:273:in `initialize':
Address already in use - bind(2) for "0.0.0.0" port 3000 (Errno::EADDRINUSE)
I set it up everything according to the puma docs and systemd docs.
I reloaded systemd config of course and tried to restart several times. I don't deeply understand how systemd does this socket activation but for me puma's error message seems reasonable :/
Maybe puma shouldn't try to bind on that port? But how can I communicate to puma to not bind to it but use systemd's forwarded stream?
My configuration:
$ cat /etc/systemd/system/puma.socket
[Unit]
Description=Puma HTTP Server Accept Sockets
[Socket]
ListenStream=0.0.0.0:3000
# Socket options matching Puma defaults
NoDelay=true
ReusePort=true
Backlog=1024
[Install]
WantedBy=sockets.target
$ cat /etc/systemd/system/puma.service
[Unit]
Description=API with Puma server
After=network.target
Requires=puma.socket
[Service]
Type=simple
WorkingDirectory=/opt/myapp/current
ExecStart=/opt/myapp/current/script/bootup_puma
SyslogIdentifier=api-puma
PIDFile=/opt/myapp/current/tmp/pids/puma.pid
Restart=no
TimeoutSec=30
User=ubuntu
[Install]
WantedBy=multi-user.target
$ cat script/bootup_puma
#!/bin/bash
# [setting up some envvars here]
bundle exec puma -C config/puma.rb
$ cat config/puma.rb
# frozen_string_literal: true
app_dir = File.expand_path("..", __dir__)
workers ENV.fetch("API__PUMA_WORKERS", 4).to_i
threads 1, ENV.fetch("RAILS_MAX_THREADS", 8).to_i
bind "tcp://0.0.0.0:#{ENV.fetch('PORT', 3000)}"
pidfile "#{app_dir}/tmp/pids/puma.pid"
directory ENV.fetch("API__PUMA_DIRECTORY") unless ENV.fetch("RAILS_ENV", "development") == "development"
As a desperate attempt, I also tried to use a unix socket instead of TCP but that ended up with a similar error despite I was being careful to not reference the socket through a symlink.
/opt/myapp/shared/vendor/ruby/2.6.0/gems/puma-3.12.1/lib/puma/binder.rb:367:in `add_unix_listener':
There is already a server bound to: /opt/myapp/shared/tmp/puma.sock (RuntimeError)
Other useful resources I already went through: