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:
- Exact match —
app.example.commatcheshostname = "app.example.com" - Wildcard match —
*.example.commatches any subdomain - 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
# Rollback a specific app
bext rollback 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] workersthreads 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
Each app's cache is isolated by namespace, so invalidating one app's cache never affects another. For cache purge mechanics and TTL configuration, see the Caching guide.
Related#
- Configuration reference — full
bext.config.tomlschema including all app fields - Caching — per-app cache namespaces, ISR, and invalidation
- Security & WAF — per-app WAF rules and CORS configuration
- TLS & HTTPS — wildcard certificates and per-app TLS setup
- Deployment Modes — static, SSR, proxy, and PHP app types explained
- Operations — Scaling — worker pool sizing across multiple SSR apps