Bring your own Ollama: running Factory builds on your own models through a Cloudflare Tunnel

A remote user wants the Factory to code with the 70B model sitting on their own workstation — not our hosted providers, not an API bill. The honest answer is that you cannot connect a laptop to a cloud agent directly, and WebMCP is the wrong tool. But there is a real, scriptable pattern: expose your local Ollama through an outbound Cloudflare Tunnel and hand the Factory the HTTPS URL. Here is the full automation, why it fits our architecture with no infra change, and an even-handed case for when you should and should not do it.

A question came up that is worth answering in public, because the obvious framing is wrong and the correct answer is more interesting: can a remote user run Factory builds on their own local Ollama instance and their own downloaded models?

The intuition is “let me connect my local Ollama to your site.” That is not how the data flows, and chasing it leads people toward things like WebMCP that do not solve the problem. This post walks through why, then gives you a complete, scripted, production-shaped way to actually do it with a Cloudflare Tunnel — and an honest account of when it is a good idea and when it is not.

The one fact that decides everything

The Factory’s coding agent runs server-side, in a pod on our cluster. When a build needs the model, the call goes pod to Ollama. It does not run in your browser.

So for your local model to do the work, our pod has to reach your Ollama — and your Ollama is listening on localhost:11434 on a machine behind your home or office NAT. There is no inbound path to a laptop. The fact that your browser can reach your own localhost is irrelevant, because the agent is not in your browser.

That also kills two tempting non-answers:

What is left is the pattern every comparable product actually uses (Cursor tells users to expose localhost with a tunnel; LibreChat, Open WebUI and Jan all require one): you publish your local Ollama as a public HTTPS endpoint through an outbound tunnel, and the Factory calls that URL.

Why a tunnel fits the Factory with zero infra change

Our build pods run under a strict egress network policy. They are allowed to reach port 443 to any public address (with RFC1918 private ranges blocked), plus DNS. Nothing else — not port 11434, not a private LAN IP.

That constraint is exactly why a tunnel works and a raw endpoint does not. A Cloudflare Tunnel terminates TLS at Cloudflare’s edge and gives you a https://... hostname on 443. Our pod can reach that today, unchanged. A bare http://your-laptop:11434 or a LAN address cannot leave the cluster.

It is also the same mechanism we already run our own services on — the cockpit lives behind a cfargotunnel.com CNAME. Nothing exotic here.

The real-life scenario

Maya is a platform engineer. She has a workstation with a big GPU and a Qwen coder model she has been happy with, and a strict rule that her company’s code does not get sent to a third-party inference API. She wants the Factory’s plan, build and verify loop, but she wants the coding to happen on her model, on her hardware, on her premises.

She does not want to babysit it either — she wants to run one command, get a URL, paste it into the Factory once, and have it survive a reboot.

Below is exactly that, in two tiers: a sixty-second ephemeral trial, and a stable named tunnel for real use.

Tier 1: the sixty-second trial (no Cloudflare account)

This uses Cloudflare’s ephemeral trycloudflare.com tunnel. It is perfect for proving the round trip before committing to anything. The URL is random and disappears when you stop the process.

#!/usr/bin/env bash
# byo-ollama-quick.sh - expose a local Ollama to the Factory via an ephemeral
# Cloudflare tunnel. No Cloudflare account needed. Good for a first trial only.
set -euo pipefail

PORT="${OLLAMA_PORT:-11434}"

command -v ollama     >/dev/null || { echo "Install Ollama:     https://ollama.com/download"; exit 1; }
command -v cloudflared >/dev/null || { echo "Install cloudflared: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/"; exit 1; }

# Bind Ollama to loopback only - the tunnel is the ONLY thing that reaches it.
export OLLAMA_HOST="127.0.0.1:${PORT}"
export OLLAMA_ORIGINS="*"
pgrep -x ollama >/dev/null || { ollama serve >/tmp/ollama.log 2>&1 & }

# Wait for the model server to answer.
until curl -fsS "http://127.0.0.1:${PORT}/api/tags" >/dev/null 2>&1; do sleep 1; done
echo "Ollama is up on 127.0.0.1:${PORT}"

# Start the ephemeral tunnel and scrape the public URL it prints.
: > /tmp/cf.log
cloudflared tunnel --url "http://127.0.0.1:${PORT}" >/tmp/cf.log 2>&1 &
url=""
for _ in $(seq 1 30); do
  url=$(grep -oE 'https://[a-z0-9-]+\.trycloudflare\.com' /tmp/cf.log | head -1) || true
  [ -n "$url" ] && break
  sleep 1
done
[ -n "$url" ] || { echo "Tunnel did not come up; see /tmp/cf.log"; exit 1; }

echo
echo "Paste this into the Factory as your Ollama endpoint:"
echo "  $url"
echo
echo "Verify it from the outside:"
echo "  curl -fsS $url/api/tags | head"

Run it, copy the URL, paste it into your Factory Ollama setting, kick off a build. That is the whole loop. When you are done, Ctrl-C it and the URL is gone.

Tier 2: a stable named tunnel, scripted against the Cloudflare API

The ephemeral URL is fine for a demo and wrong for real work — it changes every run and has no access control. For Maya’s actual setup we want a fixed hostname on her own domain, created and routed entirely through the Cloudflare API, and fronted by Cloudflare Access so it is not an open inference server on the internet.

