Cloudflare Tunnel: a free ngrok alternative for exposing local Rails apps to the internet
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:
- It’s not maintained anymore, although it still works
- Downtimes do happen
-
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
-
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:
Welp, crypto bros have "hijacked" my localtunnel subdomain that I've been using for Synonym Sprint.
— Kyrylo Silin (@kyrylosilin) December 29, 2023
I had to choose a new name instead.
It happened yesterday, and I thought maybe today it would be free again, but no, it is still running.
It's not a big deal, since changing is… pic.twitter.com/G9eL8Lq4Hq
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!
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
:
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: