beyondcode/expose
Expose by BeyondCode shares local Laravel or PHP sites through secure public URLs. Create tunnels, use custom domains, inspect requests, and test webhooks or mobile apps without deploying.
Architecture fit. Expose is a sidecar CLI, not an application dependency. Nothing lands in app/, config/, service providers, or the container image — the client runs as a separate process and relays traffic to a port or hostname. From an architecture-review standpoint this is close to a zero-footprint adoption: no ADR is needed for the Laravel application itself, and rollback is literally "stop the command." What it does add is a new network path and a new trust boundary: any request traversing the tunnel leaves the developer's machine and passes through either a third-party relay or one we operate. That distinction — tooling layer versus ingress layer — is the single most important framing for the decision. Expose belongs in the developer-tooling and test-harness layer; it is not production ingress and should never be scoped as such.
Integration feasibility. High. Install options (PHAR, global Composer, Homebrew, Docker) all fit an existing PHP toolchain without introducing Node or Go. Time-to-first-value is roughly five minutes: install, expose auth token, expose share localhost:8000 or expose share myapp.test, and a public HTTPS URL exists. The recommended first proof is the webhook loop — point a Stripe/GitHub test endpoint at the tunnel, fire an event, watch it land in storage/logs/laravel.log. That is a one-day spike with a binary success criterion, which makes it cheap to approve and cheap to abandon.
Technical risk. Concentrated in four areas, all manageable with process rather than code:
Host header. That is RFC-correct and a good signal about maintainer quality, but it changes observable behavior for anything host-dependent: TrustHosts/allowlists, tenant resolution from host, Route::domain(), absolute-URL generation, and URL::signedRoute()/temporarySignedRoute() where the host participates in the signature. Treat this as a behavior change requiring a regression test before rollout, with 3.2.3 pinned as a one-cycle fallback.APP_URL and SESSION_DOMAIN will produce wrong links, broken redirects, or login loops unless scoped correctly for the session. This is the most common "it's a bug" report that turns out to be configuration.Key questions for the decision.
Stack fit. Native to PHP/Laravel. Global Composer install keeps it inside the toolchain we already patch and audit; PHAR is better for pinning and checksum verification. It works with artisan serve, Valet/Herd (use the hostname, not the port, so Host-dependent routing and tenant resolution behave as they do locally), Sail/Compose (run the client on the host against the published port; localhost inside a container refers to the container), and devcontainers (install and auth in postCreate, launch manually per session).
Migration path. There is no application migration — that's the point. What migrates is workflow: from a shared staging ingress used to test provider callbacks, or from a paid tunnel. Both are parallel-run candidates for one sprint: keep the existing path live, run the tunnel alongside, compare time-to-verified-webhook. The secondary migration path is to a self-hosted relay (expose serve on a VPS with wildcard DNS, then --server=https://tunnel.example.com on the client) — that is the only real integration work in the package and should be gated on a compliance or contract trigger, not on enthusiasm.
Compatibility. Effectively version-agnostic because it operates at the HTTP layer, so it won't constrain Laravel upgrade planning. The compatibility surface is configuration, not code: TrustProxies for forwarded host/proto, TrustHosts allowlists, APP_URL, SESSION_DOMAIN set to null locally, and OAuth redirect URIs registered against a stable tunnel host. Raw-body signature verification still works because the proxy doesn't re-encode bodies — provided middleware reads raw input. WebSockets, SSE, and long-polling generally pass through but should be benchmarked before anything depends on them.
Sequencing.
TrustHosts, tenant resolution, absolute URLs, and signed URLs against 3.2.4. Keep 3.2.3 available as fallback for one cycle.APP_URL/session config, a Makefile or Composer target that prints the current tunnel URL and updates provider endpoints so re-pointing is one command.Maintenance. Client-side is near-zero and fully reversible: no application dependencies, no container changes, rollback = stop the process. The recurring obligations are version pinning and checksum verification, watching release notes for protocol behavior changes (3.2.4 is the template), and token rotation. Self-hosting inverts this — relay patching, TLS renewal, wildcard DNS, and capacity become owned operational work with a deploy that disconnects every tunnel at once.
Support. Adopt a triage taxonomy so tickets route correctly: 401/403 from the relay means token expired or missing; 502 means the relay reached the client but the local server is down or on the wrong port; 404 is ambiguous and should be checked in the inspector — if the request appears there with a body, the tunnel worked and route/host matching failed. Two policy calls belong to the PM, not the engineer: whether short-lived public access to a developer's local instance is an approved field-debugging pattern (likely yes, with guardrails, as an alternative to screen-sharing or debug builds), and that tunnel-level basic auth is never a substitute for the application's own authorization.
Scaling. Fits bursty, human-scale traffic: demos, QA handoff, webhook validation, partner previews. It does not fit production ingress, SLA-backed endpoints, DDoS exposure, or anycast requirements. The origin is a single developer machine, so concurrency is bounded by that laptop, not by the relay. If real-time is the day-to-day workload, self-host and benchmark before committing.
Failure modes to put in the runbook. Relay restart or deploy drops all tunnels simultaneously; free-tier subdomain reallocation breaks registered callbacks silently; the 3.2.4 Host change can break routing or signature validation in ways that look like application bugs; SESSION_DOMAIN/APP_URL mismatches manifest as login loops; inspector body truncation produces false "missing field" conclusions — verify against logs before chasing ghosts; developer machine sleep or network change drops the tunnel mid-demo; self-hosted certificate expiry or DNS misconfiguration is a total outage; and a leaked or un-revoked token is standing public access to a dev environment.
Ramp-up. Roughly five minutes per developer, and no framework knowledge required. The real learning curve is two configuration traps — hostname vs. port on Valet/Herd, and APP_URL/session domain — plus the habit of reserving a subdomain for anything that recurs. Ship a one-page runbook and the pinned install script; teach the webhook loop as the canonical first use case. For the PM, ramp-up is the decision framing itself: build-versus-buy for developer ingress, reserve-versus-rotate for hostnames, and self-host-versus-managed for the relay — each with a named owner, because the tool is free and the ownership is not.
How can I help you explore Laravel packages today?