mothership 0.0.6

Process supervisor with HTTP exposure - wrap, monitor, and expose your fleet
Documentation
mothership-0.0.6 has been yanked.

Mothership

Process supervisor with HTTP exposure. Launches your fleet, routes traffic, runs WASM plugins.

Features

  • Fleet Management: Launch and monitor processes with dependency ordering
  • Named Binds: Multiple listeners (http, https, ws) routed to ships via TCP or Unix sockets
  • 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 Plugins: Request/response processing with sandboxing
  • Prometheus Metrics: Ship status, restarts, request counts at /metrics
  • Tag Filtering: --only web or --except workers for selective launching
  • Base Templates: Reduce config duplication
  • Graceful Shutdown: Dependency-aware termination

Installation

cargo install mothership

Or from source:

git clone https://github.com/seuros/mothership.git
cd mothership
cargo build --release

Quick Start

# Initialize a new manifest
mothership init

# Validate configuration
mothership clearance

# Run the fleet
mothership

# Run with TUI dashboard
mothership --tui

Configuration

Create ship-manifest.toml:

# Global configuration
[mothership]
metrics_port = 9090

# Named binds - external listeners
[mothership.bind]
http = "0.0.0.0:80"
https = "0.0.0.0:443"
ws = "0.0.0.0:8080"

# Web ships
[[fleet.web]]
name = "app"
command = "ruby"
args = ["app.rb"]
bind = "tcp://127.0.0.1:3000"
healthcheck = "/health"
routes = [
  { bind = "http", pattern = "/.*" },
  { bind = "https", pattern = "/.*" },
]

[[fleet.web]]
name = "cable"
command = "anycable-go"
bind = "tcp://127.0.0.1:8081"
routes = [
  { bind = "ws", pattern = "/cable" },
]

# Background workers
[[fleet.workers]]
name = "sidekiq"
command = "bundle"
args = ["exec", "sidekiq"]
critical = false  # Won't kill fleet on crash

# One-shot jobs
[[fleet.jobs]]
name = "migrate"
command = "rails"
args = ["db:migrate"]
oneshot = true

[[fleet.web]]
name = "main-app"
depends_on = ["migrate"]  # Wait for migration

# WASM modules (optional)
[[modules]]
name = "rate_limiter"
wasm = "./modules/rate_limiter.wasm"
routes = ["/api/.*"]
phase = "request"

Ship Configuration

Field Type Default Description
name string required Unique ship identifier
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 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)
comment string - Description (for documentation)

Route Configuration

Routes map mothership binds to ships. Three formats supported:

# Object format (explicit)
routes = [
  { bind = "http", pattern = "/api/.*" },
  { bind = "ws", pattern = "/cable" },
]

# Shorthand format
routes = ["http:/api/.*", "ws:/cable"]

# Legacy format (defaults to "http" bind)
routes = ["/api/.*", "/health"]

Bind Formats

# TCP with explicit prefix
bind = "tcp://127.0.0.1:3000"

# TCP without prefix
bind = "0.0.0.0:8080"

# Port only (defaults to 127.0.0.1)
bind = "3000"

# Unix socket
bind = "unix:///tmp/app.sock"

Unix Socket Example (Puma)

Configure Puma in config/puma.rb:

bind "unix:///tmp/puma.sock"

Then in ship-manifest.toml:

[[fleet.web]]
name = "puma"
command = "bundle"
args = ["exec", "puma"]
bind = "unix:///tmp/puma.sock"
healthcheck = "/up"
routes = ["/.*"]

Health checks and HTTP proxying both work over Unix sockets.

Commands

# Run fleet (default)
mothership
mothership run
mothership run -c /path/to/manifest.toml

# Run with TUI
mothership --tui

# Filter by tags
mothership --only web           # Only ships tagged "web"
mothership --only web,api       # Ships tagged "web" OR "api"
mothership --except workers     # All except "workers"

# Validate manifest
mothership clearance

# Initialize new manifest
mothership init

Base Templates

Reduce duplication with base templates:

[base.ship]
env = { RAILS_ENV = "production" }
critical = true
tags = ["ruby"]

[base.module]
phase = "request"
tags = ["security"]

# Ships inherit from base.ship
[[fleet.web]]
name = "app"
command = "ruby"
tags = ["web"]  # Combined: ["ruby", "web"]
# Inherits: env, critical from base

TUI Dashboard

The TUI shows real-time fleet status:

  • Overview Tab: Ship status, group, PID, health, routes
  • Logs Tab: Per-ship stdout/stderr with scroll
  • Modules Tab: WASM module status

Controls:

  • Tab - Switch tabs
  • ↑/↓ - Navigate ships
  • PgUp/PgDn - Scroll logs
  • q - Quit

WASM Modules

Modules process requests/responses at the proxy layer:

[[modules]]
name = "auth"
wasm = "./modules/auth.wasm"
routes = ["/admin/.*"]
phase = "request"  # or "response"

[[modules]]
name = "cache"
wasm = "./modules/cache.wasm"
routes = ["/api/.*"]
phase = "response"
config = { ttl = "3600" }

Modules can:

  • Block requests (return custom status/body)
  • Modify request path/headers
  • Modify response headers

Environment Variables

Ships inherit all environment variables from the mothership process. Pass secrets at runtime:

MASTER_KEY=secret DATABASE_URL=postgres://... mothership run

Ship-specific env vars override inherited values:

[[fleet.web]]
name = "app"
command = "ruby"
env = { RAILS_ENV = "production" }  # adds to inherited env

Shell expansion is supported:

env = {
  DATABASE_URL = "${DATABASE_URL}",
  REVISION = "${git rev-parse HEAD}",
}

Metrics

Enable Prometheus metrics:

[mothership]
metrics_port = 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