feat: built-in reverse proxy for multi-instance local development#2026
Open
feat: built-in reverse proxy for multi-instance local development#2026
Conversation
Adds a reverse proxy that routes <project>.localhost:6563 to the correct local Airflow instance, eliminating port collisions when running multiple projects simultaneously. The proxy auto-starts on first `astro dev start` and auto-stops when the last project stops. Key features: - HTTP reverse proxy routing by Host header (port 6563) - Random port allocation (10000-19999) for backend services - File-based route registry (~/.astro/proxy/routes.json) with locking - Daemon lifecycle management (auto-start, auto-stop, crash recovery) - Landing page at localhost:6563 showing active routes - `astro dev proxy status/stop` subcommands - `--no-proxy` flag and `proxy.enabled` config to opt out - Works with both Docker and standalone modes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix all golangci-lint issues: - Pass Route by pointer to AddRoute (hugeParam) - Use named constants for file permissions (mnd, gosec) - Fix gofumpt formatting (import order, struct alignment, const alignment) - Fix "marshalling" → "marshaling" (misspell) - Replace nil with http.NoBody in tests (httpNoBody) - Rewrite if-else chain as switch (ifElseChain) - Extract duplicate proxy registration in standalone.go (dupl) - Remove unused return from setupTestDir (unparam) Add worktree-aware hostname derivation: - Detect git worktrees via .git file (pure filesystem, no CLI/library deps) - Worktree URLs: <worktree>.<repo>.localhost (e.g. feature-branch.astro-cli.localhost) - Normal repos: <dir>.localhost (unchanged) - Works cross-platform (modern browsers resolve *.localhost per RFC 6761) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Make the reverse proxy always-on by default (--no-proxy as escape hatch) and try default ports (8080/5432) first, only falling back to random allocation when they're occupied. This preserves muscle memory for single-instance users while still supporting multi-instance development. - Remove proxy.enabled config gating; proxy is on unless --no-proxy - Export IsPortAvailable from proxy package for port probing - Docker mode: try default API/postgres ports before random allocation - Standalone mode: try default webserver port before random allocation - Switch routes.go to flock-based locking and atomic writes - Simplify proxy handler variable naming Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pull Request Test Coverage Report for Build f45b57a8-5f6e-4ff2-bb75-3d040315b3d6Details
💛 - Coveralls |
- Add HTTP server timeouts (ReadHeaderTimeout, WriteTimeout, IdleTimeout) to prevent slowloris attacks and goroutine leaks - Add graceful shutdown via SIGTERM/SIGINT signal handler with 5s grace - Cache reverse proxy instances per backend port with shared transport instead of allocating a new one per request - Store CLI version in PID file and restart daemon on version mismatch to prevent incompatibilities after CLI upgrades - Log warnings in removeProxyRoute instead of silently swallowing errors - Inline trivial proxyEnabled() wrapper - Fix readRoutes to handle whitespace-only routes files gracefully Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a built-in reverse proxy (inspired by vercel-labs/portless) that routes
<project>.localhost:6563to the correct local Airflow instance. This eliminates port collisions when running multiple projects simultaneously — no more manually configuring ports in each project's config..localhosthostname routed through the proxy--no-proxyescape hatch to disable and use classic fixed-port behaviorastro dev start, stops when last project stops, auto-restarts on crash<worktree>.<repo>.localhostwhen inside a git worktreeUser experience
Before (today)
Running two projects simultaneously requires manual port configuration to avoid collisions:
After (with this PR)
Just
astro dev startin each project. The first project keeps port 8080 (the default), while subsequent projects get random ports — but you access everything via the proxy URL and don't need to care about backend ports:Both are accessible simultaneously in the browser at their
.localhostURLs. No configuration needed. Single-instance users get the same default port (8080) they always had.Git worktrees
When working in a git worktree, the hostname includes both the worktree name and the repo name for clarity:
Detection is pure filesystem — checks whether
.gitis a file (worktree) vs directory (normal repo). Works cross-platform; all modern browsers resolve*.localhostto127.0.0.1per RFC 6761.Finding your ports
astro dev proxy statusshows all running projects and their ports:Opting out
If you prefer the old behavior with fixed ports and no proxy:
There is no config toggle — the proxy is always on unless
--no-proxyis explicitly passed.Crash recovery
If the proxy daemon crashes, the next
astro dev proxy statusauto-restarts it:All routes are preserved (stored in
~/.astro/proxy/routes.json), so every project is immediately accessible again.Architecture
Key design decisions
--no-proxyto disable).localhostURLs.gitfile check)gitCLI or go-git; cross-platformMode: "docker"skips PID check)docker compose up, so PID-based pruning would incorrectly remove routesroutes.json)New files
airflow/proxy/proxy.go— HTTP reverse proxy server with landing page and 404airflow/proxy/routes.go— Route registry with flock-based locking and atomic writesairflow/proxy/daemon.go— Daemon lifecycle (start/stop/ensure/auto-stop)airflow/proxy/ports.go— Smart port allocator: try defaults first, random fallback (10000-19999)airflow/proxy/hostname.go— Hostname derivation with git worktree detectioncmd/airflow_proxy.go—astro dev proxy status/stop/servesubcommandsModified files
airflow/docker.go— Proxy integration in Start/Stop/Kill; smart port defaultingairflow/standalone.go— Proxy integration in Start/Stop; smart port defaultingairflow/container.go—PortOverridesstruct for port injectioncmd/airflow.go—--no-proxyflagconfig/config.go/config/types.go—proxy.portconfig keyTest plan
IsPortAvailableexported wrapper testsastro dev start→ proxy auto-starts → browser openshttp://{name}.localhost:6563→ backend uses port 8080.localhosthostnames<worktree>.<repo>.localhost--no-proxyrestores original fixed-port behaviorastro dev proxy statusauto-restarts dead daemon*.localhost🤖 Generated with Claude Code