Multi-App
Run dozens of applications on a single bext binary using hostname-based routing. Each app gets its own configuration, cache namespace, and deploy lifecycle while sharing the same process, TLS certificates, and port.
Configuration
Define apps in the [apps] table of bext.config.toml:
# bext.config.toml
[server]
listen = "0.0.0.0:443"
[apps.marketing]
hostname = "example.com"
root = "./apps/marketing"
type = "static"
[apps.dashboard]
hostname = "app.example.com"
root = "./apps/dashboard"
type = "ssr"
workers = 4
[apps.api]
hostname = "api.example.com"
root = "./apps/api"
type = "proxy"
upstream = "http://127.0.0.1:8080"
[apps.blog]
hostname = "blog.example.com"
root = "./apps/blog"
type = "ssr"
Routing Behavior
When a request arrives, bext resolves the app by matching the Host header against configured hostnames:
1. Exact match — app.example.com matches hostname = "app.example.com"
2. Wildcard match — *.example.com matches any subdomain
3. Default app — if no hostname matches, the [apps.default] app handles it (or 404)
[apps.catch_all]
hostname = "*.example.com"
root = "./apps/fallback"
type = "static"
App Types
| Type | Behavior |
|---|---|
static |
Serves files from root with compression and caching |
ssr |
Runs the V8 render pool for server-side rendering |
proxy |
Reverse proxies to upstream with health checks and circuit breaking |
spa |
Serves index.html for all routes (single-page app) |
php |
Runs the embedded PHP runtime (Enterprise) |
Per-App Configuration
Each app can override global settings:
[apps.dashboard]
hostname = "app.example.com"
root = "./apps/dashboard"
type = "ssr"
workers = 4
[apps.dashboard.cache]
max_entries = 5000
default_ttl_ms = 60000
[apps.dashboard.tls]
cert = "/etc/certs/app.example.com/fullchain.pem"
key = "/etc/certs/app.example.com/privkey.pem"
[apps.dashboard.headers]
X-Frame-Options = "DENY"
Content-Security-Policy = "default-src 'self'"
Isolated Caching
Each app gets its own cache namespace. Cache tags, invalidation, and ISR operate independently:
# Invalidate only the blog's cache
bext cache purge --app blog --tag "posts"
# View per-app cache stats
bext cache stats
# marketing: 1,234 entries (12 MB)
# dashboard: 567 entries (8 MB)
# blog: 89 entries (2 MB)
Deploys
Deploy apps independently with zero downtime:
# Deploy only the marketing site
bext deploy ./apps/marketing --app marketing
# Deploy all apps
bext deploy --all
# Rollback a specific app
bext rollback --app dashboard
Each app has its own deploy history and rollback stack.
Multi-Tenant Mode
For SaaS platforms, combine multi-app with tenant isolation:
[apps.saas]
hostname = "*.myplatform.com"
root = "./apps/saas"
type = "ssr"
tenant_resolution = "subdomain" # extract tenant from subdomain
tenant_cache_isolation = true # separate cache per tenant
The {tenant_id} is available in your application via the X-Bext-Tenant-Id header.
Limits
- No hard limit on the number of apps per instance
- Each SSR app consumes [render] workers threads from the pool
- TLS certificates are shared by default (use wildcard certs) or per-app
- The admin dashboard at /__bext/admin/ shows stats for all apps