Frozen Staging Deploys: When a Tunnel Replaces the Ritual
The inbound deploy port is gone: KotaPortal staging is now served through an outbound-only tunnel, with no per-commit deploy ritual.
TL;DR
After a Cloudflare Tunnel took over backend staging, the per-commit deploy job disappeared, replaced by a persistent staging link that updates as the origin refreshes. The tunnel is outbound-only on port 7844, so the origin needs no public IP and loses its scannable deploy port, a real security win. Frontend keeps preview hosting; most 502s mean a wrong Service URL.
The staging deploy status sat frozen in the pipeline dashboard that morning. The green checkmark that used to follow every git push to the staging branch had turned into a gray strikethrough. The per-commit deploy ritual, a fixture of the routine for months, disappeared after a Cloudflare Tunnel took over staging duty for the KotaPortal project. No failure notification arrived; just the silence of a process that no longer runs.
The first thing that changed was the daily habit. A push to staging used to be followed by waiting for the deploy job before the review link could be shared; now the staging link stays alive and its content turns over as soon as the origin refreshes. Commit history remains recorded in Git, not in deploy job logs. The freeze sounded like a step backward, but on inspection it only moved the point of work: what gets repeated is no longer copying files to a server, it is keeping the origin healthy and its services answering.
The First Assumption That Missed
The initial suspicion was a misconfiguration on the frontend hosting side. Build logs got checked, environment variables got verified, and the working assumption was that staging would keep running through the provider's built-in preview mechanism. A manual rebuild was even triggered to test the hypothesis. The assumption turned out to be wrong. The problem was not a failed build but a deliberate architectural decision: the backend no longer deploys to the VPS on every commit, and its staging door moved entirely.
Backends Do Not Fit the Preview Pattern
Applications that cannot live on preview-first hosting, such as Go APIs, internal admin panels, or services that need direct access to an internal database, require a different staging door. This is where cloudflared changes the infrastructure model. It establishes outbound-only connections to Cloudflare's network, so the origin server does not need a publicly routable IP address [1].
This is the most commonly misunderstood part. The origin's firewall is allowed to block all inbound traffic, because every legitimate request arrives from inside an already-authenticated tunnel [1]. Setup is managed through the dashboard: install the connector on the origin, add a published application route, and point the Service URL at the application running locally on the origin, the same shape as the official documentation example that targets a local app over HTTP [2]. One hard prerequisite comes first: the origin must be able to reach Cloudflare on port 7844 [2]. If an outbound firewall rule blocks that port, the connection never forms and the tunnel never comes online.
For a small team, the pattern also removes an entire class of problem: version drift between staging and local machines. As long as the origin pulls from the same staging branch, what reviewers see and what developers run locally sit at an identical Git ref. The question "which commit is staging running?" no longer needs an answer dug out of old deploy logs.
The pattern contrasts with what frontend hosting provides out of the box. There, a push to any non-production branch automatically triggers an isolated preview deployment [3]. For longer phases, there is the dedicated staging branch with its own domain that accumulates changes before merging to production [4]. Modern CI pipelines model the same idea as named environments with protection rules [5]. The healthiest combination keeps the responsibilities separate: the frontend rides preview features, the backend and internal apps use the tunnel, and both meet in the same Git workflow. Reviewers never need to know two different mechanisms sit behind it.
The Missing Port Is a Win
Losing the per-commit deploy ritual for the backend is not a loss. Removing the inbound port that stayed open as the deploy target raises the security posture immediately: attackers no longer have a door to scan. The old approach of IP-based restrictions on a port that remains open was never truly equivalent, because the port itself still exists and can still be found.
One small configuration pitfall is worth remembering. When the Service URL points at an inactive service on the internal network, the tunnel answers 502 because cloudflared cannot reach the origin it was told to target. The diagnosis order after that incident: check the service name and port in the Service URL first, then dig into deeper network layers. Most cases end at the first check.
Sources
[1] Cloudflare Tunnel, Cloudflare One docs
[2] Create a tunnel (dashboard), Cloudflare One docs
[3] Vercel Docs, Environments
[4] Vercel Docs, Deploying Git Repositories
[5] GitHub Docs, Deploying with GitHub Actions