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 3000Cloudflare Tunnel
Free, fast, integrates with Cloudflare's network. Requires Cloudflare account. Good for production-like testing with their CDN.
cloudflared tunnel --url http://localhost:3000Tailscale 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 3000localtunnel
Open source, self-hostable. Less reliable than commercial options but no account required. Good for one-off testing.
lt --port 3000Security 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
waitWriting 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.comSecuring 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/healthWebhook 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}/webhookAgent 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:3000Same 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:3000Remote 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
Related guides
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.