Exposing your local dev-server to the Internet
I'm a big fan of having utility scripts, where each script solves a small problem I have in my day-to-day work. One of those problems is to share something I have running locally with colleagues. For example:
- I have a local dev server running on port 3000 with some experimental changes that I would like to share
- I want to share some analytics created in DuckDB UI with a colleague. The UI is hosted on port 3000
- A colleague has birthday today. I want to create a personal, AI-generated birthday card and share it on Slack for anyone in the organization to access. The birthday card should be easily accessible, preferably by just clicking on a link
In all those cases, I would ideally just run this in the terminal:
tunnel 3000
... and be done with it. Any request from example.com (or
whatever domain you own) should be forwarded to port 3000. There are
already solutions to this (ngrok and
opentunnel are both great).
However, I want some tweaks that isn't provided out-of-the-box:
- Access control: Only colleagues should have access to the proxied page. I should be fairly confident nobody else is able to view the page.
- URL copied to clipboard: Once the tunnel is running, I want the URL copied to clipboard so I can easily send the link to my colleague. In addition, I would like to display a QR-code in the terminal I can take a screenshot of, in case I want to share a QR-code instead (e.g. on PowerPoint slides). The main goal is to be able to quickly share a page, so anything that reduces friction is great.
- No account sign-up: Ideally, I shouldn't need to configure anything. In the end, I made a script using Cloudflare Tunnel which messes with tunnel configurations and DNS records, so not sure I managed to reach this goal.
Subdomains as passwords
the first part, about access control is a bit tricky. How can we ensure colleagues with access to the link is able to view the page, whereas strangers that might also know the public domain it ends up on cannot see the contents? We allow ourselves to do some cowboy-style access control. We're not building a cryptocurrency wallet. We're just sharing birthday cards with our colleagues, or share something that shouldn't be accessed publicly. Anything really confidential should probably be shared in-person, anyways. With that in mind, we have several alternatives:
-
Add basic authentication: We could add Basic Authentication on the server, requiring username/password to access the contents. Before the user can access the page, they need to fill in a username and password that they know.
This would work, but I would end up sending this kind of Slack message: "Hi Jerry, check out the prototype on
example.com. Use the password 80b162bc andadminfor the username. I am making life hard for Jerry. I need to share three pieces: the domain, username and password. For the birthday card usecase, nobody is going to fill in that much just to see a birthday card. -
URL-based authentication: Append a token, e.g.
example.com?token=80b162bc, and have a HTTP middleware on the server validate the request. If the token matches, the content is served, otherwise a 404-page appears.This works unless you need page navigation. For single pages, this would be sufficient. However, the tunnel should support page navigation.
-
Subdomain-based authentication: Instead of appending to
example.com, let's try to prepend it. So the URL becomes80b162bc.example.com. Visitexample.comand you get nothing. But visit this particular subdomain and you'll have access!This works great, as you need to know the link in order to get access. I also don't need to provide any username / password. Furthermore, navigation works fine. Figuring out subdomains for a given domain also seems like a difficult problem , which is good! Interestingly, this is also how opentunnel and ngrok does it.
So generating a random subdomain is the way to go for this script. I'm sure there are weaknesses to this approach. But it seems to be good enough for my needs.
Setting up a cloudflare tunnel
We're using Cloudflare Tunnel for the heavy lifting. We have the tunnel client in place. However, we need to create a new tunnel, specify a configuration file and run. In addition, once the tunnel close, we also need to tear down resources, like the good citizen we are. I let Claude Code do the implementation. It's a python script, and it was written by an initial spec by me. I had to iterate with Claude a few times before the end result was good enough. All in all, I estimate I spent around 1 - 2 hours for this script. Very roughly, the code is like this:
function tunnel(port) {
tunnel = "mytunnel" # randomly generated
subdomain = "80b162bc.example.com" # randomly generated
# destroy tunnel when process exits
setup_interrupt_handlers()
tunnel_id = create_tunnel(tunnel, subdomain)
write_config(tunnel_id, tunnel, subdomain, port)
start_tunnel()
copy_url_to_clipboard()
display_qr_code()
}
Copying to clipboard and generating QR-codes
The URL is copied over to clipboard with the xclip tool:
echo "$link" | xclip -selection clipboard
And for QR-code generation, I used qrencode that prints the code to stdout:
qrencode -t ansi256 "$link"
Bonus: serving file contents
With a small tweak, we can serve static files in a directory! We'll just
run python -m http.server /tmp/foo/bar on a random port,
and then we create a tunnel there:
tunnel /tmp/foo/bar
Summary
Creating this helper script has been really fun and I use it weekly-ish. The script lets me do exactly what I want, eliminating the small friction points I have. Claude did most of the gruntwork, while I defined its behaviours. All in all, I probably spent 1 - 2 hours implementing this.