Mothership
Process supervisor with HTTP exposure. Launches your fleet, routes traffic, runs WASM plugins.
Features
- Fleet Management: Launch and monitor ships and bays with dependency ordering
- Uplinks: Pre-flight connectivity checks for external dependencies (databases, caches)
- Bays (Docking Protocol): WebSocket multiplexing over Unix sockets for real-time apps
- Named Binds: Multiple listeners (http, https, ws) routed via TCP or Unix sockets
- Static File Serving: Serve static assets with configurable path prefix
- Response Compression: Automatic gzip/deflate/br compression
- Health Checks: HTTP-based monitoring with circuit breakers
- Crash Loop Protection: Exponential backoff for failing ships
- TUI Dashboard: Real-time fleet status with logs (
--tui) - WASM Payloads: Request/response processing with sandboxing
- Prometheus Metrics: Ship status, restarts, request counts at
/metrics - Tag Filtering:
--only webor--except workersfor selective launching - Base Templates: Reduce config duplication
- Graceful Shutdown: Dependency-aware termination
Installation
Or from source:
Quick Start
# Initialize a new manifest
# Validate configuration
# View routing chart
# Run the fleet
# Run with TUI dashboard
Configuration
Create ship-manifest.toml:
# Global configuration
[]
= 9090
= true
# Static files (served before routing to ships, with fallthrough)
# Longest prefix wins; if file not found, falls through to routes
[[]]
= "./public"
= "/static"
# Named binds - external listeners
[]
= "0.0.0.0:80"
= "0.0.0.0:443"
= "0.0.0.0:8080"
# Web ships
[[]]
= "app"
= "ruby"
= ["app.rb"]
= "tcp://127.0.0.1:3000"
= "/health"
= [
{ = "http", = "/.*" },
{ = "https", = "/.*" },
]
# Background workers
[[]]
= "sidekiq"
= "bundle"
= ["exec", "sidekiq"]
= false
# One-shot jobs
[[]]
= "migrate"
= "rails"
= ["db:migrate"]
= true
# Docked bays (WebSocket multiplexing)
[[]]
= "orbitcast"
= "./orbitcast"
= [{ = "ws", = "/ws" }]
= { = "redis://localhost:6379" }
# WASM payloads (optional)
[[]]
= "rate_limiter"
= "./modules/rate_limiter.wasm"
= ["/api/.*"]
= "request"
Ships vs Bays
Ships are traditional processes that bind to a port (TCP or Unix socket). Mothership proxies HTTP/WebSocket traffic to them.
Bays use the docking protocol for WebSocket multiplexing. Multiple client connections are multiplexed over a single Unix socket. Ideal for:
- Real-time apps (chat, notifications)
- Reducing connection overhead
- Processes that don't need their own HTTP server
Ship Configuration
| Field | Type | Default | Description |
|---|---|---|---|
name |
string | required | Unique identifier (ASCII only, no spaces) |
command |
string | required | Command to execute |
args |
string[] | [] |
Command arguments |
bind |
string | - | Internal bind address (tcp://host:port or unix:///path) |
healthcheck |
string | - | Health check endpoint path |
routes |
array | [] |
HTTP routes (see below) |
depends_on |
string[] | [] |
Ships/bays to wait for before starting |
env |
table | {} |
Environment variables |
critical |
bool | true |
Crash kills entire fleet |
oneshot |
bool | false |
Run once and exit |
tags |
string[] | [] |
Tags for filtering (--only, --except) |
Bay Configuration
| Field | Type | Default | Description |
|---|---|---|---|
name |
string | required | Unique identifier (ASCII only, no spaces) |
command |
string | required | Command to execute |
args |
string[] | [] |
Command arguments |
routes |
array | [] |
WebSocket routes |
depends_on |
string[] | [] |
Ships/bays to wait for |
env |
table | {} |
Environment variables |
config |
table | {} |
Config passed via docking protocol |
critical |
bool | true |
Crash kills entire fleet |
tags |
string[] | [] |
Tags for filtering |
Route Configuration
Routes map mothership binds to ships/bays:
# Object format
= [
{ = "http", = "/api/.*" },
{ = "ws", = "/cable" },
]
# Shorthand format
= ["http:/api/.*", "ws:/cable"]
# With path stripping
= [
{ = "http", = "/api/.*", = "/api" },
]
Bind Formats
# TCP with explicit prefix
= "tcp://127.0.0.1:3000"
# TCP without prefix
= "0.0.0.0:8080"
# Port only (defaults to 127.0.0.1)
= "3000"
# Unix socket
= "unix:///tmp/app.sock"
Docking Protocol
Bays communicate with mothership via Unix sockets using a binary protocol:
| Message | Direction | Purpose |
|---|---|---|
Dock |
Bay → Mothership | Bay ready, sends version |
Moored |
Mothership → Bay | Docking confirmed, sends config |
Boarding |
Mothership → Bay | New WebSocket client connected |
Disembark |
Mothership → Bay | Client disconnected |
Cargo |
Bidirectional | WebSocket data payload |
Environment variables provided to bays:
MS_PID- Mothership process IDMS_SHIP- Bay nameMS_SOCKET_DIR- Socket directoryMS_SOCKET_PATH- Full socket pathMS_BAY_TYPE- Bay type (e.g., "websocket")
Commands
# Run fleet (default)
# Run with TUI
# Filter by tags
# Pre-flight check (validate + verify uplinks)
# View routing chart
# Validate manifest (no network access)
# Initialize new manifest
Base Templates
Reduce duplication with base templates:
[]
= { = "production" }
= true
= ["ruby"]
[]
= true
= ["realtime"]
[]
= "request"
= ["security"]
# Ships inherit from base.ship
[[]]
= "app"
= "ruby"
= ["web"] # Combined: ["ruby", "web"]
# Bays inherit from base.bay
[[]]
= "orbitcast"
= "./orbitcast"
TUI Dashboard
The TUI shows real-time fleet status:
- Overview Tab: Ship/bay status, group, PID, health, routes
- Logs Tab: Per-process stdout/stderr with scroll
- Modules Tab: WASM payload status
Controls:
Tab- Switch tabs↑/↓- Navigate shipsPgUp/PgDn- Scroll logsq- Quit
WASM Payloads
Payloads process requests/responses at the proxy layer:
[[]]
= "auth"
= "./modules/auth.wasm"
= ["/admin/.*"]
= "request"
[[]]
= "cache"
= "./modules/cache.wasm"
= ["/api/.*"]
= "response"
= { = "3600" }
Payloads can:
- Block requests (return custom status/body)
- Modify request path/headers
- Modify response headers
Static Files & Compression
Static File Serving
Multiple static directories can be configured. Longest prefix wins, with implicit fallthrough to routes if file not found.
# Specific assets directory
[[]]
= "./public/assets"
= "/assets"
# Catch-all for other static files (with fallthrough to routes)
[[]]
= "./public"
= "/"
= "http" # Optional: limit to specific bind
Response Compression
[]
= true
Supports gzip, deflate, and brotli based on Accept-Encoding.
Environment Variables
Ships and bays inherit environment variables from mothership:
MASTER_KEY=secret DATABASE_URL=postgres://...
Process-specific env vars override inherited values:
[[]]
= "app"
= "ruby"
= { = "production" }
Uplinks
Verify external dependencies are reachable before launching the fleet. If any uplink fails, mothership aborts startup.
[[]]
= "postgres://localhost:5432/mydb"
= "postgres"
[[]]
= "$DATABASE_URL" # env var expansion
= "primary-db"
= "10s" # default: 5s
[[]]
= "redis://cache.internal:6379"
= "redis"
Pre-flight Command
Validates the manifest and verifies all uplinks are reachable.
Note: preflight requires network access to the configured uplinks. Running it on your local machine may fail if uplinks are only accessible from the deployment environment (e.g., internal databases, VPC-only services). Use clearance for offline manifest validation.
Supported Schemes
| Scheme | Default Port | Check |
|---|---|---|
postgres://, postgresql:// |
5432 | TCP |
mysql:// |
3306 | TCP |
redis:// |
6379 | TCP |
memgraph://, neo4j:// |
7687 | TCP |
http://, https:// |
80/443 | HTTP GET |
tcp:// |
required | TCP |
Deduplication
Uplinks with the same host:port are checked only once:
# These two share the same postgres server - only one TCP check
[[]]
= "postgres://localhost:5432/app_production"
= "primary"
[[]]
= "postgres://localhost:5432/app_replica"
= "replica"
Metrics
Enable Prometheus metrics:
[]
= 9090
Scrape http://127.0.0.1:9090/metrics:
mothership_ship_status{ship="app",group="web"} 1
mothership_ship_healthy{ship="app",group="web"} 1
mothership_ship_restarts_total{ship="app",group="web"} 0
mothership_requests_total{route="/api"} 1234
mothership_fleet_ships_total 3
Also serves /health for liveness probes.
License
MIT