I’ve been using ngrok on quite a few projects lately. I’ve written about it before, but in short, it solves to problems for me.

  1. It tunnels back to localhost from a hostname that live on the net, allowing me to develop for webhooks that would barf on http://localhost:3000
  2. It provides a valid SSL cert. More and more the platforms I build apps for require HTTPS when talking to an app.

The downside is that it adds another moving part to the process. I’m too lazy for that, let’s automate it!

A little background, I recently upgraded to a paid ngrok account, so I could reserve subdomains. By default, ngrok gives you a random hostname, like, 23b0390b.ngrok.io, every time you run it, meaning you have to reconfigure your webhooks whenever you restart it. With a paid account, I can reserve a fixed hostname like, spikex.ngrok.io, problem solved.

I had be using localtunnel which has the advantage of being free, including use of (unreserved) subdomains. However, I found it crashed randomly, and naturally during client demos. I really appreciate the localtunnel folks providing this service, but I decided to paid ngrok for something more stable.

Regardless of the tunnel, the point is that for a lot of my development work I need one and it’s one more window to open, one more process to start.

Fortunately, it turns out there’s a gem, ngrok-tunnel, for managing ngrok. It goes in your Gemfile in the usual way, and I stick it in my development/test block:

1
2
3
4
group :development, :test do
# [...]
  gem 'ngrok-tunnel'
end

The gem provides command to stop, start, and get info about the tunnel:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Ngrok::Tunnel.start
 => "http://d5ccdf29.ngrok.io"
Ngrok::Tunnel.port
 => 3001
Ngrok::Tunnel.ngrok_url
 => "http://d5ccdf29.ngrok.io"
Ngrok::Tunnel.ngrok_url_https
 => "https://d5ccdf29.ngrok.io"
Ngrok::Tunnel.status
 => :running
Ngrok::Tunnel.stop
 => :stopped
Ngrok::Tunnel.status
 => :stopped

Note that the default port is 3001, where we typical run Rails on 3000, the :port option controls this:

1
2
3
4
Ngrok::Tunnel.start(port: 3000)
 => "http://e5de1a61.ngrok.io"
Ngrok::Tunnel.port
 => 3000

With this, we can automate the process. Presuming you are using Rails 5 and Puma, stick the following at the end of config/puma.rb:

1
2
3
4
5
6
7
8
if Rails.env.development?
  require 'ngrok/tunnel'
  options = {   addr: ENV.fetch("PORT") { 3000 },
                config: File.join(ENV['HOME'],'.ngrok2','ngrok.yml')
            }
  options[:subdomain] = ENV['NGROK_SUBDOMAIN'] if ENV['NGROK_SUBDOMAIN']
  puts "[NGROK] tunneling at " + Ngrok::Tunnel.start(options)
end

(If you’re using something else this code will work in config/unicorn.rb, or config/thin.rb as well.)

This will fire up ngrok when Rails starts. If the environmental variable NGROK_SUBDOMAIN is set, it will be use to set the ngrok subdomain, otherwise a random one will be used. In either case, the URL of the tunnel will be display.

If you are using localtunnel, there’s a gem for that too, so you can easily apply the same logic using it.

There, laziness is satisfied, ngrok starts and stops automatically with the Rails server, one less thing to worry about.

Comments