Phase 11: Zero-Config Defaults

Goal

Make bext run . a production-grade server with zero configuration — smart defaults for everything, auto-detection of the deployment context, and progressive disclosure of config options. Inspired by Caddy's "2-line Caddyfile" and FrankenPHP's "just works" philosophy.

Current State

  • bext run . auto-detects framework, generates config, starts server
  • Reasonable defaults for cache, workers, rate limits
  • But: no TLS by default, no compression auto-tuning, no security defaults
  • Config required for domains, upstreams, WAF rules

Design Philosophy

Progressive disclosure: Works with zero config. First config line unlocks new capabilities. Full config available for power users.

Zero config:      bext run .
                  → auto-detect framework, self-signed TLS on localhost,
                    gzip+br compression, 4 workers, ISR cache, health endpoint

One line config:  domain = "example.com"
                  → auto-HTTPS via Let's Encrypt, H2+H3, Early Hints for
                    detected assets, security headers, OCSP stapling

Full config:      bext.config.toml (all knobs)

Auto-Detection Matrix

Signal Detection Default Behavior
$DOMAIN or SERVER_NAME env var Domain configured Auto-HTTPS (ACME)
No domain configured Development mode Self-signed TLS on localhost:3000
public/ directory exists Static assets present Enable pre-compressed serving
.next/ or dist/ exists Build output detected Set static_dir and bundle_path
Redis URL in env Redis available Enable L2 cache + Pub/Sub
platform.toml exists Multi-app mode Start platform server
CPU count Server capacity Set jsc_workers = min(cpu_count, 8)
Available memory Server capacity Set max_memory_mb per worker = total_ram / (workers * 2)
Port 443 available Can bind HTTPS Bind 443 + 80 redirect
Port 443 unavailable Non-root or conflict Bind 3000, log suggestion to use setcap

Smart Defaults Table

