feat: built-in reverse proxy for multi-instance local development by jlaneve · Pull Request #2026 · astronomer/astro-cli

and others added 5 commits

March 3, 2026 14:06
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>

@jlaneve jlaneve marked this pull request as ready for review

March 3, 2026 23:05
- 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>

@jlaneve

@jlaneve jlaneve deleted the feat/portless-proxy branch

March 4, 2026 21:58