Skip to content

YAML Configuration Reference

Complete reference for all Navigator configuration options.

Configuration Structure Overview

Navigator's YAML configuration is organized into logical sections:

server:                    # HTTP server settings
  listen: 3000
  hostname: "localhost"
  root_path: "/prefix"
  static:                  # Static file configuration
    public_dir: "./public"
    allowed_extensions: [...]
    try_files: [...]
    normalize_trailing_slashes: true
    cache_control: {...}
  bot_detection:           # Bot detection and access control
    enabled: true
    action: reject
  idle:                    # Fly.io machine idle management
    action: suspend
    timeout: 20m

cable:                     # Built-in WebSocket support (TurboCable)
  enabled: true
  path: "/cable"
  broadcast_path: "/_broadcast"

auth:                      # Authentication (top-level)
  enabled: true
  realm: "Restricted"
  htpasswd: "./htpasswd"
  public_paths: [...]

maintenance:               # Maintenance page configuration
  page: "/503.html"

applications:              # Multi-tenant app configuration
  pools: {...}
  framework: {...}
  env: {...}
  tenants: [...]

managed_processes:         # External process management
  - name: redis
    command: redis-server
    ...

routes:                    # URL routing and rewriting
  rewrites: [...]
  reverse_proxies: [...]
  fly:                     # Fly.io-specific routing
    replay: [...]

hooks:                     # Lifecycle hooks
  server: {...}
  tenant: {...}

logging:                   # Logging configuration
  format: json
  file: "..."
  vector: {...}

server

HTTP server configuration.

server:
  listen: 3000                    # Port to listen on (required)
  hostname: "localhost"           # Hostname for requests (optional)
  root_path: "/showcase"          # Root URL path prefix (optional)
  trust_proxy: false              # Trust X-Forwarded-Host from upstream proxy (optional, default: false)

  # Health check configuration
  health_check:
    path: "/up"                   # Health check endpoint path (optional)
    response:                     # Synthetic response (optional)
      status: 200                 # HTTP status code
      body: "OK"                  # Response body
      headers:                    # Response headers
        Content-Type: "text/plain"

  # Static file configuration
  static:
    public_dir: "./public"        # Directory for static files (optional)
    allowed_extensions:           # Allowed file extensions (optional)
      - html
      - css
      - js
      - png
      - jpg
    try_files:                    # Try files suffixes (optional)
      - index.html
      - .html
      - .htm
    normalize_trailing_slashes: true  # Redirect directories to trailing slash (optional)
    cache_control:
      default: "1h"               # Default cache duration
      overrides:                  # Path-specific cache durations
        - path: "/assets/"
          max_age: "24h"

  # Machine idle configuration (Fly.io)
  idle:
    action: suspend               # "suspend" or "stop"
    timeout: 20m                  # Duration: "30s", "5m", "1h30m"
Field Type Default Description
listen integer/string 3000 Port to bind HTTP server
hostname string "" Hostname for Host header matching
root_path string "" Root URL path prefix (e.g., "/showcase")
trust_proxy boolean false Trust X-Forwarded-Host headers from upstream proxy (see server.md)

server.health_check

Health check endpoint configuration with optional synthetic response support.

server:
  health_check:
    path: "/up"
    response:                      # Optional - omit to proxy to application
      status: 200
      body: "OK"
      headers:
        Content-Type: "text/plain"
Field Type Default Description
path string "" Health check endpoint path (e.g., "/up")
response object nil Optional synthetic response configuration
response.status integer - HTTP status code (e.g., 200, 503)
response.body string - Response body text
response.headers map {} Response headers (e.g., Content-Type)

Synthetic Response Mode: When response is configured, Navigator returns the synthetic response directly without proxying to your application. This provides:

  • Fast response time (< 1ms)
  • Works even if applications are down/starting
  • No application startup required
  • Ideal for load balancers and container orchestration

Proxy Mode: When response is omitted, health check requests are forwarded to your application, allowing custom health check logic.

Example - Kubernetes:

health_check:
  path: "/healthz"
  response:
    status: 200
    body: "alive"

Example - Application Health Check:

health_check:
  path: "/up"
  # No response - proxies to Rails /up endpoint

server.static

Static file serving configuration.

