Deploy a static site
A static app is files. The CLI packs a directory, uploads the archive, and waits until https://<slug>.tori.cloud serves those files. Nothing runs in a container, so environment variables do not apply. The CLI refuses tori env set on a static site; see Environment variables.
An app is static or a service for its whole life. A static site cannot later become a service. Create a new app with --service or --image in a directory that has no tori.json if you need a container — see Deploy a container service.
Directory deploys
Section titled “Directory deploys”From the project root:
npx tori.hostor, with an explicit directory:
npx tori.host deploy ./distDetection, when you omit the directory, is documented in the quickstart. Flags live on tori deploy.
A first deploy from a directory that looks like a server — a server framework in package.json, a Django project, a Go module and the like — makes a service, not a site; see what decides that. When that is wrong, --static publishes the directory as files:
npx tori.host deploy --static--static publishes everything in the directory it settles on except node_modules, .git and tori.json. Point it at the output directory, not at a project root that holds source or a .env. The CLI does not fall back to publishing such a root on its own: when the only index.html sits beside server code — a Dockerfile or one of the signs above — the deploy stops, also for an app that is already a static site. Name the directory, npx tori.host deploy --static ., to publish it anyway.
The first deploy writes tori.json with the app slug. Later deploys from that directory update the same app. Put tori.json in git; do not hand-edit the slug to a name you do not own.
Sign-in on the deploy path
Section titled “Sign-in on the deploy path”If the machine has no saved token, deploy opens a browser (or prints a device code). In CI set TORI_TOKEN, ideally to a deploy token for this app; see Deploy from CI. Revoke a token in the console under Settings → API tokens. See Sign in.
Uploads
Section titled “Uploads”The upload can take minutes on a slow link. The progress line on stderr is how you tell a wait from a hang; when stderr is a pipe (CI), progress is periodic lines instead.
Archives over the CLI’s local size cap are refused before the request starts. The server also enforces the plan’s artefact limit, a per-file size cap, and a limit on how many members the archive may contain. A single file that is too large fails after upload with a reason such as artifact contains a file larger than 64 MB. Unsafe paths (.., absolute names) are refused the same way.
A very slow upload can be closed by the API with HTTP 408 before the last byte arrives. That is a server request deadline, not a broken connection. Retry from a faster link; nothing was deployed.
When it is live
Section titled “When it is live”live means the files of this deploy are the ones the URL serves. Ctrl+C stops watching; it does not cancel an upload that already finished. There is no separate “promote” step.
If another deploy of the same app is already in progress, the server refuses the new upload with HTTP 409 and nothing is sent. Wait, or inspect it with npx tori.host deploy status.
In the console, open the app → deploys for the same history. Rollback is offered there for a previous static deploy whose files have not been reclaimed yet; how many previous deploys keep their files depends on the plan (Deploys). A static deploy made before the platform recorded a checksum for each upload cannot be rolled back even if its files remain: no zone can serve it, so the console marks it predates checksums instead of offering rollback, and the API refuses the rollback.
Custom hostnames
Section titled “Custom hostnames”An app can use a hostname you already own if the plan includes custom hostnames. The console hides Add domain when the plan has none; the API refuses with a message that names the plan. tori whoami and Settings show the plan.
When the plan allows it:
- In the console, open the app → domains, or run
npx tori.host domains add www.example.comfrom the directory that holdstori.json. - Create the DNS record the command prints — a CNAME to the app’s
*.tori.cloudname for a subdomain, or an A record to the zone’s addresses for a zone apex. The apex is the registrable domain by the Public Suffix List —example.com, and alsoshop.co.uk— so the printed record is the one DNS lets you create; a public suffix itself (co.uk) is refused. If the hostname is a delegated zone of its own (it has NS records, so a CNAME is not allowed there), create an A record to the zone’s addresses instead; the check accepts either. With an A record, also create the TXT record it prints (_tori-verify.<hostname>with the valuetori-verify=<token>): the zone’s addresses are shared by every app, so the TXT is what proves the name is yours. A CNAME needs no TXT. npx tori.host domains check www.example.comre-runs the check (at most once per 30 seconds). A certificate is issued only after the record points here.
See tori domains for flags. Do not point a hostname at Tori until the add command has reserved it — the printed record is the one to create.
One canonical hostname
Section titled “One canonical hostname”Preview. The redirect is built and tested against the zone’s configuration; its first live verification on api.tori.host is the rollout of this release, and this note goes when it has been seen answering 301.
With several hostnames on an app — example.com, www.example.com and the app’s own <slug>.tori.cloud name — you can mark one as primary and have the others answer 301 to it, with the path and query kept:
npx tori.host domains primary my-app example.com # only an active (serving over TLS) hostnamenpx tori.host domains redirect my-app on # off by defaultThe redirect is served over TLS on every hostname, so https://www.example.com/a?b=1 answers 301 Location: https://example.com/a?b=1 with a valid certificate. The zone sets it up within a few seconds of the switch; npx tori.host domains list my-app shows redirect: on on the primary once the zone has confirmed it, waiting for the zone until then (from the moment the switch is on), and refused by the zone (…) with the reason if it could not — the CLI does not claim the 301 is live until the zone has said so, and the console shows the same beside the switch. While the primary is not active — withdrawn after DNS drift, or its certificate expired without a renewal (a failed renewal keeps it active, and the redirect on, until the certificate runs out) — the zone withdraws the 301s within a few seconds and every hostname serves the app again; the console says so, and the switch stays as you set it. In the console, the app → domains tab has “make primary” on active rows and the switch above the list.
export TORI_TOKEN=torid_… # Settings → API tokens → Deploy one appnpx tori.host deploy ./distProgress becomes periodic lines because stderr is not a terminal. The deploy still waits until the URL is live. Deploy from CI has complete GitHub Actions and GitLab CI workflows.
See tori deploy for flags and the API for the HTTP side of the same flow.
Download this page as Markdown.