How I setup nginx, with multiple ruby puma apps on ubuntu 16.04 with systemd
The thing that caused me the most friction was getting systemd
to execute puma
correctly. Since the current server I’m using still use rvm
instead of rbenv
, that was
the thing causing me the most headache. Maybe it’s just that I’m used to rbenv
these days. One of my learning out of this is most def that I should have started the migration of all those legacy apps to rbenv
in the first place.
Setup puma in the ruby app
Add a config/puma.rb
file. Read more about puma here.
For my sinatra app it looks like this:
# Change to match your CPU core count
workers ENV.fetch("PUMA_WORKERS") { 1 }
# Min and Max threads per worker
threads ENV.fetch("PUMA_MIN_THREADS") { 1 }, ENV.fetch("PUMA_MAX_THREADS") { 10 }
app_dir = File.expand_path("../..", __FILE__)
shared_dir = "#{app_dir}/shared"
# Set up socket location
bind "unix://#{shared_dir}/sockets/puma.sock"
# Redirect STDOUT to log files
stdout_redirect "#{app_dir}/log/puma.stdout.log", "#{app_dir}/log/puma.stderr.log", true
# Set master PID and state locations
pidfile "#{shared_dir}/pids/puma.pid"
state_path "#{shared_dir}/pids/puma.state"
rackup "#{app_dir}/config.ru"
activate_control_app
For a sinatra app you also need to set a config:
configure do
set :server, :puma
end
Setup nginx as proxy using unix socket
Minimal nginx conf. Nginx acts as a so called “reversed proxy” and redirects all traffics to puma through a unix socket. This is my nginx virtual host file:
upstream app {
server unix:/var/www/my-website.com/shared/sockets/puma.sock;
}
server {
listen 80;
server_name my-website.com;
root /var/www/my-website.com/public;
location / {
try_files $uri @puma;
}
location @puma {
include proxy_params;
proxy_pass http://app;
}
}
Setup systemd service
Since ubuntu 16.04 uses systemd instead of upstart I’m using that. It’s very straight forward. Create the following file here: /etc/systemd/system/puma-my-website.service
. The ExecStart
command is the most important one. That is the one starting puma
. The service also loads the environment variables from the .env
file, see the EnvironmentFile
command. So dotenv
is not needed and all the variables will be available in the puma config file.
[Unit]
Description=Puma HTTP Server
After=network.target
[Service]
# Foreground process (do not use --daemon in ExecStart or config.rb)
Type=simple
# Preferably configure a non-privileged user
User=joel
Group=joel
# Specify the path to your puma application root
WorkingDirectory=/var/www/my-website.com
# Helpful for debugging socket activation, etc.
Environment=PUMA_DEBUG=1
EnvironmentFile=/var/www/my-website.com/.env
# The command to start Puma
ExecStart=/home/joel/.rvm/wrappers/ruby-2.4.1/bundle exec puma -C /var/www/my-website.com/config/puma.rb
Restart=always
[Install]
WantedBy=multi-user.target
When that file is added you can start it like this:
sudo systemctl start puma-my-website.service
And make it start on system boot
sudo systemctl enable puma-my-website.service
Check systemd logs
When you have a problem with systemd
, check the logs with journalctl
:
sudo journalctl -f -u puma-my-website.service
RVM gemset and wrappers (optional)
As mentioned earlier, you need a way that systemd
can execute puma
. In the above example I’ve just used the default ruby wrapper. But some people using rvm
prefer to use gemsets to isolate each app bundle from each other. If you’re of those, follow this guide:
rvm gemset create my-website
Then you need to explicitly say that you want to use it. The ruby-2.4.1
part will depend on which ruby version you use. (I always tend to forget this)
rvm ruby-2.4.1@my-website
And then install the gems in it
bundle install
Now puma
and the other executables should be available under this directory: /home/joel/.rvm/wrappers/ruby-2.4.1@my-website