All Guides

17 / 25

Ingress & Public Access

Core Questions

  • How do agents access running services?
  • How do you expose local or ephemeral environments to the outside?
  • How do you secure public URLs for dev environments?

Your agent just made a change to the login flow. How does it verify the change works? It needs to hit the running service — but the service is on localhost:3000, and the agent might be running in a remote container. Ingress — the ability to reach your dev environment from outside — is infrastructure, not an afterthought. Without it, agents can't test what they build.

The access problem

Development environments are traditionally private. Your dev server runs on localhost, accessible only from your machine. This works for human developers sitting at that machine. It doesn't work for agents running elsewhere, webhooks from external services, or teammates who need to preview your changes.

Who Needs Access to Dev Environments

Agents verifying changes

An agent makes a code change and needs to test it against a running server. If the agent is in a different container or VM, localhost isn't reachable.

External webhooks

Stripe, GitHub, Twilio — these services need to POST to your endpoint. They can't reach localhost. You need a public URL.

Mobile devices

Testing a mobile app against your local API requires the phone to reach your dev server. Same network helps, but public URL is more reliable.

Preview for stakeholders

Product manager wants to see the feature before it merges. You need a URL they can open without SSH access or VPN setup.

CI/CD preview environments

Every PR gets a preview deployment. Reviewers, QA, and agents need stable URLs to test against before merge.

Tunnel services

Tunnels expose your local services to the public internet. You run a command, get a public URL, and traffic to that URL routes to your localhost. Simple concept, multiple implementations.

Tunnel Service Comparison

ngrok

The original. Stable, well-documented, good debugging UI. Free tier with random URLs, paid for stable subdomains. Good for quick testing.

ngrok http 3000

Cloudflare Tunnel

Free, fast, integrates with Cloudflare's network. Requires Cloudflare account. Good for production-like testing with their CDN.

cloudflared tunnel --url http://localhost:3000

Tailscale Funnel

Built into Tailscale. No separate account needed if you're already using Tailscale for networking. Stable URLs tied to your machine name.

tailscale funnel 3000

localtunnel

Open source, self-hostable. Less reliable than commercial options but no account required. Good for one-off testing.

lt --port 3000

Security warning

Tunnels expose your local services to the entire internet. Anyone with the URL can access them. Don't tunnel services with real credentials, sensitive data, or destructive operations without additional authentication.

Ephemeral URLs for dev environments

Ephemeral URLs live and die with your dev session. When you start working, you get a URL. When you stop, the URL dies. This is actually a feature — URLs that linger are URLs that can be abused.

# scripts/dev-with-tunnel.sh
#!/bin/bash

# Start dev server in background
npm run dev &
DEV_PID=$!

# Wait for server to be ready
until curl -s http://localhost:3000/health > /dev/null; do
  sleep 1
done

