DEV Community

Cover image for Using My Local Coding Model From Anywhere
Dustin VanKrimpen
Dustin VanKrimpen

Posted on • Originally published at dustinvk.com

Using My Local Coding Model From Anywhere

In the last post I got a coding model (qwen2.5-coder:14b) running on a repurposed GPU (Radeon RX 6750 XT 12GB) that AMD's compute stack officially won't support. Which is great, except for one thing: the box it runs on sits in a corner in my basement. A local model you can only use while physically sitting at the machine it runs on is just inconvenient.

The whole point of a local tier is that it is there when I am. At my laptop on the couch, on my phone waiting in line somewhere, from a coffee shop. This post is the connective tissue, detailing how I reach that headless box from anywhere without punching a single hole in my router.

Why the obvious answer is the wrong one

Two things I wanted, and they pull against each other:

  1. Reach the machine, and its model endpoint, from any of my devices, wherever I am.
  2. Expose nothing to the public internet.

The naive way to do (1) is port forwarding: tell your router to send traffic on some port straight to the box. It works, and it flatly violates (2). And it is especially reckless here, because the model endpoint has no authentication. Ollama does not ask for a password. Anyone who can reach it can use it. Forwarding that to the open internet means giving your GPU to the first scanner that wanders by.

So port forwarding is out. What I wanted was a way for my own devices to reach each other as if they were on the same local network, no matter what physical network each one is actually on, without anything being visible to anyone else. Keeping everything private, with nothing exposed, is the whole point.

Tailscale: a private network that follows your devices around

The tool I settled on is Tailscale. It is a mesh VPN built on WireGuard. It's not the kind of VPN that hides your traffic from your ISP. Instead, it's the kind that connects your own devices to each other. You install it on each machine, they all join one private network (your "tailnet"), and every device gets a stable address in the 100.x range that is reachable from any other device on the tailnet, wherever they physically are.

The clever part is how the connection gets made. A coordination server helps your devices find each other and then gets out of the way. The actual traffic is a direct, end-to-end encrypted, peer-to-peer link. There is no port forwarding, and nothing is exposed to the public internet. The same mechanism carries everything: SSH on port 22, the model endpoint on :11434, and whatever else I run, all over the tailnet. Nothing public.

A few practical notes:

  • The free Personal tier is plenty. Unlimited devices of your own, up to 6 users. A handful of personal machines is nowhere near any limit, and it costs nothing.
  • Install it on everything, one account. The GPU box, the laptop, the phone, all on the same tailnet.
  • MagicDNS gives your devices names instead of making you memorize 100.x addresses... in theory. See the gotchas.

Note: For those who would rather not trust Tailscale's coordination server at all, there is an open-source, self-hostable replacement for it called Headscale. I plan on trying it out at some point and documenting my verdict here in the future, so stay tuned!

Two gotchas

This is the part worth documenting, because these are the things that will make someone bounce off Tailscale thinking it's broken when it isn't.

The Mac app can get stuck, and the fix is the CLI build. The standalone Tailscale Mac app leans on a macOS system extension, and that extension can wedge itself: no login prompt, the VPN toggle throws errors, and reinstalling does nothing. It is a known issue, more likely if you ever had an old App Store install or you're on a newer macOS. This happened to me, unfortunately. The fix that worked was installing the Homebrew CLI build instead (brew install tailscale), which uses the utun interface and sidesteps the system extension entirely. The tradeoff is you lose the menu-bar GUI and drive it from the terminal. Really no problem at all for me. It connects reliably, which is the part that matters.

MagicDNS short names are flaky on that CLI build. Once you are on the Homebrew CLI client, MagicDNS does not wire into the Mac's resolver as cleanly as the GUI app does. The name you set up may just not resolve. The fix is to use the full MagicDNS name or the raw 100.x IP. Slightly annoying, entirely livable once you know it. It's worth noting the MagicDNS names resolved fine on the Android client.

SSH, and making it survive real life

With the tailnet up, SSH into the box is just <!--email_off-->ssh you@100.x.x.x<!--email_on--> from any of your devices. A few things make it pleasant instead of fiddly:

  • Key auth, no passwords. Standard ssh-copy-id. If you import your keys from GitHub during the Ubuntu install, this is basically already done.
  • An SSH config alias so you can type ssh yourMachine and never think about IPs again. In ~/.ssh/config, point Host yourMachine at the raw 100.x address. This has a nice side effect: because you hardcoded the IP, the alias works even when MagicDNS is being flaky. It routes around the gotcha above entirely.
  • tmux for anything long-running. An SSH session dies when your laptop sleeps or your WiFi drops, and anything running directly in that session dies with it. tmux runs the session on the box, decoupled from your connection, so you start something, detach, close the lid, and reattach later from a different device exactly where you left off. This is load-bearing once you are kicking off unattended work.
  • mosh for the phone. Plain SSH drops when your phone roams between WiFi and cellular. mosh survives that, so a mobile session does not die every time you walk out of the building. mosh plus tmux is the mobile combo.