server:
  static:
    public_dir: "./public"
    allowed_extensions: [html, css, js, png, jpg]
    try_files: [index.html, .html, .htm]
    normalize_trailing_slashes: true
    cache_control:
      default: "1h"
      overrides:
        - path: "/assets/"
          max_age: "24h"
        - path: "/images/"
          max_age: "12h"
Field Type Default Description
public_dir string "./public" Directory for static files
allowed_extensions array [] File extensions allowed (empty = all allowed)
try_files array [] Suffixes to try when resolving paths
normalize_trailing_slashes boolean false Redirect directories to trailing slash URLs
cache_control object - Cache header configuration
cache_control.default string "" Default cache duration (e.g., "1h")
cache_control.overrides array [] Path-specific cache configurations
cache_control.overrides[].path string - URL path prefix to match
cache_control.overrides[].max_age string - Cache duration (e.g., "24h")

Allowed Extensions: If omitted or empty, all files in public_dir can be served. If specified, only files with these extensions can be served.

Try Files: When present, Navigator attempts each suffix in order before falling back to the application.

Example: For request /studios/boston with try_files: [index.html, .html, .htm]: 1. Try public/studios/boston (exact match) 2. Try public/studios/boston/index.html 3. Try public/studios/boston.html 4. Try public/studios/boston.htm 5. Fall back to application

Normalize Trailing Slashes: When enabled, Navigator checks if a path without a trailing slash is a directory containing index.html. If found, it issues a 301 Moved Permanently redirect to the path with a trailing slash. This ensures relative paths in the HTML work correctly (e.g., <img src="logo.png"> resolves to /studios/boston/logo.png instead of /studios/logo.png). This matches standard nginx/Apache behavior.

server.bot_detection

Bot detection and access control configuration. Uses the isbot library for comprehensive bot identification.

server:
  bot_detection:
    enabled: true                 # Enable bot detection (default: false)
    action: reject                # Action: "reject", "ignore", "static-only"
Field Type Default Description
enabled boolean false Enable bot detection
action string "reject" Action to take when bot detected

Actions: - reject: Block bots with 403 Forbidden (prevents unnecessary application wake-ups) - ignore: Allow bots through (disables bot blocking) - static-only: Allow bots to access static files only, block dynamic content

Per-Tenant Override: Tenants can override the global bot detection settings:

server:
  bot_detection:
    enabled: true
    action: reject               # Global: block all bots

applications:
  tenants:
    - name: production
      path: /app/
      # Inherits global config (rejects bots)

    - name: demo
      path: /demo/
      bot_detection:
        enabled: true
        action: ignore           # Override: allow bots in demo

Use Case: The Showcase application uses global bot blocking to prevent unnecessary machine wake-ups from crawlers, but allows bots on the demo tenant for search engine indexing.

server.idle

Machine auto-suspend/stop configuration (Fly.io only).

Field Type Default Description
action string "" Action to take: "suspend" or "stop"
timeout string "" Idle duration before action (e.g., "20m", "1h")

server.cgi_scripts

CGI script configuration for executing standalone scripts directly.

server:
  cgi_scripts:
    - path: /admin/sync           # URL path (exact match)
      script: /opt/scripts/sync.rb # Path to executable script
      method: POST                 # HTTP method (optional, empty = all methods)
      user: appuser               # Unix user (optional, requires root)
      group: appgroup             # Unix group (optional)
      allowed_users:              # Access control (optional)
        - admin
        - operator
      timeout: 5m                 # Execution timeout (optional)
      reload_config: config/navigator.yml  # Reload config after execution (optional)
      env:                        # Additional environment variables
        RAILS_DB_VOLUME: /mnt/db
        RAILS_ENV: production
Field Type Required Description
path string Yes URL path for exact matching
script string Yes Absolute path to executable CGI script
method string No HTTP method restriction (GET, POST, etc.). Empty = all methods
user string No Unix user to run script as (requires Navigator running as root)
group string No Unix group to run script as
allowed_users array No Usernames allowed to access this script. Empty = all authenticated users
env map No Additional environment variables
reload_config string No Config file to reload after successful execution
timeout string No Execution timeout (e.g., "30s", "5m"). Zero = no timeout

