1

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:

3
  • 3
    You should not have a shell script that starts puma. Move everything directly into the systemd .service unit and call puma (or a binstub) directly. Commented Mar 26, 2019 at 22:48
  • 1
    Thanks @MichaelHampton , I cannot accept it as a solution since you wrote it as a comment but it was indeed the issue. Thanks for taking the time to review my config and pointing it out! Systemd is still like a black box for me, I'm not confident in how this socket activation works :| Commented Mar 29, 2019 at 7:34
  • I don't care that much about points, I have enough of them. It's just about helping people find their solutions. Commented Mar 29, 2019 at 12:40

1 Answer 1

0

The problem was the intermediate shell script bootup_puma as Micheal Hampton pointed out in the comments.

Changing the service to ExecStart=/opt/myapp/current/bin/puma -C config/puma.rb and providing the environment with the EnvironmentFile directives solved the issue.

1
  • 1
    Damn, we need an intermediate shell script because of SELinux executable labelling. Commented May 21, 2020 at 9:38

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.