Exposing the model endpoint (only to the tailnet)

Here is the piece specific to the model. By default Ollama binds to 127.0.0.1, meaning it only listens to the box itself. To let other devices reach it, you tell it to listen on all interfaces with a systemd drop-in that sets OLLAMA_HOST=0.0.0.0.

That sounds alarming, and it would be, except for what actually holds the boundary. The bind is broad, but the firewall is the fence. ufw is set to default-deny incoming, with a single allow rule scoped to the tailnet interface (ufw allow in on tailscale0). So the endpoint listens broadly, but it's only reachable over the tailnet. Nothing on the physical LAN or the public internet can touch it. This matters enormously precisely because the endpoint is unauthenticated: the firewall scoping is the only thing standing between "my private model" and "a GPU for anyone on the internet." It is not optional.

The one command that proves the whole chain

There are three moving parts stacked here: the broad bind, the ufw rule, and Tailscale itself. When something doesn't connect, it is tempting to guess which layer is at fault. Don't guess. Prove it from a real client with one request:

curl http://100.x.x.x:11434/api/tags
Enter fullscreen mode Exit fullscreen mode

Run that from the laptop, not from the box. If it returns JSON listing your models, the entire chain is proven end to end. The bind is listening, the firewall is allowing tailnet traffic, and Tailscale is carrying it. If it hangs or refuses, you've narrowed it to the network path rather than the model. This is the fastest "is it actually working" check, and I run it before wiring up anything else.

Actually using it from the laptop

Now the payoff. Anything that speaks to Ollama can be pointed at the box over the tailnet. The ollama CLI and most Ollama-aware tools respect an OLLAMA_HOST environment variable, so on the laptop:

export OLLAMA_HOST=http://100.x.x.x:11434
Enter fullscreen mode Exit fullscreen mode

and the CLI is now a remote control for the GPU box. Nothing runs locally on the laptop; it is purely a client. For tools that only speak the OpenAI API shape, Ollama also exposes an OpenAI-compatible endpoint at the /v1 path, which you point the tool at with any dummy API key.

The reason this works so cleanly is a small architectural point worth internalizing: the model is stateless per request, and "workspace context" is just files you put into the request. So you can gather context on the machine that has your files (the laptop) and send only the model call to the remote GPU. The code and the compute do not have to live in the same place.

In a future post, I will dive deeper into the tooling that you can connect to your local LLM for development. Currently, I've been experimenting with using JetBrains Gateway and Cline.

The honest tradeoff: plan a way back in

As configured, SSH into this box rides only the tailnet firewall rule. So if Tailscale fails to come up on boot, the machine is unreachable over the tailnet, and right now that is the main door.

The fix is cheap and does not compromise the posture: add a second ufw rule that allows SSH from your local network, scoped to your subnet (ufw allow from 192.168.x.0/24 to any port 22). That is still not public. Your LAN is not the internet, and you are not touching the router or forwarding any ports. You are just widening the way in from "tailnet only" to "tailnet, or the same physical network." When Tailscale is down and you are home, you can still walk over to your own network and get in.

The LAN rule only saves you when you are physically on the LAN. If the tunnel dies while you are out, you are still waiting until you get home to console in or reboot. But for a headless box sitting in my house, "I can always reach it from my own network" covers nearly every lockout worth worrying about, and it is a rule that only needs to be set once. The thing to actually avoid is having neither path, tailnet-only with no LAN fallback, on a box you have made a dependency.

Where this leaves the project

So now the model from post 1 is not stuck in a corner. I can reach it from my laptop or my phone, from anywhere, and point tools at it as if it were local, with nothing exposed to the internet and one curl to prove the whole path. That is the local tier.

The next question is the one that decides whether any of this was worth it: is the model actually good enough to trust with real software engineering work? A quantized 14B on a consumer card is capable, but capable enough to hand real tasks to and route around a frontier model? The benchmarking of it will be my next post. And it has a twist, because the first numbers I got were badly wrong for reasons that had nothing to do with the model.

The bigger goal underneath all of it is the same: a coding agent that does the cheap work locally, for free and off any rate limit, and only spends frontier-model credits when the problem actually earns one. A free tier LLM reachable from anywhere is most of the setup for that. Now I need to know how much of my real work it can actually carry.

That is all for today. Go make the machine in your closet reachable from anywhere.

Top comments (0)