# Start tunnel and capture URL
TUNNEL_URL=$(cloudflared tunnel --url http://localhost:3000 2>&1 | grep -o 'https://[^ ]*')

echo "======================================="
echo "Dev server: http://localhost:3000"
echo "Public URL: $TUNNEL_URL"
echo "======================================="

# Write URL for agents to discover
echo "$TUNNEL_URL" > .tunnel-url

# Wait for interrupt
trap "kill $DEV_PID; rm .tunnel-url; exit" INT TERM
wait

Writing the tunnel URL to a file (.tunnel-url) lets agents discover it without parsing output. The URL changes every session, so this file is always regenerated.

Preview deployments

Preview deployments take a different approach: instead of tunneling to your local machine, they deploy your branch to a real environment with a stable URL. Every PR gets its own preview.

Preview Deployment Platforms

Vercel

Every push gets a unique URL. Comments on PR with preview link. Great for frontend-heavy projects. Built-in serverless functions.

Netlify

Similar to Vercel. Deploy previews per branch. Good for static sites and JAMstack. Branch subdomains available.

Railway

Ephemeral environments from PRs. Full backend support including databases. Per-branch environments mirror production config.

Render

Preview environments for PRs with full services (web, workers, databases). Good for complex backend deployments.

Principle

Preview URLs in specs make verification automatic

When you write a spec, include the preview URL pattern. Agents can construct the URL from the branch name and verify changes without asking for URLs.

# In your spec or AGENTS.md
preview_url_pattern: "https://{{branch}}.preview.example.com"

# Agent constructs URL from branch
# Branch: feat/oauth-google
# Preview: https://feat-oauth-google.preview.example.com

Securing public dev environments

A public URL to your dev environment is a security risk if not properly protected. You're exposing an environment with debug mode enabled, hot reload running, and possibly real credentials configured.

Security Layers for Dev Environments

Basic auth

Simple password protection. Stops casual snooping. Not secure against determined attackers but raises the bar.

ngrok http 3000 --basic-auth=" user:password"

OAuth/SSO

Require login through your identity provider. Cloudflare Access and ngrok Edge do this. Better than basic auth for teams.

IP allowlisting

Only allow connections from known IPs (office, VPN, agent infrastructure). Breaks mobile testing but good for controlled access.

mTLS (mutual TLS)

Both client and server present certificates. High security but more complex to set up. Good for agent-to-dev-environment communication.

Ephemeral URLs

Security through obscurity plus short lifetime. Random URLs that change frequently. Harder to discover, but not truly secure alone.

# cloudflared with Cloudflare Access
# Requires Cloudflare Access policy configured

cloudflared tunnel --url http://localhost:3000 \
  --hostname dev.example.com

# In Cloudflare Access, create policy:
# - Allow: email ends with @yourcompany.com
# - Allow: Service Token (for agents)

# Agents authenticate with service token header:
curl -H "CF-Access-Client-Id: $CLIENT_ID" \
     -H "CF-Access-Client-Secret: $CLIENT_SECRET" \
     https://dev.example.com/api/health

Webhook routing to local services

Webhooks are a special case: external services need to reach your dev environment on a stable URL. You can't just spin up a tunnel — you need the webhook URL configured in the external service.

Webhook Routing Strategies

Stable tunnel subdomain

Pay for a stable ngrok or Cloudflare subdomain. Configure webhooks once to point there. Tunnel to that subdomain when developing.

Webhook proxy service

Services like smee.io or webhook.site receive webhooks and forward them to your local machine. Good for development, not production testing.

Preview environment webhooks

Configure webhook URLs per environment. Preview deployments get their own webhook endpoints. More setup but more realistic testing.

# Using smee.io for GitHub webhook development

# 1. Get a smee channel URL
npx smee-client -u https://smee.io/YOUR_CHANNEL -t http://localhost:3000/webhook

# 2. Configure GitHub webhook to point to smee URL
# https://smee.io/YOUR_CHANNEL

# 3. Webhooks flow: GitHub -> smee.io -> your local machine

# In production, replace with real URL
WEBHOOK_URL=${PREVIEW_URL:-https://smee.io/YOUR_CHANNEL}/webhook

Agent access patterns

Agents need reliable, discoverable access to the environments they're testing against. The access pattern depends on where the agent runs relative to the dev environment.

Agent-to-Environment Access Patterns

Same machine

Agent runs locally alongside dev server. Access via localhost. No tunneling needed. Fastest but limits agent isolation.

BASE_URL=http://localhost:3000

Same network (container/VM)

Agent in container, dev server on host. Use Docker host networking or host.docker.internal. No public exposure.

BASE_URL=http://host.docker.internal:3000

Remote agent, local dev

Agent in cloud, dev server on your laptop. Requires tunnel or VPN. Write tunnel URL to file for agent discovery.

BASE_URL=$(cat .tunnel-url)

Remote agent, preview deploy

Both in cloud. Agent accesses preview deployment URL. Most reliable pattern — no local machine involved.

BASE_URL=https://$BRANCH.preview.example.com
# AGENTS.md - Environment access configuration

## Accessing the Dev Environment

The dev server runs on port 3000. To access it:

### If you're running locally:
BASE_URL=http://localhost:3000

### If you're in a container on the same machine:
BASE_URL=http://host.docker.internal:3000

### If you're remote and a tunnel is running:
BASE_URL=$(cat .tunnel-url 2>/dev/null || echo "http://localhost:3000")

### If there's a preview deployment:
BASE_URL=https://${BRANCH_NAME}.preview.example.com

## Verifying Access
curl $BASE_URL/health should return {"status": "ok"}

Balancing access and isolation

The tension: agents need access to test, but too much access creates security risks. The solution is controlled access — specific ports, authenticated endpoints, and network boundaries.

Access Control Strategies

Expose only the web port

Tunnel or expose port 3000, not the database port or debug port. Agents interact through the HTTP API, not internal services.

Use test databases

Dev environments should have their own database, seeded with test data. Never expose access to production data through dev tunnels.

Disable dangerous endpoints

Admin endpoints, data deletion, migrations — disable or protect these when running with public access. Use environment flags.

Time-bound access

Tunnels should stop when work stops. Use session timeouts. Preview deployments should auto-delete after PR merge.

What goes wrong

Agent can't reach the dev server

Agent is running remotely but trying to hit localhost:3000. Connection refused. Agent reports "server not running" even though it is.

Fix: Document access patterns in AGENTS.md. Provide URL discovery mechanism (.tunnel-url file, environment variable, or derivable from branch name).

Tunnel URL changes mid-session

Free ngrok URLs change when you restart. Agent cached the old URL. Requests fail.

Fix: Pay for stable subdomains, or write URL to a file that agents read fresh each time. Better yet, use preview deployments with predictable URLs.

Security incident from exposed dev environment

Someone found your tunnel URL, accessed debug endpoints, extracted credentials or data.

Fix: Always use authentication on tunnels (basic auth minimum). Never tunnel with production credentials. Disable sensitive endpoints in dev mode. Treat every tunnel as potentially compromised.

Webhooks don't reach local environment

Webhook configured but tunnel wasn't running. Events lost. Development can't proceed without receiving webhooks.

Fix: Use webhook proxy services that queue events. Configure webhooks to stable URLs (paid tunnels or preview environments). Add webhook receipt logging so you can see what's arriving.

Summary

  • Ingress to dev environments is infrastructure — agents can't test what they can't reach
  • Tunnels (ngrok, Cloudflare, Tailscale) expose localhost to the internet — fast setup but security-sensitive
  • Preview deployments provide stable URLs per PR — more setup but more reliable for CI and agent access
  • Always authenticate public dev environments — basic auth minimum, SSO or mTLS for sensitive work
  • Document access patterns in AGENTS.md so agents know how to reach the running service

Stay updated

Get notified when we publish new guides or make major updates.
(We won't email you for little stuff like typos — only for new content or significant changes.)

Found this useful? Share it with your team.