How To Automatically Change Ports In Rails When The Default Is In Use

When I was in high school our internet used to timeout at a rather inconvenient hour so as a workaround I would open as much content as possible in separate browser tabs with the hope that one of those tabs would have what I needed. Needless to say, years of constant paranoia transformed me into the tab hoarder I am today. While I’ve gotten much better at tab management, I’m still pretty bad at it and run into problems occasionally.

Recently, I was working on multiple Rails projects in separate terminals and noticed they were both being hosted on the same server. Since Rails automatically starts a server at ‘http://localhost:3000’, when you run ‘rails s’ multiple times and don’t keep track of the port, everything will go to the default port creating conflicts. This can get pretty messy and confusing quickly. (If you don’t want a quick overview of how ports work, you can skip to the TLDR section below)

Fortunately there are lots of ways to remedy this.

For starters one way to view all the current ports in use is by running the following line in terminal from the root path of your project folder:

ps aux 

This should populate a long list of all the processes by user. The list also displays a corresponding PID for each process and a file path listed under ‘command’.

From there, a user would find all relevant PIDs and then kill them manually using:

kill -9 <PID>
#for example:
#kill -9 12908

But to me, this list seemed a bit extensive. We only really want a list of processes that are running on our rails server. Therefore we can match specific processes using ‘grep’ or ‘lsof’. Instead, since we already know what the default port number is, and what our server is running on, it’s much easier to run any of the following lines of code to be more specific and get a more refined list:

ps aux | grep 3000
OR
ps aux | grep puma
OR
ps aux | grep tcp
OR
lsof -i :3000

This is a much cleaner list of processes and more straight to the point:

From here we can again use the ‘kill -9 <PID>’ command with the appropriate PID number to kill the port in use.

As an aside, to avoid the mess of having multiple projects ever open in one server, instead of running ‘rails s’ to start a server we can always manually specify which port we want to run a project on. By default, the port is 3000 so, if we wanted we could run an additional server on port 3001 by running the following line:

rails s -p 3001

But why should we have to do that? Couldn’t Rails know better and just automate the task of opening up a project on an open port?

One thing to note is that when using the previous grep command of ‘ps aux | grep :3000’, we can see that our server is being handled by Puma in the last column of the list. But we already knew that since every time we run a rails server, Puma lets us know it is booting up. So could we direct Puma to automatically assign a server on a different port than the default?

TLDR:

If you look inside your project folder (assuming it was generated with ‘rails new’) under ‘config’ there should be a file called ‘puma.rb’. Inside it there is a line of code that sets the default port:

# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
port        ENV.fetch("PORT") { 3000 }

Using some of the previous commands I discussed, I added a while loop right before this action to automate the port number that is fetched and then passing it into our original code:

# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#If default is in use, increment the default port number by 1 until you hit an open port
#
new_port = 3000
while system("lsof -i:#{new_port}")
new_port += 1
end
port        ENV.fetch("PORT") { new_port }

Now whenever the default port of 3000 is in use, it should automatically generate a new default server by incrementing the default port by 1 until there’s an open port. So our new port would be on ‘http://localhost:3001’ (assuming it’s unused)!

Again, notice how initially I had multiple projects open on the same server:

But with the added while loop in ‘puma.rb’, running the ‘rails s’ command will automatically go to the next open port, in this case 3001:

Unfortunately this technique doesn’t change the second output line after I enter ‘rails s’, but if you take a look at the last line, it is in fact running on port 3001. This isn’t as clear as I’d like it to be, but with effectively 2 lines of code, I’ve added a lot of functionality to my project. While this technique doesn’t allow me to create multiple instances of the same project on different servers, to me this feature seems pretty redundant for my purposes.

For me, this was ideal because I could implement a few lines of code into my projects without the need of other gems and it would allow me to safely start servers with ‘rails s’ and not have server conflicts with different projects running concurrently.

It also just enabled by tab hoarding…

But in the end I had fun learning how to automate a task that caused me a little bit of frustration.

Sources:

  1. PerezHilton (gif)
  2. StackOverflow [checking if a port is open]
  3. StackOverflow [finding and killing ports]
  4. Blog Post by Weston Ganger
  5. Rambo (gif)
  6. Success (gif)
  7. Tab Management (comic)