Access Control: When allowed_users is specified, only those usernames can access the script (returns 403 Forbidden for other authenticated users). If allowed_users is empty or not specified, all authenticated users can access the script. Scripts on paths listed in auth.public_paths can be accessed without authentication.

See Also: CGI Scripts Documentation for detailed usage examples.

cable

Built-in TurboCable/WebSocket support for real-time features without external dependencies.

cable:
  enabled: true                   # Enable built-in WebSocket support (default: true)
  path: "/cable"                  # WebSocket endpoint path (default: "/cable")
  broadcast_path: "/_broadcast"   # Broadcast endpoint path (default: "/_broadcast")
Field Type Default Description
enabled boolean true Enable built-in WebSocket/Cable support
path string "/cable" WebSocket endpoint for client connections (requires authentication)
broadcast_path string "/_broadcast" Broadcast endpoint for Rails apps (localhost-only, no auth)

Overview

Navigator provides built-in WebSocket support for Rails applications using TurboCable. This eliminates the need for: - Separate Action Cable server process (~153 MB) - Redis or Solid Cable for pub/sub (~13 MB) - Additional configuration and dependencies

Memory savings: 89% reduction (163 MB → 18 MB per machine)

How It Works

  1. WebSocket Endpoint (/cable): Handles client WebSocket connections after authentication
  2. Broadcast Endpoint (/_broadcast): Receives broadcasts from Rails apps (localhost-only for security)
  3. In-Memory Subscriptions: Tracks subscriptions within Navigator process
  4. Zero Configuration: Works automatically with default settings

Security Model

  • /cable: Requires authentication, same as other protected endpoints
  • /_broadcast: Localhost-only (127.0.0.1, ::1, localhost), no authentication needed
  • Broadcasts from remote hosts return 403 Forbidden

Rails Configuration

Configure your Rails app to use Navigator's built-in WebSocket support:

# config/application.rb or config/environments/production.rb
config.turbo_cable_broadcast_url = ENV.fetch('TURBO_CABLE_BROADCAST_URL', 'http://localhost:3000/_broadcast')

Set the environment variable in Navigator:

applications:
  env:
    TURBO_CABLE_BROADCAST_URL: "http://localhost:3000/_broadcast"

Custom Endpoints

You can customize the WebSocket and broadcast endpoints:

cable:
  enabled: true
  path: "/websocket"              # Custom WebSocket path
  broadcast_path: "/internal/broadcast"  # Custom broadcast path

When using custom paths, update your Rails configuration accordingly:

config.turbo_cable_broadcast_url = "http://localhost:3000/internal/broadcast"

And configure the WebSocket path:

# config/application.rb
config.action_cable.mount_path = "/websocket"  # Match cable.path

Disabling Built-in WebSocket

To disable Navigator's built-in WebSocket support and use traditional Action Cable:

cable:
  enabled: false

managed_processes:
  - name: redis
    command: redis-server
  - name: action-cable
    command: bundle
    args: [exec, puma, -p, "28080", cable/config.ru]

Backward Compatibility

The built-in WebSocket feature is fully backward compatible: - Default configuration enables WebSocket automatically - Existing Action Cable setups continue to work - Both can coexist during migration - No breaking changes to existing configurations

See Also: TurboCable Feature Documentation for detailed usage, examples, and migration guide.

auth

Authentication configuration using htpasswd files.

auth:
  enabled: true                   # Enable/disable authentication
  realm: "Restricted"             # Authentication realm name
  htpasswd: "./htpasswd"          # Path to htpasswd file
  public_paths:                   # Simple patterns that bypass authentication
    - "/assets/"
    - "/favicon.ico"
    - "*.css"
    - "*.js"
  auth_patterns:                  # Advanced regex patterns for auth control
    - pattern: "^/showcase/2025/(boston|seattle)/?$"
      action: "off"               # "off" = bypass auth, or realm name
Field Type Default Description
enabled boolean false Enable authentication
realm string "Restricted" Basic Auth realm displayed in browser
htpasswd string "" Path to htpasswd file
public_paths array [] Glob/prefix patterns for paths that bypass auth
auth_patterns array [] Regex patterns with actions for auth control

Auth patterns support complex regex matching and are checked before public_paths. Each pattern has: - pattern: Regular expression to match against the request path - action: "off" to bypass auth, or a realm name to require auth with that realm

