Imagine you want to test a new page that you just wrote the code for against a real mobile device. You don’t want to push it into production without testing it. You somehow need to expose your local Rails server to the internet so that you (or anyone else) could test it before it goes live.

These is a very common problem. Luckily, it’s been solved already. My go-to tool for this was ngrok or localtunnel. Both of these tools are great, but they didn’t fit my needs perfectly.

Ngrok is very advanced, but I don’t need all of its features. The feature that I need is a static domain name, so that I can expose my local Rails app to the same address. On the free version, ngrok binds to a subdomain assigned to you randomly. A static domain name is a must if you want to save time developing your app.

Localtunnel is an open-source alternative to ngrok that allows you to do just that:

lt --port 3000 --subdomain=telebugs --print-requests

There are a bunch of problems with localtunnel, though:

  1. It’s not maintained anymore, although it still works
  2. Downtimes do happen
  3. Sometimes, the tunnel just crashes, or your subdomain doesn’t get bound

    To address the former, I wrapped my localtunnel in a while loop like this:

    while true; do lt --port 3000 --subdomain=telebugs --print-requests; sleep 1; done
    
  4. Anyone can hijack the subdomain that you use when your tunnel is not active.

    In fact, this happened to me last year, so I had to choose another subdomain:

Meet the Cloudflare Tunnel

Your domain name must be managed by Cloudflare. Otherwise, this tutorial will not work for you.

Why would you go through all these struggles, when around the corner, there’s a better alternative that solves all our problems?

Without further ado, let’s replace ngrok/localtunnel with Cloudflare Tunnel in our Rails 7 app!

Installation

Let’s install the cloudflared CLI app. On macOS, it’s as simple as:

brew install cloudflared

Confirm that it’s installed via:

% cloudflared --version
cloudflared version 2024.3.0 (built 2024-03-19T18:08:31Z)

Create a new tunnel

First, you need to log into your Cloudflare account via cloudflared so that you can manage CF tunnels via the CLI:

% cloudflared tunnel login
A browser window should have opened at the following URL:

https://dash.cloudflare.com/argotunnel?aud=&callback=https%3A%2F%2Flogin.cloudflareaccess.org%2F0n1R7UqQdRd7vR3D4CT3D4wzHoB0-63_RZ63vSVzIhakw%3D

If the browser failed to open, please visit the URL above directly in your browser.
You have successfully logged in.
If you wish to copy your credentials to a server, they have been saved to:
/Users/kyrylo/.cloudflared/cert.pem

Choose the domain for your Rails app and click “Authorize”. That’s all!

Authorize Cloudflare Tunnel

If cloudflared tunnel login didn’t print a link, don’t worry. Just continue reading.

Now, you need to create a named tunnel. I will name my tunnel telebugs:

% cloudflared tunnel create telebugs
Tunnel credentials written to /Users/kyrylo/.cloudflared/3de42678-313b-4801-bd71-1e4dda81880b.json. cloudflared chose this file based on where your origin certificate was found. Keep this file secret. To revoke these credentials, delete the tunnel.

Created tunnel telebugs with id 3de42678-313b-4801-bd71-1e4dda81880b

You will use that tunnel to access your local Rails app. Now you need to update your DNS records to point to that tunnel. Choose a subdomain that you would like to use to access your local Rails app. localhost seems like a good choice: it’s easy to understand, and it’s unlikely you will want it for your production needs.

% cloudflared tunnel route dns telebugs localhost.telebugs.com
2024-03-31T09:59:33Z INF Added CNAME localhost.telebugs.com which will route to this tunnel tunnelID=3de42678-313b-4801-bd71-1e4dda81880b

Go to your DNS settings for the Cloudflare domain you use and verify that the Tunnel CNAME record was added.

If not, then add a new CNAME record manually. The target should be the tunnel ID you received when you created the tunnel plus .cfargotunnel.com. In my case, it’s 3de42678-313b-4801-bd71-1e4dda81880b.cfargotunnel.com:

DNS settings. Adding the Cloudflare Tunnel CNAME

Configure your Rails app to use the tunnel

Create config/cloudflare-tunnel.yml with the following contents:

# config/clouflare-tunnel.yml
tunnel: 3de42678-313b-4801-bd71-1e4dda81880b
credentials-file: /Users/kyrylo/.cloudflared/3de42678-313b-4801-bd71-1e4dda81880b.json

ingress:
  - hostname: localhost.telebugs.com
    service: http://localhost:3000
  - service: http_status:404

Note: you must use the full path for credentials-file. Cloudflare will not expand $HOME or ~/ for you, so ~/.cloudflared/3de42678-313b-4801-bd71-1e4dda81880b.json will fail:

Tunnel credentials file '~/.cloudflared/3de42678-313b-4801-bd71-1e4dda81880b.json' doesn't exist or is not a file

Next, validate the tunnel’s ingress:

% cloudflared tunnel --config config/cloudflare-tunnel.yml ingress validate telebugs
Validating rules from config/cloudflare-tunnel.yml
OK

Ok, we’re all good. Now we can add this file to .gitignore, copy its contents to config/cloudflare-tunnel.yml.example, redact tunnel and credentials-file values, and track it with git:

% echo config/cloudflare-tunnel.yml >> .gitignore
% cp config/cloudflare-tunnel.yml config/cloudflare-tunnel.yml.example
# config/clouflare-tunnel.yml.example
tunnel: CHANGE_ME
credentials-file: CHANGE_ME

ingress:
  - hostname: localhost.telebugs.com
    service: http://localhost:3000
  - service: http_status:404
git add config/clouflare-tunnel.yml.example

One last thing we need to do is add localhost.telebugs.com to the list of allowed domains so that Rack doesn’t block it:

# config/environments/development.rb

Rails.application.configure do
  config.hosts << "localhost.telebugs.com"
end

Run the tunnel

Awesome! We can now start using our tunnel. Let’s confirm that it works:

% cloudflared tunnel --url localhost:3000 --config config/cloudflare-tunnel.yml run telebugs
2024-03-31T11:19:10Z INF Starting tunnel tunnelID=3de42678-313b-4801-bd71-1e4dda81880b

Now, make sure your local Rails app is running, navigate to https://localhost.telebugs.com (use your own domain), and verify that your local Rails server gets hit.

% curl -I https://localhost.telebugs.com
HTTP/2 200

Nice! We’re almost done!

Starting the tunnel every time is a hassle, so add it to the Procfile, so that it is started automatically every time you launch your Rails app locally.

Edit Procfile and add the following:

# Procfile
tunnel: cloudflared tunnel --url localhost:3000 --config config/cloudflare-tunnel.yml run telebugs

Now, every time you run bin/dev, your Rails 7 application will start the tunnel, and you will be able to access your local server from anywhere on the internet. For free!

Was the article helpful? Follow me on X/Twitter where I post daily about my indie hacking journey and the projects I work on. You can discuss this article on X/Twitter, too: https://twitter.com/kyrylosilin/status/1774406300295717101

Share on: