Hosting.
wasi:http component as a function, or an unmodified server as a machine, served by Tenzro nodes. Static files are content-addressed over iroh and routed by a signed manifest; a function runs in a capability sandbox and answers requests directly; a machine is a resident process in a Firecracker microVM. All three share one naming and ingress layer, so a hostname can point at any of them.- STATUS
- Testnet
- CRATE
- tenzro-node
- STABILITY
- Beta
- TYPE
- Component
Model
A site is a manifest: an owner DID, a route map, an index route, an optional not-found route, and a single-page-app flag. Each route names a content-addressed blob (the file's hash in the node's iroh store) plus its content type and size. Content never touches the filesystem — a request path only ever indexes the manifest's route map.
Serving resolves a request in order: exact route match, then — for a single-page app — the index route at HTTP 200 for a route miss (so the client-side router takes over), then the manifest's not-found route at 404, then a plain 404. Asset misses (a request whose last path segment has a file extension) skip the SPA fallback and 404 directly, so a missing bundle chunk is never masked as the index page.
Deploy
# Build your app, then publish the output directory
tenzro site deploy \
--name my-app \
--owner-did did:tenzro:human:... \
--dir ./dist \
--did-envelope <hex> \
--rpc https://rpc.tenzro.xyz
# Single-page-app fallback is auto-detected; override with --spa / --no-spa.
# The site is served at GET /sites/<site_id>.Naming
Each operator serves sites under its own app domain — there is no network-wide canonical host. Point a hostname (a subdomain of the hosting operator's app domain) at a site so the Web-API edge serves it by Host header. Setting an alias requires control of the owner DID and ownership of the target site; re-pointing an existing hostname requires ownership of the existing alias too.
tenzro site set-alias \
--hostname my-app.<operator-app-domain> \
--site-id <site_id> \
--owner-did did:tenzro:human:... \
--did-envelope <hex>
tenzro site list
tenzro site get --site-id <site_id>
tenzro site list-aliases
tenzro site remove --site-id <site_id> --owner-did ... --did-envelope <hex>Placement
A site's content is content-addressed and node-agnostic, so any node holding the blobs can serve it. A placement records which serving nodes a site runs on, each named by its iroh endpoint id. When the edge resolves a request to a site placed on remote nodes, it forwards the request over the tenzro/http transport — one QUIC stream per request — and streams the response back. An empty placement means the site serves locally on whichever node receives the request.
tenzro site set-placement \
--site-id <site_id> \
--serving-node <endpoint_id> \
--owner-did did:tenzro:human:... \
--did-envelope <hex>
tenzro site get-placement --site-id <site_id>
tenzro site list-placements
# Clear placement to revert to local serving
tenzro site remove-placement --site-id <site_id> --owner-did ... --did-envelope <hex>Custom domains
Attach a domain you control. The hosting operator's node returns the DNS records to publish — a record pointing the hostname at that operator's edge, plus a _tenzro-ownership TXT proof. After the records resolve, verify reads the TXT and admits the domain; TLS is then issued lazily on the first request, with no certificate or web server to configure on your side.
tenzro site domain add \
--hostname app.example.com \
--site-id <site_id> \
--owner-did did:tenzro:human:... \
--did-envelope <hex>
# prints the DNS records reported by the hosting operator's node
tenzro site domain verify \
--hostname app.example.com \
--owner-did did:tenzro:human:... \
--did-envelope <hex>
tenzro site domain get --hostname app.example.com
tenzro site domain list
tenzro site domain remove --hostname app.example.com --owner-did ... --did-envelope <hex>RPC
tenzro_sitePublish # publish a manifest (owner-authenticated)
tenzro_siteGet # read a manifest by id
tenzro_listSites # list sites, optionally by owner DID
tenzro_siteRemove # remove a site (owner-authenticated)
tenzro_siteSetAlias # point a hostname at a site
tenzro_siteGetAlias # read a hostname alias
tenzro_listSiteAliases # list aliases, optionally by owner DID
tenzro_siteRemoveAlias # remove an alias (owner-authenticated)
tenzro_siteSetPlacement # set the serving nodes a site runs on
tenzro_siteGetPlacement # read a site's placement
tenzro_listSitePlacements # list all site placements
tenzro_siteRemovePlacement # clear placement, revert to local serving
tenzro_siteClaimDomain # claim a custom domain; returns DNS records to publish
tenzro_siteVerifyDomain # verify the DNS TXT proof and admit the domain
tenzro_siteGetDomain # read a custom-domain claim
tenzro_listSiteDomains # list custom domains, optionally by owner DID
tenzro_siteRemoveDomain # remove a custom-domain claim (owner-authenticated)
tenzro_functionDeploy # publish a wasi:http component (owner-authenticated)
tenzro_functionGet # read a function deployment by id
tenzro_listFunctions # list functions, optionally by owner DID
tenzro_functionRemove # remove a function (owner-authenticated)
tenzro_machineDeploy # publish a microVM machine (owner-authenticated)
tenzro_machineGet # read a machine deployment by id
tenzro_listMachines # list machines, optionally by owner DID
tenzro_machineRemove # remove a machine (owner-authenticated)
tenzro_machineStatus # report a machine's runtime status
tenzro_machineSealingKey # read the node's X25519 sealing key for env secrets
tenzro_listLeases # list every active hosting lease on this node
tenzro_getLeasesForApp # list the leases placed for one appFunctions
A function is a single wasi:http component — a .wasm file exporting the wasi:http/incoming-handler world, the same shape used by Fastly Compute and Fermyon Spin. It compiles from Rust, TypeScript, Go, Python, or any WASI 0.2 target. The node compiles it once, then invokes it per request inside a capability sandbox with deterministic fuel metering and a wall-clock deadline. It starts with no filesystem, network, or environment access; a JSON capability grant opens named authority explicitly.
A function shares the naming layer with a static site — its id is derived from owner DID and name the same way, and a hostname alias can point at either. Each invocation is request-scoped and stateless: state that must survive across requests belongs in a granted storage mount, an external service reached through a network capability, or a companion long-lived component. A workload that needs a resident process is a machine app, not a function.
# Upload a wasi:http component and publish a deployment
tenzro function deploy \
--name my-fn \
--owner-did did:tenzro:human:... \
--wasm ./target/wasm32-wasip2/release/my_fn.wasm \
--capabilities ./caps.json \
--did-envelope <hex> \
--rpc https://rpc.tenzro.xyz
tenzro function get --id <id>
tenzro function list
tenzro function remove --id <id> --owner-did ... --did-envelope <hex>
# Point a hostname at it — the alias id argument accepts a function id
tenzro site set-alias --hostname my-fn.<operator-app-domain> \
--site-id <id> --owner-did did:tenzro:human:... --did-envelope <hex>Machines
A machine is an unmodified long-lived server — a Node, Python, Rust, or Go process that binds a loopback port — run inside a hardware-virtualized Firecracker microVM. Where a function is a per-request sandbox, a machine keeps a resident process: persistent connections, in-memory cache, and background loops survive between requests. The deployment declares the port the guest listens on as internal_port; ingress dials the microVM over plain TCP and replays each request as raw HTTP, so an unmodified server is reachable through the edge with no code changes.
Environment secrets are sealed client-side: the deploy command fetches the assigned node's X25519 sealing key, envelope-wraps each value to it, and ships only the ciphertext — the plaintext never leaves your host. Serving a machine needs an operator node that runs the microVM supervisor (a Linux host with KVM and nested virtualization); a node without it holds the deployment metadata but answers a request with HTTP 501, and placement filters machine deployments to capable nodes. Its id shares the naming layer with sites and functions, so a hostname alias can point at it.
# --env is a JSON file of secrets, sealed to the node before sending
tenzro machine deploy \
--name my-server \
--owner-did did:tenzro:human:... \
--image ./rootfs.ext4 \
--internal-port 8080 \
--vcpus 2 --mem-mib 1024 --disk-mib 4096 \
--env ./secrets.json \
--did-envelope <hex> \
--rpc https://rpc.tenzro.xyz
tenzro machine get --id <id>
tenzro machine list
tenzro machine status --id <id>
tenzro machine remove --id <id> --owner-did ... --did-envelope <hex>
# Point a hostname at it — the alias id argument accepts a machine id
tenzro site set-alias --hostname my-server.<operator-app-domain> \
--site-id <id> --owner-did did:tenzro:human:... --did-envelope <hex>Placement and economics
Deploying does not pin a workload to the receiving node. That node runs an automatic placement pass over the network's capable nodes and records the result as leases. Each node announces its hosting capability — the runtime classes it serves, its free CPU / RAM / disk, whether it supports a TEE, and the per-hour TNZO price it charges — with a time-to-live; a stale announcement is ignored.
Placement hard-filters candidates to the right runtime class, enough resources, TEE support when required, and a price at or below the deploy's max_price_per_hour, then ranks the survivors — the requested region first, then cheapest — and leases the top N distinct nodes for N replicas. The advertised price is the bid, so placement is deterministic over a candidate snapshot. When no capable remote node exists the app serves locally and no lease is recorded, so a deploy never fails for lack of a target. The chosen nodes are written into the app's ingress placement.
A serving node that stops announcing falls out of the candidate set when its TTL lapses. A reconcile pass sweeps expired leases and re-places any replica whose node has gone silent onto a surviving node, so the replica count holds across a node loss without owner intervention. Removing an app releases every lease it holds.
# Optional placement params on any deploy
tenzro machine deploy ... \
--replicas 3 \
--region-hint us-central \
--max-price-per-hour 500
# Inspect leases
tenzro lease list --rpc https://rpc.tenzro.xyz
tenzro lease get --app-id <app_id> --rpc https://rpc.tenzro.xyzPayment
A manifest, function, or machine deployment may carry a per-request price in TNZO. When set, the node gates serving behind an x402 payment challenge — a caller presents a payment credential, the node verifies and settles it, then serves the content, invokes the function, or bridges to the machine.