See Authentication for detailed examples and performance tips.

Supported htpasswd Formats

  • APR1 (Apache MD5)
  • bcrypt
  • SHA
  • MD5-crypt
  • Plain text (not recommended)

maintenance

Maintenance mode configuration.

maintenance:
  enabled: false                  # Enable maintenance mode (dynamic requests get maintenance page)
  page: "/503.html"               # Path to maintenance page (within public_dir)
Field Type Default Description
enabled boolean false Enable maintenance mode - serves maintenance page for dynamic requests (static files still served)
page string "/503.html" Path to custom maintenance page

Navigator serves the maintenance page in these scenarios:

Explicit Maintenance Mode (enabled: true):

What continues working during maintenance mode: - ✅ Health checks (/up) - for load balancer monitoring - ✅ Authentication - htpasswd rules still enforced - ✅ Static files - all files matching allowed_extensions - ✅ Try files - extensionless URL resolution (e.g., /page/page.html) - ✅ Redirects - configured URL redirects - ✅ Rewrites - URL path modifications - ✅ Fly-Replay routes - PDF/XLSX generation, cross-region routing - ✅ CGI scripts - configuration update endpoints - ✅ Reverse proxies - WebSocket connections, external service proxies

What shows maintenance page: - ❌ Dynamic web application requests only

This design ensures optimal user experience during maintenance - infrastructure and static content remain accessible while protecting the application during updates.

Automatic Error Handling (enabled: false, default): - An application is starting and exceeds the startup_timeout - A Fly-Replay target is unavailable

The maintenance page is served from {server.static.public_dir}/{maintenance.page} (e.g., public/503.html). If this file doesn't exist, Navigator serves a default maintenance page.

Recommended 503.html:

<!DOCTYPE html>
<html>
<head>
  <title>Service Starting (503)</title>
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <meta http-equiv="refresh" content="5">  <!-- Auto-refresh every 5 seconds -->
  <style>
    body {
      font-family: arial, sans-serif;
      text-align: center;
      padding: 50px;
    }
  </style>
</head>
<body>
  <h1>Service Starting...</h1>
  <p>The application is starting up. This page will refresh automatically.</p>
</body>
</html>

The <meta http-equiv="refresh" content="5"> tag automatically reloads the page every 5 seconds, so users don't need to manually refresh while waiting for the application to become ready.

Example: Startup Maintenance Mode

Use maintenance mode during container startup to provide immediate user feedback while initialization completes:

# config/navigator-maintenance.yml - Initial startup config
server:
  listen: 3000
  static:
    public_dir: public

maintenance:
  enabled: true  # Dynamic requests get 503 page with auto-refresh, static files served
  page: /503.html

hooks:
  server:
    ready:
      - command: /app/script/initialize.sh  # Sync S3, generate config, etc.
        timeout: 5m
        reload_config: config/navigator.yml  # Switch to normal operation

Flow: 1. Navigator starts with navigator-maintenance.yml (maintenance enabled) 2. Server listens immediately, serves 503.html to dynamic requests (static files still served) 3. Ready hook runs asynchronously (initialization tasks) 4. Hook completes and triggers reload to navigator.yml (maintenance disabled) 5. Normal operation begins

This provides fast cold starts (~1s to first response) while long-running initialization happens in the background.

applications

Application configuration for multi-tenant deployments.

