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:
.serviceunit and call puma (or a binstub) directly.