You need: a domain already on Cloudflare, and an API token scoped to Account: Cloudflare Tunnel: Edit and Zone: DNS: Edit.

#!/usr/bin/env bash
# byo-ollama-named.sh - a STABLE Cloudflare named tunnel for your own domain,
# created via the Cloudflare API, fronted by Cloudflare Access.
# usage: ./byo-ollama-named.sh ollama.example.com
set -euo pipefail

: "${CF_API_TOKEN:?set CF_API_TOKEN (Account: Cloudflare Tunnel:Edit, Zone: DNS:Edit)}"
: "${CF_ACCOUNT_ID:?set CF_ACCOUNT_ID}"
: "${CF_ZONE_ID:?set CF_ZONE_ID}"
HOSTNAME="${1:?usage: $0 ollama.example.com}"
PORT="${OLLAMA_PORT:-11434}"
TUNNEL_NAME="${TUNNEL_NAME:-factory-ollama}"
API="https://api.cloudflare.com/client/v4"
auth=(-H "Authorization: Bearer ${CF_API_TOKEN}" -H "Content-Type: application/json")

command -v jq >/dev/null || { echo "Install jq"; exit 1; }

# 1) Create the named tunnel (Cloudflare-managed config).
secret="$(openssl rand -base64 32)"
tunnel_id="$(curl -fsS "${auth[@]}" "${API}/accounts/${CF_ACCOUNT_ID}/cfd_tunnel" \
  --data "$(jq -n --arg n "$TUNNEL_NAME" --arg s "$secret" \
            '{name:$n, tunnel_secret:$s, config_src:"cloudflare"}')" \
  | jq -r '.result.id')"
echo "tunnel id: ${tunnel_id}"

# 2) Route the hostname to your local Ollama; everything else 404s.
curl -fsS "${auth[@]}" -X PUT \
  "${API}/accounts/${CF_ACCOUNT_ID}/cfd_tunnel/${tunnel_id}/configurations" \
  --data "$(jq -n --arg h "$HOSTNAME" --arg svc "http://127.0.0.1:${PORT}" \
            '{config:{ingress:[{hostname:$h, service:$svc}, {service:"http_status:404"}]}}')" \
  >/dev/null

# 3) DNS: CNAME the hostname at the tunnel (proxied/orange-cloud).
curl -fsS "${auth[@]}" -X POST "${API}/zones/${CF_ZONE_ID}/dns_records" \
  --data "$(jq -n --arg h "$HOSTNAME" --arg t "${tunnel_id}.cfargotunnel.com" \
            '{type:"CNAME", name:$h, content:$t, proxied:true}')" \
  >/dev/null || echo "DNS record may already exist - continuing"

# 4) Run Ollama (loopback) and the tunnel with its token.
token="$(curl -fsS "${auth[@]}" \
  "${API}/accounts/${CF_ACCOUNT_ID}/cfd_tunnel/${tunnel_id}/token" | jq -r '.result')"
export OLLAMA_HOST="127.0.0.1:${PORT}"; export OLLAMA_ORIGINS="*"
pgrep -x ollama >/dev/null || { ollama serve >/tmp/ollama.log 2>&1 & }

echo "Your stable endpoint: https://${HOSTNAME}"
echo "Now add a Cloudflare Access policy + service token for this hostname (see below),"
echo "and give the Factory the service-token headers, not just the URL."
exec cloudflared tunnel run --token "${token}"

Run it under a process manager so it comes back after a reboot. A minimal systemd unit:

# /etc/systemd/system/factory-ollama.service
[Unit]
Description=Factory BYO-Ollama tunnel
After=network-online.target
Wants=network-online.target

[Service]
Environment=CF_API_TOKEN=...  CF_ACCOUNT_ID=...  CF_ZONE_ID=...
ExecStart=/usr/local/bin/byo-ollama-named.sh ollama.example.com
Restart=always
RestartSec=5
User=maya

[Install]
WantedBy=multi-user.target

Close the open-door problem with Cloudflare Access

Ollama has no authentication of its own. A plain tunnel means anyone who learns the hostname can spend your GPU. Do not ship that. Put Cloudflare Access in front of the hostname with a service token, so only a caller presenting CF-Access-Client-Id and CF-Access-Client-Secret gets through. The Factory then sends those headers with every request — the tunnel URL stops being a bearer secret and becomes a properly authenticated endpoint. This is the difference between “a script that works” and “a thing you are willing to leave running.”

How the Factory consumes it

On our side the integration is small and already mostly in place:

Why you would do this

Why you might not

Be honest with yourself before you wire this into anything that matters:

The verdict

This is an alternative, not the default. For most users the right answer is the hosted providers or a subscription model — fewer parts, no uptime burden, real parallelism. But for the Mayas of the world — privacy-bound, opinionated about their own models, with hardware already on the desk — bringing your own Ollama over a Cloudflare Tunnel is a genuinely good fit, it costs us nothing in infrastructure, and as the scripts above show, it automates down to one command and a hostname.

If you run it, run it with Cloudflare Access in front. The tunnel is the easy part; treating an unauthenticated inference server with the seriousness it deserves is the part that actually matters.


← All posts