applications:
  # Process pool configuration
  pools:
    max_size: 10                  # Maximum app processes
    timeout: 5m                   # Idle timeout (duration format)
    start_port: 4000              # Starting port for allocation
    default_memory_limit: "512M"  # Default memory limit per tenant (Linux only)
    user: "rails"                 # Default user to run tenants as (Unix only)
    group: "rails"                # Default group to run tenants as (Unix only)

  # Startup configuration
  health_check: "/up"             # Default health check endpoint
  startup_timeout: "5s"           # Timeout before showing maintenance page

  # WebSocket connection tracking (default: true)
  track_websockets: true          # Track WebSocket connections globally

  # Framework configuration (optional, can be per-tenant)
  framework:
    command: bin/rails
    args: ["server", "-p", "${port}"]
    app_directory: "/rails"
    port_env_var: PORT
    start_delay: 2s

  # Environment variable templates
  env:
    DATABASE_URL: "sqlite3://db/${database}.sqlite3"
    RAILS_ENV: production

  # Individual tenant applications
  tenants:
    - path: "/tenant1/"           # URL path prefix (required)
      var:                        # Template variables
        database: tenant1
      env:                        # Tenant-specific environment
        TENANT_NAME: "Tenant 1"

      # Optional tenant-specific overrides
      root: "/app"                # Application directory
      public_dir: "public"        # Public files directory
      framework: rails            # Framework type
      runtime: bundle             # Runtime command
      server: exec                # Server command
      args: ["puma", "-p", "${port}"]  # Server arguments
      health_check: "/health"     # Override: custom health check endpoint
      startup_timeout: "10s"      # Override: wait longer for this tenant
      track_websockets: false     # Override: disable WebSocket tracking
      memory_limit: "1G"          # Override: memory limit for this tenant (Linux only)
      user: "app"                 # Override: user for this tenant (Unix only)
      group: "app"                # Override: group for this tenant (Unix only)

      # Tenant-specific lifecycle hooks
      hooks:
        start:
          - command: /app/bin/tenant-init.sh
            timeout: 30s
        stop:
          - command: /app/bin/tenant-cleanup.sh
            timeout: 30s

applications.pools

Process pool management for tenant applications.

Field Type Default Description
max_size integer 10 Maximum number of app processes
timeout string "5m" Idle timeout before stopping processes (duration: "5m", "10m"). Also controls automatic cleanup of deleted tenants after config reload.
start_port integer 4000 Starting port for dynamic allocation
default_memory_limit string "" Default memory limit (e.g., "512M", "1G") - Linux only, requires root
user string "" Default user to run tenant processes as - Unix only
group string "" Default group to run tenant processes as - Unix only

Note: The timeout setting controls both resource management (stopping idle processes) and configuration reload cleanup (automatically removing deleted tenants). See Configuration Hot Reload - Tenant Lifecycle for details on tenant behavior during config reload.

Memory Limits (Linux only): - Requires running Navigator as root on Linux with cgroups v2 - Uses Linux cgroups to enforce per-tenant memory limits - When a tenant exceeds its limit, the kernel OOM kills only that tenant - OOM-killed tenants are removed from the registry and restart on next request - Cgroups persist during idle timeout, only cleaned up on Navigator shutdown - Supported formats: "512M", "1G", "2048M", "1.5G" - On non-Linux or non-root: Configuration is ignored (graceful degradation)

User/Group Credentials (Unix only): - Runs tenant processes as specified non-root user for security isolation - Navigator must run as root to drop privileges to specified user/group - Enhances security by limiting tenant process permissions - On Windows or when not running as root: Configuration is ignored

applications.health_check

Global default health check endpoint for application readiness detection. Can be overridden per-tenant.

Field Type Default Description
health_check string "/" HTTP endpoint for health checks (e.g., "/up", "/health")

applications.startup_timeout

Global timeout for waiting for applications to become ready before serving the maintenance page. Can be overridden per-tenant.

Field Type Default Description
startup_timeout string "5s" Duration to wait for app startup (e.g., "5s", "10s", "30s")

When an application is starting, Navigator waits up to this duration for the health check to pass. If the timeout is reached, Navigator serves the configured maintenance page (typically public/503.html) instead of returning a 502 error. The maintenance page can include <meta http-equiv="refresh" content="5"> to auto-refresh.

Example:

applications:
  startup_timeout: "10s"    # Wait 10 seconds globally

  tenants:
    - name: slow-app
      path: /slow/
      startup_timeout: "30s"  # Override: wait 30 seconds for this tenant

applications.track_websockets

Global setting for WebSocket connection tracking. When enabled, Navigator tracks active WebSocket connections to prevent apps from shutting down during idle timeouts. Can be overridden per-tenant.

Field Type Default Description
track_websockets boolean true Track WebSocket connections to prevent idle shutdown

When to disable: Tenants that proxy WebSockets to standalone servers (e.g., separate Action Cable) or don't handle WebSockets directly.

applications.framework

Default framework configuration (can be overridden per-tenant).

Field Type Default Description
command string "" Framework command (e.g., "bin/rails")
args array [] Command arguments with ${port} substitution
app_directory string "" Application root directory
port_env_var string "PORT" Environment variable for port number
start_delay string "0s" Delay before starting (duration format)