Feature Zero Config Default With Domain Rationale
TLS Self-signed on localhost Auto-HTTPS (Let's Encrypt) Secure by default
HTTP/2 On (when TLS active) On Modern protocol
HTTP/3 Off On (if feature compiled in) Opt-in for binary size
Compression Gzip + Brotli Gzip + Brotli + Zstd All modern browsers
Early Hints Off (no domain = no preloads) Auto-discover from first render 30% LCP improvement
ISR Cache 10K entries, 60s TTL, 60m SWR Same Good general defaults
Stampede Guard On On Always beneficial
Workers min(cpu_count, 8) Same Match hardware
Worker Lifecycle max_requests=50000, max_memory=512MB Same Prevent leaks
Rate Limit 600 req/min per IP Same Basic protection
Security Headers HSTS, X-Frame-Options, X-Content-Type Same + CSP Secure by default
WAF Off Basic rules (SQLi, XSS, path traversal) Protect with domain
Bot Detection Off Challenge mode Protect public sites
Access Log JSON to stdout Same Container-friendly
Metrics On at /metrics Same Always useful
Health On at /health Same LB integration
Dashboard On at /__bext/dev On at /__bext/dashboard (localhost) Monitoring
Real-time Hub On On Turbo.js integration

Environment Variable Overrides

Every config option can be overridden via environment variable with BEXT_ prefix:

# Instead of bext.config.toml
BEXT_DOMAIN=example.com
BEXT_LISTEN=0.0.0.0:443
BEXT_WORKERS=8
BEXT_CACHE_TTL=120000
BEXT_REDIS_URL=redis://localhost:6379
BEXT_LOG_FORMAT=json
BEXT_WAF=on

Precedence: CLI flags > env vars > config file > auto-detected defaults.

One-Line Quick Start Modes

# Development (auto-detect everything)
bext run .

# Production with domain (auto-HTTPS, security, compression)
bext run . --domain example.com

# Multi-app platform
bext serve

# Static site
bext static ./public

# Proxy mode (no rendering, just reverse proxy + cache)
bext proxy --upstream http://localhost:3001 --domain example.com

Implementation

Config Resolution Pipeline

fn resolve_config(
    cli_args: &CliArgs,
    env: &Environment,
    config_file: Option<&BextConfig>,
) -> ResolvedConfig {
    // 1. Start with auto-detected defaults
    let mut config = auto_detect_defaults(env);

    // 2. Merge config file (if exists)
    if let Some(file) = config_file {
        config.merge(file);
    }

    // 3. Override with environment variables
    config.apply_env_overrides(env);

    // 4. Override with CLI flags
    config.apply_cli_overrides(cli_args);

    // 5. Apply smart defaults based on resolved state
    config.apply_smart_defaults();

    config
}

fn auto_detect_defaults(env: &Environment) -> ResolvedConfig {
    let cpu_count = num_cpus::get();
    let total_memory = sys_info::mem_info().unwrap().total;
    let has_domain = env.var("DOMAIN").is_some() || env.var("SERVER_NAME").is_some();
    let has_redis = env.var("REDIS_URL").is_some();
    let port_443_available = check_port_available(443);

    ResolvedConfig {
        workers: cpu_count.min(8),
        max_memory_mb: (total_memory / (cpu_count as u64 * 2 * 1024)) as u32,
        tls: if has_domain { TlsMode::Auto } else { TlsMode::SelfSigned },
        h2: true,
        h3: has_domain,  // Only with real domain
        compression: CompressionConfig::default(),
        early_hints: has_domain,  // Auto-discover with domain
        cache: CacheConfig::default(),
        waf: if has_domain { WafMode::Basic } else { WafMode::Off },
        redis: has_redis,
        listen: if port_443_available && has_domain {
            "0.0.0.0:443".into()
        } else {
            "0.0.0.0:3000".into()
        },
        ..Default::default()
    }
}

Startup Banner

Show resolved configuration on startup:

  ╔══════════════════════════════════════════════════════════╗
  ║                    bext v0.3.0                          ║
  ╠══════════════════════════════════════════════════════════╣
  ║  Framework:  Next.js (app dir)                          ║
  ║  Domain:     example.com                                ║
  ║  Listen:     https://0.0.0.0:443 (H2 + H3)             ║
  ║  TLS:        Let's Encrypt (auto)                       ║
  ║  Workers:    4 (max 50K req, 512MB each)                ║
  ║  Cache:      10K entries, 60s TTL, Redis L2             ║
  ║  Compress:   gzip + br + zstd                           ║
  ║  WAF:        basic (sqli, xss, traversal)               ║
  ║  Hub:        SSE + WS at /__bext/events                 ║
  ║  Dashboard:  https://localhost/__bext/dashboard          ║
  ║  Metrics:    https://localhost/metrics                   ║
  ║  Health:     https://localhost/health                    ║
  ╚══════════════════════════════════════════════════════════╝

  Ready in 1.2s

Dockerfile Template

FROM ghcr.io/bext/bext:latest

COPY . /app
WORKDIR /app

# Build (framework auto-detected)
RUN bext build .

# Run (zero config, DOMAIN from env at runtime)
CMD ["bext", "run", "."]
EXPOSE 443 80

Docker Compose Example

services:
  web:
    image: ghcr.io/bext/bext:latest
    environment:
      - DOMAIN=example.com
      - REDIS_URL=redis://redis:6379
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - ./app:/app
      - certs:/data/certs    # Persist ACME certs

  redis:
    image: redis:7-alpine

volumes:
  certs:

Testing Plan

Test Type What it validates
Auto-detect framework Unit Next.js, Hono, static detected correctly
Auto-detect workers Unit min(cpu, 8) selected
Auto-detect TLS mode Unit Domain → auto, no domain → self-signed
Env var overrides Unit BEXT_WORKERS overrides auto-detected
Config file merge Unit File values override defaults
CLI flag precedence Unit CLI > env > file > default
Smart defaults with domain Unit WAF on, H3 on, Early Hints on
Smart defaults without domain Unit WAF off, self-signed, localhost
Startup banner Unit All resolved values shown
Port fallback Unit 443 unavailable → 3000
Redis auto-enable Unit REDIS_URL present → L2 cache on

Done Criteria

  • bext run . works with zero configuration
  • Auto-detection of framework, CPU, memory, domain, Redis
  • Smart defaults based on deployment context
  • Environment variable overrides with BEXT_ prefix
  • Config file merging with correct precedence
  • CLI flag overrides
  • Startup banner showing resolved configuration
  • --domain shortcut for production mode
  • bext static, bext proxy quick-start modes
  • Dockerfile + Docker Compose examples in docs
  • All tests passing