tori.host
v0.9.7
Published
Deploy AI-generated apps to tori.host: npx tori.host
Maintainers
Readme
tori.host
Deploy static sites and container services to tori.host. Nothing to install:
npx tori.hostGuides, the quickstart and the generated CLI and API reference are at docs.tori.host. The command list below is the same reference, rendered for npm.
Signing in
There is nothing to set up first. Run the deploy; if this machine has never signed in, a browser opens, you sign in (or sign up — they are the same click, auth here is a magic link), and the deploy carries on:
npx tori.hostWhat gets saved to ~/.config/tori/config.json is an API token the sign-in
mints, named after this machine — CLI · macbook-vaclav. It shows up in the
Console under Settings → API tokens, and revoking it there stops this machine
deploying, immediately and by itself.
On a machine with no browser
Over SSH, in a container, or anywhere with no DISPLAY, the CLI does not try to
open anything. It prints a short code and a URL instead; open the URL on any
device you are signed in on, type the code, and the terminal carries on:
$ npx tori.host
→ no browser here (this is an SSH session) — sign in from any other device
Open: https://my.tori.host/cli-auth
Code: PQXR-4K7M
⠧ waiting for approval…--no-browser picks that flow deliberately on a machine that does have a
browser. BROWSER=none does the same through the environment.
In CI
There is nobody there to approve anything, so tori deploy does not try: set
TORI_TOKEN to a token minted in the Console (Settings → API tokens), and
TORI_API_URL if you are not pointing at the default.
Commands
This command list is generated from the same metadata as tori --help and the documentation site. It matches [email protected].
| Command | What it does |
| --- | --- |
| tori / tori deploy [dir] | Pack a directory, build a service from source, or run a digest-pinned image, and wait until it is live. |
| tori deploy --service [dir] | Upload the source tree and have Tori build and run it. A Dockerfile is used if there is one; otherwise the language is detected. |
| tori deploy --image <ref> | Deploy a container image the registry already has. <ref> must be digest-pinned. Nothing is built or uploaded. |
| tori deploy --image <ref> --restart | Force a new revision when that image and env are already live. |
| tori deploy --image <ref> --release "<cmd>" | Run a Job with that command before the new revision takes traffic. |
| tori deploy status [id] | Inspect the latest or named deployment |
| tori deploy watch <id> | Resume watching a deployment |
| tori deploy cancel <id> | Cancel a pending service deployment; keep the live version |
| tori deploy build-log [id] | Print the build log of a source deployment |
| tori logs [--follow] | Read retained app stdout and stderr |
| tori env list / set KEY=VALUE / unset KEY | The environment this app's containers are started with |
| tori db create / status / dumps / restore / delete | Manage this service app's Postgres database |
| tori domains list / add <hostname> / remove <hostname> / check <hostname> | Connect your own hostname to an app |
| tori mcp | Serve Tori tools over MCP stdio |
| tori login | Sign in through the browser and save the token |
| tori whoami | Show the logged-in account and plan |
| tori --version / --help | Print the package version or this command list. tori deploy --help and tori login --help are more specific. |
Example:
$ npx tori.host
→ opening your browser to sign in…
✓ signed in as [email protected]
Saved as "CLI · macbook-vaclav" — revoke it any time in the console.
→ created app plucky-wren
⠹ uploading 8.4 MB / 30.0 MB 28% 1.1 MB/s ETA 19s
→ uploaded 30.0 MB in 27s (1.1 MB/s)
✓ live: https://plucky-wren.tori.cloudServices: building from source
An app is either a static site — files packed out of a directory — or a service, a container the zone runs for you. Which one it is is decided when the app is created and cannot be changed afterwards.
A service is built on Tori from the directory you run the CLI in. With a
Dockerfile at the root there is nothing to say: run npx tori.host and the
CLI sees the Dockerfile, creates a service, and uploads the tree. Without one,
say what you mean with --service, and the build detects the language
(Node, Python, Go, PHP, Ruby, Java, Rust, Deno, .NET, Elixir and a few more).
Later deploys from the same directory read tori.json and need neither.
$ npx tori.host deploy --service
→ packing . (Dockerfile)
→ packed 23 files, 41.2 kB
→ uploaded 41.2 kB in 0s (312.0 kB/s)
→ queued for build — position 2 in the build queue
→ building — attempt 1
→ built registry.tori.zone/tenant-a_6d9009a4ef/app@sha256:9f2c…
✓ live: https://plucky-wren.tori.cloudWhat travels is the directory as it is, minus .git, node_modules and
whatever .gitignore matches — every one git itself reads, from the repository
root down, so build output, a .env, a target/ stay home exactly as they stay
out of the repository. Symlinks and file modes
are kept; the build runs the tree you tested. .dockerignore is applied by the
build itself, next to the Dockerfile, not by the upload. The plan caps the
compressed upload and how long a build may run.
The container that results has to meet the same requirements as one you bring
yourself: listen on port 8080 and run as a numeric non-root user. A
Railpack-detected build is arranged that way for you; a Dockerfile has to say
USER 1000 itself.
A build that fails does not touch what is live. The failure message carries the cause and the last lines of the build log; the whole log is one command away:
$ npx tori.host
…
✗ deploy failed: Dockerfile build failed: exit status 1
…
ERROR: process "/bin/sh -c npm ci" did not complete successfully: exit code: 1
Full build log: tori deploy build-log d_7f3a…
$ npx tori.host deploy build-log d_7f3a… | lesstori deploy cancel <id> stops a build that is queued or running; the build VM
is destroyed and the previous version keeps serving.
Services: deploying an image
A service can also run an image you built elsewhere:
$ npx tori.host deploy --image registry.example.com/acme/api@sha256:e3b0c442…
→ deploying registry.example.com/acme/api@sha256:e3b0c442…
⠹ starting… (6s)
✓ live: https://plucky-wren.tori.cloudPass -v or --verbose to also print the created app, deploy ID and recovery
command, server progress diagnostics, and phase timing breakdown.
The container must listen on port 8080 and declare a numeric non-root
USER, for example USER 1000. The upstream knative/helloworld-go image runs
as root and is incompatible. A verified request-echo example is:
TEST_IMAGE='ghcr.io/mendhak/http-https-echo@sha256:1721d9487ee774fd1d37de3014afd9c215df5056c125af941ee17512178129f6'
npx tori.host deploy --image "$TEST_IMAGE"Keep tori.json to retry the same app. This sample returns request data as JSON;
it does not implement the Hello World sample's TARGET variable.
Nothing is built, packed or uploaded on this path: the image is already in a
registry, and all that travels is its name. A service app takes either kind of
deploy, one at a time: a directory with tori.json naming a service is built
from source whenever --image is absent.
The reference must be pinned to a digest — registry/repo@sha256:<64 hex>.
A tag is refused locally, before any request:
$ npx tori.host deploy --image nginx:latest
✗ --image must name an image by digest, and nginx:latest names one by tag
A tag can be moved onto different bytes tomorrow, so it cannot identify a
deploy — nothing was sent to the API. Resolve it once:
docker inspect --format "{{index .RepoDigests 0}}" nginx:latestlive here means the revision the zone started is Ready and serving.
Inspecting and stopping an attempt
The CLI prints the deploy ID and the latest observed reason while waiting. Ctrl+C stops watching; the deploy continues on the server. Resume it or explicitly cancel it:
npx tori.host deploy status
npx tori.host deploy watch d_example
npx tori.host deploy cancel d_exampleCancellation applies only to pending service attempts. It prevents that attempt from going live and allows another deploy; resource cleanup follows in the zone. An existing live version remains available. Repeating cancellation is harmless; a deploy that has already gone live cannot be cancelled. Use the ID printed by your deploy, not the placeholder above. If another attempt blocks a deploy, the CLI prints its ID and the watch/cancel commands.
Application logs
From the directory containing tori.json:
npx tori.host logs --since 1h --limit 500
npx tori.host logs --followOne-shot reads show the latest matching lines in chronological order. --follow
polls every three seconds; Ctrl-C stops it. A 30-second replay window catches
short ingestion delays. Logs replayed after a longer outage require a historical
read. Missing storage and an empty time range produce different messages. Log
availability depends on the zone's logging rollout and configured retention.
MCP server
The published CLI also carries Tori's MCP server. For Claude Code:
claude mcp add tori -- npx -y tori.host mcpOther MCP hosts should launch npx with -y, tori.host, and mcp as its
arguments using the stdio transport. Run npx tori.host login once first, or
set TORI_TOKEN in the MCP host's environment. The server reads the same saved
credential and TORI_API_URL as the CLI.
tori_deploy accepts either an already-built static directory containing
index.html or a digest-pinned service image; building from source is a CLI
path today (issue 315). tori_logs returns a bounded,
chronological page and labels application output as untrusted. Database tools
are not advertised until the database API and credential path exist.
Environment variables
$ npx tori.host env set DATABASE_URL=postgres://…
✓ set DATABASE_URL = pos•••••••• on plucky-wren
Applies on the next deploy — a running revision keeps the environment it started with.
$ npx tori.host env list
DATABASE_URL pos••••••••
PORT ••••Three things worth knowing:
- Only a service has an environment. A static site is files served from
object storage: nothing runs, so nothing reads a variable, and every static
deploy is stored with no environment at all.
env setandenv unsetare refused on a static site rather than accepted and quietly ignored.env liststill works, and says the values are inert — variables set before this check existed are still there. - A change applies on the next deploy, not immediately. Each deploy takes a
snapshot of the environment, so a revision that is already running keeps the
one it started with. Run
tori deployfor the change to take effect. - Values never come back. The API returns them masked and there is no
endpoint that does otherwise, so
env listcan show that a variable is set and not what it is set to.
The listing goes to stdout and the commentary to stderr, so piping it somewhere gets the variables and not the paragraph about them.
Uploads
The server holds the request open for the whole upload and only answers once the
last byte has arrived, so on a slow uplink tori deploy can legitimately run for
several minutes — a 100 MB artifact over 1 Mbit/s is about fourteen. The progress
line is there so that wait is distinguishable from a hang; when stderr is a pipe
(CI), progress is printed as periodic lines instead.
Limits and timeouts, all matched to the server rather than guessed:
| | | | --- | --- | | Largest artifact | 100 MB compressed (refused locally before the upload starts); a source upload is further capped by the plan | | Upload deadline | 20 minutes, the same as the API ingress and the server's build watchdog | | Stall detection | 2 minutes without the socket accepting a byte | | Wait for the build | 20 minutes; timing out here abandons the wait, it does not cancel the deploy |
Environment overrides, mainly for tests: TORI_UPLOAD_TIMEOUT_MS,
TORI_UPLOAD_STALL_MS, TORI_DEPLOY_TIMEOUT_MS, TORI_LOGIN_TIMEOUT_MS.
Known ceiling, server-side: the API today closes any single request after
five minutes (Node's server.requestTimeout default, not overridden), so an
upload slower than roughly 2.8 Mbit/s at the 100 MB cap will be cut off with
HTTP 408 no matter what the client does — issue 48. The CLI reports that as
what it is rather than as a connection error, but it cannot work around it.
Tests
npm test -w packages/cliThe suite drives the real binary as a child process against a mock control plane on a real socket. Two tests reproduce the five-minute ceiling at real speed and take five and a half minutes each, so they are opt-in:
TORI_SLOW_TESTS=1 npm test -w packages/cli