applications.env

Environment variable templates with ${variable} substitution from tenant var values.

applications.tenants

List of tenant applications.

Field Type Required Description
path string URL path prefix (must start/end with /)
var object Template variables for env substitution
env object Tenant-specific environment variables
root string Application root directory
public_dir string Public files directory
framework string Framework type override
runtime string Runtime command override
server string Server command override
args array Server arguments override
health_check string Health check endpoint override (e.g., "/up")
track_websockets boolean Override WebSocket tracking (nil = use global)
bot_detection object Override bot detection settings (nil = use global)
memory_limit string Memory limit override (e.g., "1G") - Linux only
user string User override (runs as this user) - Unix only
group string Group override (runs as this group) - Unix only
hooks object Tenant-specific lifecycle hooks

Note: The name field is automatically derived from the path (e.g., /showcase/2025/boston/2025/boston).

Per-Tenant Memory Limits: Useful for tenants with different resource requirements. For example, a large event might use memory_limit: "1G" while smaller events use the pool default of 512M.

managed_processes

External processes managed by Navigator.

managed_processes:
  - name: redis                   # Process identifier
    command: redis-server         # Command to execute
    args: ["--port", "6379"]     # Command arguments
    working_dir: "/var/lib/redis" # Working directory
    env:                         # Environment variables
      REDIS_PORT: "6379"
    auto_restart: true           # Restart on crash
    start_delay: 0               # Delay before starting (seconds)
Field Type Required Description
name string Unique process identifier
command string Command to execute
args array Command arguments
working_dir string Working directory
env object Environment variables
auto_restart boolean Restart process on crash
start_delay integer Delay before starting (seconds)

routes

URL routing and rewriting rules.

routes:
  rewrites:                      # URL rewrite rules
    - pattern: "^/old/(.*)"      # Regex pattern
      replacement: "/new/$1"     # Replacement string
      redirect: true             # HTTP redirect vs rewrite
      status: 301               # HTTP status code

  reverse_proxies:               # Reverse proxy routes
    - name: "api-backend"        # Route name
      path: "^/api/"             # URL pattern (regex)
      target: "https://api.example.com"  # Target URL
      strip_path: true           # Remove prefix before proxying
      headers:                   # Custom request headers
        X-Forwarded-Host: "$host"
      response_headers:          # Custom response headers
        Access-Control-Allow-Origin: "*"

  fly:                           # Fly.io-specific routing
    replay:                      # Fly-Replay routing
      - path: "^/api/"           # URL pattern
        region: "syd"            # Target region
        app: "my-app"            # Target app
        machine: "abc123"        # Target machine ID
        status: 307             # HTTP status
        methods: [GET, POST]    # HTTP methods

rewrites

Field Type Required Description
pattern string Regular expression pattern
replacement string Replacement string
redirect boolean Send HTTP redirect vs internal rewrite
status integer HTTP status code for redirects

reverse_proxies

Reverse proxy routes to external services.

Field Type Default Required Description
name string - Descriptive name for the route
path string - Regular expression pattern (alternative to prefix)
prefix string - Simple prefix match (alternative to path)
target string - Target URL (supports $1, $2 for capture groups)
strip_path boolean false Remove matched prefix before proxying
headers object - Custom headers to add to requests (supports $host, $remote_addr, $scheme)
response_headers object - Custom headers to add to responses from upstream
websocket boolean false Enable WebSocket proxying

Note: Either path (regex) or prefix (simple string) must be specified, but not both.

Capture Group Substitution:

Use regex capture groups in path and reference them in target with $1, $2, etc.

routes:
  reverse_proxies:
    # Single capture group
    - name: user-api
      path: "^/users/([0-9]+)$"
      target: "https://api.example.com/v1/user/$1"

    # Multiple capture groups
    - name: archive-proxy
      path: "^/archive/([0-9]{4})/([0-9]{2})/(.*)$"
      target: "https://archive.example.com/$1-$2/$3"

Request /users/123 → Proxies to https://api.example.com/v1/user/123

routes.fly

Fly.io-specific routing configuration.

routes.fly.replay

Fly-Replay routing for region/machine targeting.

