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
-
--domainshortcut for production mode -
bext static,bext proxyquick-start modes - Dockerfile + Docker Compose examples in docs
- All tests passing