Host-Aware Deployment & Execution System. Turn the machine you already own into a real cloud. Declare an app, get a container, a route, and a public link. When one machine isn't enough, add another and the load spreads.
curl -fsSL https://tryhades.com/install.sh | sh # raise the host
hades login # enter
hades init && hades deploy # ship; returns a linkRunning, today, off a laptop: tryhades.com.
Most people don't need the cloud. A MacBook can serve a simple site tens of thousands of times a second. The bottleneck is your home internet, and even that holds thousands of concurrent visitors. You already paid for the computer; it just sits there asleep. Software should be shared, from your own desk, the way the early web worked.
Hosting on a personal device has four real drawbacks. Hades doesn't paper over them. It instruments each until it's a feature:
| Drawback | What hades does |
|---|---|
| Uptime (laptops sleep) | Every downtime window is detected and classified (slept / crashed / rebooted) in an uptime ledger. Your phone gets a priority push when the host returns; a dead-man's switch covers true host-down. hades host uptime prints availability with causes. A caffeinate power assertion keeps the host from idle-sleeping. |
| Battery (24/7 load degrades it) | Five-minute telemetry feeds hades host battery, a degradation diagnosis (capacity trend, cycle burn, high-charge dwell, temperature) including how much of the load was hades' own fault. Apps can opt into power.on_battery = "pause". |
| Memory (scarce, shared with you) | Declared memory is mandatory. Overcommitting deploys are rejected with the full allocation ledger in the error. Hard per-container limits; OOM kills are detected, restarted with backoff, crash-looped after three strikes. Under pressure, low-priority apps pause first, automatically. |
| Load (one contended machine) | Replicas are round-robined by the built-in proxy. Per-app concurrency caps shed excess with 503 + Retry-After. CPU quotas bound contention. Across a fleet, deploys land on the device with the most free memory. |
Everything the host does flows through one event stream. hades events --follow is the live wire.
One script, idempotent, re-run it any time (re-running is also how you update). It checks Docker, cloudflared and Rust, builds the binaries, puts the daemon under launchd, wires a phone notification channel, runs the doctor, and offers to join an existing fleet.
curl -fsSL https://tryhades.com/install.sh | shOnly deploying from this machine, not hosting on it? The CLI alone:
curl -fsSL https://tryhades.com/cli.sh | shOr from a checkout:
cargo build --release --workspace
export PATH="$PWD/target/release:$PATH"
hades host initmacOS-first (launchd, pmset/ioreg probes, Keychain). Linux/systemd is an explicit extension point behind traits.
# Hades.toml
[app]
name = "my-app"
ports = [8000]
replicas = 2
priority = "normal" # critical | normal | low (shedding order)
[app.build] # or: image = "nginx:alpine"
dockerfile = "Dockerfile"
[app.resources]
cpu = 0.5
memory = "256mb" # mandatory: admission control needs a number
[app.health_check]
path = "/"$ hades deploy
my-app deployed
local: http://my-app.localhost:8787
public: https://lazy-otter-4242.trycloudflare.comThe CLI tars the build context, ships it to the daemon, which builds the image, starts replicas with hard limits, health-gates them, swaps routes, and provisions a tunnel. Deploys are idempotent upserts: same name replaces with zero downtime; retries are safe.
Quick-tunnel URLs rotate on restart. For a permanent URL, an operator runs one coordinator for a domain they own, and everyone else just claims a name under it, never touching Cloudflare themselves:
$ hades domain claim coolname --app my-app
⚖ coolname.example.com is yours
https://coolname.example.com # stable, survives restarts & reboots
$ hades domain claim @ --app site # the apex: example.com + wwwThe coordinator (a workspace binary, deployable as a hades app) holds one
Cloudflare API token and creates a tunnel + DNS record per claim, handing the
host a connector token. End users need no account, no login, no DNS knowledge.
If you own a domain and run your own single host, skip the coordinator:
hades host domain apps.example.com puts the whole host on a named tunnel.
# on the hub
$ hades fleet add # prints the join line
# on each new machine (or unattended: HADES_HUB=… HADES_TOKEN=… sh)
$ hades host join --hub https://… --token …$ hades fleet
DEVICE HEALTH FREE ALLOCATABLE APPS LAST SEEN
studio green 11468MB 12700MB 3 14:02:11
macbook green 4121MB 6348MB 1 14:02:13
$ hades deploy # placed on the freest device automatically
$ hades deploy --device local # or pin it
$ hades ssh macbook # shell into a device (auth stays plain SSH)
$ hades fleet update # every device self-updates from the hubThe hub holds each device's control URL + token, polls health, places deploys by free memory, and proxies app commands to wherever an app lives. Devices re-register with their hub whenever their tunnel URL rotates (self-heal). Hosts serve their own source, so updates propagate machine-to-machine with no registry.
$ hades secrets set STRIPE_KEY=sk_live_… --app shop
$ hades secrets list --app shop # key names only, values never leave the hostPer-app, injected as env at container start, never in the manifest, git, or the build context, and never returned by the API. At rest: XChaCha20-Poly1305 with a per-host master key in the macOS Keychain. Setting a secret rolls the running replicas in place.
The CLI is the entire interface: no MCP server, no SDK, no dashboard. Every
command takes --json (one JSON object on stdout, progress on stderr; NDJSON
for streams) with stable exit codes:
hades deploy --json | jq -r .app.url| exit | meaning |
|---|---|
| 0 | ok |
| 2 | host not ready (doctor red) |
| 3 | deploy rejected (overcommit); the error's detail carries the ledger |
| 4 | not found · 5 daemon unreachable · 6 invalid spec · 7 unauthorized |
A Cargo workspace, Rust + tokio:
crates/
├── hades-core shared types: AppSpec, manifest, events, errors, config
├── hades-api the CLI⇄daemon wire contract + client
├── hades-host bootstrap, doctor, launchd, macOS probes (pmset/ioreg)
├── hades-sentinel uptime ledger, notifiers, dead-man's switch
├── hades-runtime Docker via bollard: build-from-tar, limits, OOM detection
├── hades-proxy Host-header reverse proxy: aliases, replicas, shed caps
├── hades-tunnel cloudflared: quick / named / ssh tunnels
├── hadesd the daemon: API, reconcile, watchdog, policy engine
├── hades-cli the `hades` binary
└── hades-coordinator operator subdomain service (Cloudflare-backed claims)
The event bus is the spine: the reconcile loop, watchdog, power monitor and
tunnel supervisor publish HostEvents; notifications, the policy engine, the
JSONL ledger and the /events stream consume them. The policy engine is a pure
function (event, app states) → actions, unit-tested without Docker. Desired
state is a JSON file; on restart the daemon converges reality back to it.
State lives under ~/.hades/: config, desired state, the process registry,
the uptime and event ledgers, battery and resource telemetry, encrypted
secrets, and logs.
Full reference: tryhades.com/docs.html.
macOS-first, single-owner, working end to end: real containers, real routes,
real public links over Cloudflare, real fleet placement and stable custom
domains (all running live on tryhades.com). Named extension points, not yet
built: multi-machine request-level load balancing, Linux/systemd, auto-failover
of apps off a dead device, a SQLite store.
MIT.