Field Type Required Description
path string URL regex pattern
region string Target Fly.io region
app string Target Fly.io app name
machine string Target machine ID (requires app)
status integer HTTP status code
methods array HTTP methods to match

hooks

Lifecycle hooks for server and tenant events.

hooks:
  # Server lifecycle hooks
  server:
    start:                        # Execute when Navigator starts
      - command: /usr/local/bin/init.sh
        args: ["--setup"]
        timeout: 30s
    ready:                        # Execute asynchronously after Navigator starts listening (initial start + config reloads)
      - command: curl
        args: ["-X", "POST", "http://monitoring.example.com/ready"]
        timeout: 5s
    idle:                         # Execute before machine suspend/stop
      - command: /usr/local/bin/backup-to-s3.sh
        timeout: 5m
    resume:                       # Execute after machine resume
      - command: /usr/local/bin/restore-from-s3.sh
        timeout: 2m

  # Default tenant lifecycle hooks (executed for all tenants)
  tenant:
    start:                        # Execute after tenant app starts
      - command: /usr/local/bin/tenant-init.sh
        args: ["${database}"]
        timeout: 30s
    stop:                         # Execute before tenant app stops
      - command: /usr/local/bin/tenant-backup.sh
        args: ["${database}"]
        timeout: 2m

hooks.server

Server-level lifecycle hooks execute at key Navigator events.

Event When Executed Use Cases
start Before Navigator accepts requests Initialize services, run migrations
ready After Navigator starts listening Notify monitoring, warm caches
idle Before machine suspend/stop (Fly.io) Upload data to S3, checkpoint state
resume After machine resume (Fly.io) Download data from S3, reconnect services

hooks.tenant

Default tenant hooks execute for all tenant applications. Can be overridden per-tenant under applications.tenants[].hooks.

Event When Executed Use Cases
start After tenant app starts Initialize tenant data, warm caches
stop Before tenant app stops Backup database, sync to storage

Hook Configuration

Each hook entry supports:

Field Type Required Description
command string Command to execute
args array Command arguments (supports ${var} substitution)
timeout string Max execution time (duration: "30s", "5m")
reload_config string Config file to reload after hook succeeds (server hooks only). Only reloads if path differs OR file modified during execution.

Reload Logic:

The reload_config field triggers smart configuration reload: - Reloads if config file path differs from current config - Reloads if config file was modified during hook execution - Skips reload if nothing changed (improves performance)

See Lifecycle Hooks for details.

Environment: - Server hooks receive Navigator's environment - Tenant hooks receive tenant's full environment (including env and var values)

Execution Order: - Multiple hooks execute sequentially in order - Failed hooks log errors but don't stop Navigator - Tenant stop: default hooks → tenant-specific hooks

logging

Logging configuration for Navigator and managed processes.

logging:
  format: json                    # "text" or "json"
  file: "/var/log/navigator.log" # Optional file output

  # Vector integration (optional)
  vector:
    enabled: true
    socket: "/tmp/vector.sock"
    config: "/etc/vector/vector.toml"
Field Type Default Description
format string "text" Log format: "text" or "json"
file string "" Optional file path for log output (supports {{app}} template)

logging.vector

Professional log aggregation with automatic Vector process management.

Field Type Default Description
enabled boolean false Enable Vector integration
socket string "" Unix socket path for Vector communication
config string "" Path to vector.toml configuration file

Log Format: All process output (Navigator, web apps, managed processes) includes: - Text mode: [source.stream] prefix (e.g., [2025/boston.stdout]) - JSON mode: Structured with timestamp, source, stream, message, tenant fields

Environment Variable Substitution

Navigator supports environment variable substitution using ${VAR} syntax:

applications:
  global_env:
    # Simple substitution
    DATABASE_URL: "${DATABASE_URL}"

    # With default value
    REDIS_URL: "${REDIS_URL:-redis://localhost:6379}"

    # Nested in strings
    LOG_LEVEL: "Rails ${RAILS_ENV} logging"

Template Variables

Use template variables for multi-tenant configurations:

applications:
  env:
    DATABASE_NAME: "${database}"
    TENANT_ID: "${tenant_id}"

  tenants:
    - name: customer1
      var:
        database: "app_customer1"
        tenant_id: "cust1"

Validation Rules

