This file provides guidance to Claude Code (claude.ai/code) when working with the Navigator project.
Navigator is a Go-based web server for multi-tenant web applications. It provides framework independence, intelligent request routing, dynamic process management, authentication, static file serving, managed external processes, and support for deployment patterns like Microsoft Azure's Deployment Stamps and Fly.io's preferred instance routing.
IMPORTANT: Navigator is production-ready and actively developed.
- Single Implementation: All development targets
cmd/navigator/ - Production Proven: Battle-tested serving 75+ customers across 8 countries
- Legacy Retired: The legacy and refactored split has been unified into a single codebase
✅ Framework Independence: Support for Rails, Django, Node.js, and other web frameworks
✅ Modular Architecture: Well-organized internal packages in cmd/navigator/
✅ YAML Configuration Support: Modern YAML-based configuration
✅ Managed Processes: External process management (Redis, Sidekiq, workers, etc.)
✅ Dynamic Port Allocation: Finds available ports instead of sequential assignment
✅ PID File Management: Automatic cleanup of stale PID files with /tmp/navigator.pid
✅ Graceful Shutdown: Proper SIGTERM/SIGINT handling
✅ Static File Serving: Direct filesystem serving with try_files behavior
✅ Authentication: htpasswd support with multiple hash formats
✅ Configuration Reload: Live reload via SIGHUP signal without restart
✅ Machine Idle Management: Fly.io machine auto-suspend/stop after idle timeout
✅ Intelligent Fly-Replay: Smart routing with automatic fallback for large requests
✅ WebSocket Support: Full WebSocket connection support with standalone servers
✅ High Reliability: Automatic retry with exponential backoff for proxy failures
✅ Simple Configuration: Flexible variable substitution system for multi-tenant apps
✅ Structured Logging: Source-identified output with configurable JSON format for all processes
✅ Lifecycle Hooks: Server and tenant hooks for custom integration and automation
✅ CGI Script Support: Execute standalone scripts with user switching and config reload
✅ Comprehensive Documentation: Complete documentation site at https://rubys.github.io/navigator/
Navigator has a single, production-ready implementation:
cmd/navigator/- Production version with modular, well-organized codebase- Uses shared
internal/packages for maintainability - All development happens here
- Battle-tested in production
- Uses shared
The refactored navigator uses focused internal packages for maintainability:
- internal/server/: HTTP handling, routing, static files, proxying
- internal/process/: Web app and managed process lifecycle
- internal/config/: Configuration loading and validation
- internal/auth/: Authentication (htpasswd)
- internal/proxy/: Reverse proxy and Fly-Replay logic
- internal/idle/: Fly.io machine idle management
- internal/errors/: Domain-specific error constructors
- internal/logging/: Structured logging helpers
- internal/utils/: Common utilities (duration parsing, environment, etc.)
Design principles:
- Focused modules: Each package has a single, clear responsibility
- Easy deployment: Single binary with minimal dependencies
- Clear dependencies: Only essential external Go packages
- Maintainability: Well-organized code with good separation of concerns
- Testability: Each module can be tested independently
-
Configuration Loading (
LoadConfig,ParseYAML,UpdateConfig)- YAML configuration format (nginx format removed)
- Supports template variable substitution for tenant configuration
- Live configuration reload via SIGHUP signal
-
Process Management (
AppManager,ProcessManager)- Web Apps: On-demand startup with dynamic port allocation
- Framework Configuration: Configurable runtime and server executables
- Managed Processes: External process lifecycle management
- PID File Handling: Automatic cleanup of stale processes
- Graceful Shutdown: Clean termination of all processes
-
HTTP Handler (
CreateHandler)- Rewrite Rules: URL rewriting with redirect, last, and fly-replay flags
- Authentication: Pattern-based auth exclusions with htpasswd
- Static Files: Direct filesystem serving with caching
- Try Files: File resolution for public content with multiple extensions
- Web App Proxy: Reverse proxy to web applications with method exclusions
- Standalone Servers: Proxy support for external services (Action Cable, etc.)
- Suspend Tracking: Request tracking for idle machine suspension
- Proxy Retry: Automatic retry logic with exponential backoff
-
Static File Serving (
serveStaticFile,tryFiles)- Performance: Bypasses web framework for static content
- Try Files: Attempts multiple file extensions before web app fallback
- Content Types: Automatic MIME type detection
- Caching: Configurable cache headers
-
Idle Manager (
NewIdleManager)- Idle Detection: Monitors request activity
- Auto-Suspend/Stop: Suspends or stops Fly.io machines after idle timeout
- Auto-Wake: Machines wake automatically on incoming requests
-
Lifecycle Hooks (
executeHooks,executeServerHooks,executeTenantHooks)- Server Hooks: Execute at Navigator lifecycle events (start, ready, idle, resume)
- Ready Hooks: Run after initial start and after config reloads (CGI, SIGHUP, resume)
- Tenant Hooks: Execute at tenant lifecycle events (start, stop)
- Environment Propagation: Tenant hooks receive full tenant environment
- Sequential Execution: Hooks run in order with configurable timeouts
Navigator uses YAML configuration files:
# Display help
./bin/navigator --help
# Run with default config location
./bin/navigator # Looks for config/navigator.yml
# Run with custom config file
./bin/navigator /path/to/config.yml
# Reload configuration without restart
./bin/navigator -s reload
# Or send SIGHUP signal directly
kill -HUP $(cat /tmp/navigator.pid)- YAML configuration: Create and maintain YAML configuration files
- Navigator loads: YAML configuration with framework and tenant settings
- Framework configuration: Runtime executable and server settings applied
- Environment variables: Flexible variable substitution for each tenant
- Process startup: Web apps and managed processes started as needed
server:
listen: 3000
hostname: localhost
public_dir: public
# Static file configuration (modern approach)
static:
cache_control:
default: "0" # HTML pages: always revalidate
overrides:
- path: /assets/
max_age: 1y # 1 year for fingerprinted assets
immutable: true # Fingerprinted assets never change
allowed_extensions: [html, css, js, png, jpg] # optional, omit to allow all
try_files: [index.html, .html, .htm] # enables try_files feature
idle:
action: suspend # "suspend" or "stop" for Fly.io machines
timeout: 20m # Duration format: "30s", "5m", "1h30m"
applications:
pools:
max_size: 10
timeout: 5m # App process idle timeout (duration format)
start_port: 4000
tenants:
- name: tenant1
root: /path/to/app# Build Navigator
go build -o bin/navigator cmd/navigator
# Or use make
make build
# Run with configuration file
./bin/navigator config/navigator.yml
# Run with default config (looks for config/navigator.yml)
./bin/navigator# Install dependencies
go mod download
go mod tidy
# Format and check code
go fmt ./...
go vet ./...
# Build for different platforms
GOOS=linux GOARCH=amd64 go build -o navigator-linux cmd/navigator
GOOS=darwin GOARCH=arm64 go build -o navigator-darwin-arm64 cmd/navigatorNavigator can start and manage additional processes:
managed_processes:
- name: redis
command: redis-server
auto_restart: true
- name: sidekiq
command: bundle
args: [exec, sidekiq]
working_dir: /path/to/app
start_delay: 2Features:
- Auto-restart: Processes restart on crash if configured
- Start delays: Ensures proper initialization order
- Environment variables: Custom env for each process
- Graceful shutdown: Stopped after web apps to preserve dependencies
- Configuration updates: Managed processes updated on configuration reload
- PID file cleanup: Removes stale PID files before starting web apps
- Dynamic port allocation: Finds available ports in range 4000-4099
- Graceful shutdown: SIGINT/SIGTERM handling with proper cleanup
- Environment inheritance: Web apps inherit parent environment variables
- Direct serving: Static files served without web framework overhead
- Server-based configuration: All static file settings in
serversection - Try files: File resolution with multiple extensions (configured via
try_filesarray) - Flexible extensions: Serve specific file types or allow all files
- Content-Type detection: Automatic MIME type setting
- Cache control: Per-path cache header customization with
immutabledirective support- HTML pages:
max-age=0(always revalidate with Last-Modified) - Fingerprinted assets:
max-age=31536000, immutable(1 year, never changes)
- HTML pages:
- Public routes: Serves studios, regions, docs without authentication
- Idle actions: Supports both "suspend" and "stop" actions
- Idle timeout: Configurable inactivity period using duration format (e.g., "20m", "1h30m")
- Request tracking: Monitors active requests
- Automatic wake: Machines resume on incoming requests
- Zero-downtime: Seamless suspend/resume or stop/start cycles
- Multi-Target Routing: Support for three routing types:
- App-based: Route to any instance of a specific app
- Machine-based: Route to a specific machine instance
- Region-based: Route to a specific Fly.io region
- Smart Detection: Automatically uses reverse proxy for requests >1MB
- Automatic Fallback: Uses reverse proxy for requests >1MB
- Maintenance Pages: Serves custom 503 page when targets unavailable
- Pattern matching: Route specific paths to designated regions
- Status codes: Configurable HTTP response codes
- Method filtering: Apply rules to specific HTTP methods
- Deployment stamps: Support for distributed deployment patterns
- Automatic Fallback: Constructs internal URLs for direct proxy when needed
Navigator supports hooks for custom integration at key lifecycle events:
Server Hooks:
- start: Executes before Navigator starts accepting requests (blocks startup)
- ready: Executes asynchronously after Navigator starts listening or after configuration reload (doesn't block requests)
- resume: Executes once on first request after machine suspension (Fly.io)
- idle: Executes before machine suspension (Fly.io)
Tenant Hooks:
- start: Executes after a tenant web app starts
- stop: Executes before a tenant web app stops
- Environment: Tenant hooks receive the same environment variables as the tenant app
Configuration example:
hooks:
server:
start:
- command: /usr/local/bin/prepare-server.sh
timeout: 30
ready:
- command: curl
args: ["-X", "POST", "http://monitoring.example.com/ready"]
resume:
- command: /usr/local/bin/notify-resume.sh
args: ["Machine resumed from suspend"]
idle:
- command: /usr/local/bin/prepare-suspend.sh
tenant: # Default hooks for all tenants
start:
- command: /usr/local/bin/notify-tenant-start.sh
applications:
tenants:
- name: "2025/boston"
hooks: # Tenant-specific hooks
start:
- command: /usr/local/bin/boston-setup.shYAML supports flexible variable substitution for tenant configuration:
applications:
env:
RAILS_APP_DB: "${database}"
RAILS_APP_OWNER: "${owner}"
RAILS_STORAGE: "${storage}"
PIDFILE: "pids/${database}.pid"
tenants:
- name: 2025-boston
var:
database: "2025-boston"
owner: "Boston Dance Studio"
storage: "/path/to/storage/2025-boston"Variables defined in the var map are substituted using ${variable} syntax in environment templates.
Navigator handles web app process failures:
- Detection: Connection refused errors detected
- Cleanup: Stale PID files and processes cleaned up
- Restart: Process restarted via
GetOrStartApp() - Retry: Original request retried after restart
Navigator includes robust proxy error handling:
- Automatic Retry: Failed proxy connections retry with exponential backoff
- Smart Timeouts: Up to 3 seconds of retries for connection failures
- Request Preservation: GET/HEAD requests safely retried
- Graceful Degradation: Falls back to error response after max retries
- Port conflicts: Dynamic port allocation prevents conflicts
- Stale PID files: Automatic cleanup before starting
- Process crashes: Managed processes auto-restart if configured
- Authentication: Pattern-based exclusions for public assets
- Machine unavailable: Serves maintenance page during deployments
- Large uploads: Automatically falls back to reverse proxy for >1MB requests
CRITICAL IMPORTANCE: Comprehensive test coverage is essential for maintaining Navigator's reliability in production.
Navigator's test suite is organized by package:
- internal/proxy/ - Proxy retry logic, WebSocket handling, response buffering
- internal/server/ - Request routing, authentication, static files, fly-replay
- internal/process/ - Web app lifecycle, managed processes, graceful shutdown
- internal/config/ - Configuration parsing, validation, variable substitution
- internal/auth/ - htpasswd authentication with multiple hash formats
- internal/idle/ - Fly.io machine idle management and auto-suspend
- internal/logging/ - Structured logging helpers
- internal/utils/ - Duration parsing and utility functions
# Run all tests with coverage
go test -cover ./...
# Run tests for specific package
go test -v ./internal/proxy/...
# Run tests with race detection
go test -race ./...
# Run short tests only (skip slow integration tests)
go test -short ./...
# Generate coverage report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out# Test configuration loading
./bin/navigator /path/to/navigator.yml
# Test static file serving
curl -I http://localhost:3000/assets/application.js
# Test try_files behavior
curl -I http://localhost:3000/studios/raleigh # → raleigh.html
# Test web app proxy
curl http://localhost:3000/2025/boston/# Validate YAML configuration
./bin/navigator config/navigator.yml # Should start without errors
# Check process management
ps aux | grep -E '(redis|sidekiq|rails)' # See managed processesGitHub Actions automatically builds releases when version tags are pushed:
# Create annotated tag with release notes
git tag -a v1.0.0 -m "Navigator v1.0.0: Major Release
## New Features
- Managed process support
- Dynamic port allocation
- Improved PID file handling
## Bug Fixes
- Fixed graceful shutdown
- Resolved port conflicts"
git push origin v1.0.0The workflow creates binaries for:
- Linux: AMD64, ARM64 (tar.gz)
- macOS: AMD64, ARM64 (tar.gz)
- Windows: AMD64, ARM64 (zip)
All binaries include version information and build metadata.
CRITICAL: All code must pass CI checks. GitHub Actions will reject pushes that fail.
- ✓ Formatting -
gofmt -s -l .returns no files - ✓ Linting -
golangci-lint runpasses (0 errors) - ✓ Vet -
go vet ./...passes - ✓ Tests -
go test -race -cover ./...passes - ✓ Build - Both navigators build successfully
# Run all checks locally before pushing
gofmt -s -l . && \
golangci-lint run && \
go vet ./... && \
go test -race -cover -timeout=3m ./... && \
go build ./cmd/navigator && \
echo "✓ All CI checks passed!"Every bug is a missing test.
- Write a failing test that reproduces the bug
- Fix the bug with minimal changes
- Verify the test passes and covers edge cases
Every feature requires tests.
- Write tests before implementing the feature
- Cover success, failure, and edge cases
- Verify integration with existing components
Navigator uses minimal, focused dependencies:
- Go 1.24+: Modern Go features
- github.com/tg123/go-htpasswd: htpasswd file support (APR1, bcrypt, etc.)
- gopkg.in/yaml.v3: YAML configuration parsing
No complex web frameworks - uses Go standard library for HTTP handling.
Navigator has comprehensive documentation hosted at https://rubys.github.io/navigator/ including:
- Getting Started: Installation and basic configuration
- Configuration Reference: Complete YAML configuration options
- Examples: Working configurations for common scenarios (Redis, Action Cable, multi-tenant)
- Features: Detailed explanations of all Navigator capabilities
- Deployment: Production deployment guides and best practices
- Reference: CLI options, environment variables, signal handling
- Live Examples: All configuration examples are copy-paste ready
- Step-by-Step Guides: Clear instructions for setup and testing
- Comprehensive Coverage: 50+ pages covering all Navigator features
- Search Functionality: Full-text search across all documentation
- Mobile Responsive: Works perfectly on all devices
- Automatic Updates: Documentation deploys automatically via GitHub Actions
- Home: https://rubys.github.io/navigator/
- YAML Reference: https://rubys.github.io/navigator/configuration/yaml-reference/
- Examples: https://rubys.github.io/navigator/examples/
- CLI Reference: https://rubys.github.io/navigator/reference/cli/
The documentation source is in the docs/ directory and uses MkDocs with Material theme for generation.
Navigator uses Go's slog package for structured logging:
- Log Level: Set via
LOG_LEVELenvironment variable (debug, info, warn, error) - Default Level: Info level if not specified
- Debug Output: Includes detailed request routing, auth checks, and file serving attempts
- Structured Format: Text handler with consistent key-value pairs
- Process Output Capture: All stdout/stderr from managed processes and web apps is captured with source identification
- Output Formats:
- Text mode (default): Prefixed with
[source.stream]format (e.g.,[2025/boston.stdout],[redis.stderr]) - JSON mode: Structured JSON with timestamp, source, stream, message, and optional tenant fields
- Text mode (default): Prefixed with
- Multiple Destinations: Logs can be written to console and files simultaneously (Phase 3)
- File Output: Configurable file paths with
{{app}}template variable support - Configuration: Set via
logging:section in YAML config:format: text or json (defaults to text)file: optional file path with{{app}}template supportvector: Vector integration configuration (enabled, socket, config)
- Vector Integration: Professional log aggregation with automatic Vector process management
- Current Status: Phases 1-4 complete (basic capture + JSON output + multiple destinations + Vector integration). Phase 5+ available for advanced features
- Implementation Plan: See
docs/logging-implementation-plan.mdfor complete phased approach and current capabilities
- Single binary: No external dependencies beyond htpasswd files
- YAML configuration: Create and maintain YAML configuration files
- Process monitoring: Navigator manages web apps and external processes
- Resource efficiency: Lower memory footprint than nginx/Passenger
[Unit]
Description=Navigator Web Application Proxy
After=network.target
[Service]
Type=simple
User=rails
WorkingDirectory=/opt/rails/app
ExecStart=/usr/local/bin/navigator config/navigator.yml
Restart=always
[Install]
WantedBy=multi-user.targetNavigator aims to simplify deployment of multi-tenant web applications by providing a single binary that handles:
- Application lifecycle management
- Request routing and authentication
- Static file serving
- Process management
- Regional distribution
- Multi-tenant SaaS: Each customer gets their own database and instance
- Regional deployments: Deploy closer to users using Fly.io regions
- Deployment stamps: Microsoft Azure pattern for distributed applications
- Development environments: Replace complex nginx/Passenger setups
- Simplified configuration: More flexible variable substitution system
- Dynamic machine startup: Start new machines based on demand
- Per-user machines: One machine per user with auto-suspend
- Metrics: Prometheus/OpenTelemetry integration
- SSL termination: Optional HTTPS support for development
- Docker Hub releases: Easy inclusion via COPY --from=rubys/navigator
Navigator provides reusable utility packages to reduce code duplication:
Domain-specific error constructors with proper error wrapping:
import "github.com/rubys/navigator/internal/errors"
// Use standardized error constructors
return errors.ErrTenantNotFound(tenantName)
return errors.ErrConfigLoad(path, err)
return errors.ErrProxyConnection(target, err)Benefits: Consistent error messages, proper error chaining with %w, better debugging context.
Structured logging helpers for common patterns:
import "github.com/rubys/navigator/internal/logging"
// Instead of multi-line slog calls
logging.LogWebAppStart(tenant, port, runtime, server, args)
logging.LogProcessExit(name, err)
logging.LogConfigReload()Benefits: Concise one-line calls, consistent structured logging format, easier maintenance.
Common utilities including enhanced duration parsing:
import "github.com/rubys/navigator/internal/utils"
// Duration parsing with automatic logging of errors
timeout := utils.ParseDurationWithDefault(cfg.Timeout, 5*time.Minute)
// Duration parsing with contextual logging
delay := utils.ParseDurationWithContext(cfg.Delay, 0, map[string]interface{}{
"process": procName,
})Benefits: Eliminates duplicate duration parsing code, provides helpful error logging.
- Target navigator: All changes go to
cmd/navigator/andinternal/packages - Modular design: Place new functionality in appropriate internal packages
- Use utility packages: Adopt error constructors and logging helpers for consistency
- Minimal dependencies: Only add essential external packages
- Testing: Write tests for new functionality, ensure all tests pass
- Documentation: Update README.md, CLAUDE.md, and docs/ as needed
- Release process: Use annotated tags for GitHub Actions releases
See REFACTORING.md for detailed refactoring status and guidelines. Key principles:
- Safety first: All tests must pass before and after refactoring
- Incremental progress: Small, focused refactorings with clear commits
- Clear separation: Each module has a single, well-defined responsibility
- Maintainability: Code should be easier to read, test, and modify
- No behavioral changes: Refactoring should not change functionality
- YAML configuration: YAML is the only supported configuration format
- Modular architecture: Well-organized internal packages for maintainability
- Process management: Navigator handles both web apps and external processes
- Graceful shutdown: All processes cleaned up properly on termination
- Configuration reload: Update configuration without restart using SIGHUP
- Production ready: Used in production with 75+ dance studios across 8 countries