Navigator validates configuration on startup:

  1. Required fields: server.listen, applications.tenants[].path
  2. Port ranges: Listen port must be 1-65535
  3. Path format: Tenant paths must start and end with /
  4. File paths: Must be accessible by Navigator process (htpasswd, config files, maintenance page)
  5. Regex patterns: Must compile successfully (routes.rewrites, routes.fly.replay)
  6. Process names: Must be unique within managed_processes
  7. Duration format: Must be valid Go duration (e.g., "30s", "5m", "1h30m")
  8. Hook timeouts: Should be reasonable (<10m for most operations)

Examples

Basic Single App

server:
  listen: 3000

applications:
  tenants:
    - name: app
      path: /
      working_dir: /var/www/app

Multi-Tenant with Auth

server:
  listen: 3000
  static:
    public_dir: /var/www/public
    allowed_extensions: [html, css, js, png, jpg, gif]
    cache_control:
      default: "1h"
      overrides:
        - path: /assets/
          max_age: 24h

auth:
  enabled: true
  realm: "Restricted"
  htpasswd: /etc/navigator/htpasswd
  public_paths: ["/assets/", "*.css", "*.js"]

maintenance:
  page: "/503.html"

applications:
  pools:
    max_size: 10
    timeout: 5m
    start_port: 4000

  env:
    DATABASE_URL: "sqlite3://db/${database}.sqlite3"

  tenants:
    - path: /tenant1/
      var:
        database: tenant1
    - path: /tenant2/
      var:
        database: tenant2

With Background Jobs and Hooks

server:
  listen: 3000
  idle:
    action: suspend
    timeout: 20m

hooks:
  server:
    idle:
      - command: /usr/local/bin/backup-all.sh
        timeout: 5m
  tenant:
    stop:
      - command: /usr/local/bin/backup-tenant.sh
        args: ["${database}"]
        timeout: 2m

managed_processes:
  - name: redis
    command: redis-server
    args: ["/etc/redis/redis.conf"]
    auto_restart: true

  - name: sidekiq
    command: bundle
    args: [exec, sidekiq]
    working_dir: /app
    env:
      REDIS_URL: redis://localhost:6379
    auto_restart: true
    start_delay: 2s

applications:
  pools:
    max_size: 5
    timeout: 10m
    start_port: 4000

  env:
    REDIS_URL: redis://localhost:6379
    DATABASE_URL: "sqlite3://db/${database}.sqlite3"

  tenants:
    - path: /
      var:
        database: production

Complete Fly.io Configuration

server:
  listen: 3000
  static:
    public_dir: /var/www/public
    allowed_extensions: [html, css, js, png, jpg, svg, ico, woff2]
    try_files: [index.html, .html]
    cache_control:
      default: "1h"
      overrides:
        - path: /assets/
          max_age: 7d
  idle:
    action: suspend
    timeout: 20m

auth:
  enabled: true
  realm: "Protected Application"
  htpasswd: /etc/navigator/htpasswd
  public_paths:
    - "/assets/*"
    - "*.css"
    - "*.js"
    - "/favicon.ico"

maintenance:
  page: "/503.html"

routes:
  rewrites:
    - pattern: "^/old-path/(.*)"
      replacement: "/new-path/$1"
      redirect: true
      status: 301

  reverse_proxies:
    - name: api-backend
      path: "^/api/"
      target: "https://api.example.com"
      strip_path: true

  fly:
    replay:
      - path: "^/regions/syd/"
        region: syd
        status: 307

applications:
  pools:
    max_size: 10
    timeout: 10m
    start_port: 4000

  health_check: "/up"
  startup_timeout: "5s"

  env:
    DATABASE_URL: "sqlite3://db/${database}.sqlite3"
    RAILS_ENV: production

  tenants:
    - path: /tenant1/
      var:
        database: tenant1
    - path: /tenant2/
      var:
        database: tenant2

managed_processes:
  - name: redis
    command: redis-server
    args: ["/etc/redis/redis.conf"]
    auto_restart: true

hooks:
  server:
    idle:
      - command: /usr/local/bin/backup-to-s3.sh
        timeout: 5m
    resume:
      - command: /usr/local/bin/restore-from-s3.sh
        timeout: 2m

logging:
  format: json
  file: /var/log/navigator.log

See Also