# Toft documentation > 139 pages, for the newest published version. Each page starts at a level-one heading; the pages are separated by a horizontal rule. --- # What Toft is > Give Toft a repository and it builds, runs and serves your app at a URL. No Dockerfile, no web server config. Source: /toft-docs/latest/user/start/what-toft-is/ Toft runs your organization's own software — web apps, APIs, dashboards, reports and scheduled work. You give it code; it detects the framework, installs dependencies, builds, starts the app and serves it at a URL. You do not write a Dockerfile, pick a base image or configure a web server. ## What you get | | | |---|---| | **A URL** | `/apps/your-app`, on the address your administrator set up. | | **Logs** | A build log per deploy and a live run log, so a failure tells you why. | | **Access control** | Restricted to people you choose, anyone signed in, or public — plus a way for colleagues to ask for access. | | **Configuration** | Environment variables, stored encrypted, read at startup. | | **Deployments** | Every successful deploy is kept, so you can roll back. | | **Scheduled work** | Jobs that run your code on a schedule or on demand, in their own process. | ## What it is not - **Not a general container platform.** It runs the frameworks listed in [How Toft reads your repository](/toft-docs/latest/user/build/how-detection-works.md). A repository it does not recognize is refused with a reason. - **Not a database.** An app gets a small [JSON document](/toft-docs/latest/user/data/app-data.md) and, where configured, [file storage](/toft-docs/latest/user/data/app-storage.md). Anything more is a database your administrator provides. - **Not CI.** It builds what you give it. It does not run your tests or gate merges. ## Two addresses The platform (where you sign in and manage apps) and your apps are served on different addresses, so an app's JavaScript cannot act as you on the platform. That is why your app lives under `/apps/your-app` and its links must keep that prefix. It is the most common cause of a deploy that succeeds and a page that looks broken — see [Base paths](/toft-docs/latest/user/build/base-paths.md). ## Three words **Deploy** — one attempt to turn source into a running app. Each is listed as a **deployment**, with its own build log, and either succeeds or fails. **Deployment** — what a deploy leaves. A successful one is the built app: one is *serving*; the others are what you roll back to. **Workload** — the running process that answers requests. It can be restarted, slept and woken without the deployment changing. > **Other guides** > > Running the install itself is the **admin guide**; `toft` and the app SDKs are > the **CLI & SDK guide**. Both are linked from **Documentation** in Toft. --- # Signing in > How you get an account, what your role lets you do, and what to do when you cannot get in. Source: /toft-docs/latest/user/start/signing-in/ Sign in with the button on the Toft sign-in page. Your administrator chooses the provider — usually your organization's single sign-on (OIDC) or GitHub. There is no self-service sign-up. The username-and-password form, if shown, is for the built-in administrator account only. ## Your role Every account has one system role. It decides whether you can create apps. | Role | Can | |---|---| | **Viewer** | Open apps shared with them. | | **Publisher** | Everything a viewer can, plus create, deploy and own apps. | | **Administrator** | Run the install. | No **New app** button means you are a viewer. [Ask to become a publisher](/toft-docs/latest/user/account/becoming-a-publisher.md). ## Waiting for activation Some installs create your account on first sign-in and hold it until an administrator activates it. Until then, every sign-in shows a message your administrator wrote. Retrying will not help — wait, or ask them. ## Sessions A session ends after a period of inactivity, or after a maximum lifetime, whichever comes first. Your administrator sets both. Sign in again to continue. ## When you cannot get in There is no password reset: you sign in with your organization's account. A disabled account, a provider that was switched off, or a pending activation is for your administrator to fix. Once you are in, **Your account → Security** shows your sign-in history — useful for checking whether an attempt reached Toft at all. --- # Deploy your first app > From a repository URL to a running app you can share, in about ten minutes. Source: /toft-docs/latest/user/start/first-app/ The shortest path from a git repository to a URL. You need the [publisher role](/toft-docs/latest/user/start/signing-in.md). ## 1. Create the app 1. Select **New app**. 2. **Source** — choose **Git repository** and paste the **Repository URL**. Set **Branch** if it is not `main`. For a private repository, pick a connected host under **Deploy through** or paste an **Access token**. Select **Next**. 3. **Configure** — give it an **App name**. The **Slug** is derived from it and becomes the app's address, `/apps/`. Add any environment variables now (mark secrets sensitive). Select **Next**. 4. **Deploy** — check the summary and select **Deploy now**. > **Monorepo?** > > Put the app's folder in **Folder (optional)**, for example `apps/web`. Only > that folder is built. See [From a git repository](/toft-docs/latest/user/deploy/from-git.md). If the slug is taken, Toft adds a number (`sales-2`). Slugs starting with `_` are reserved and refused. ## 2. Watch the deploy You land on the app's page with a deploy console above the tabs: | Phase | What happens | |---|---| | **Source** | The repository is cloned (**Upload** for an archive). `node_modules`, `.venv` and similar folders are dropped — the build reinstalls them. | | **Scan** | Dependencies are checked against your install's policy. | | **Deploy** | Detect the framework, install, build, start, health-check, go live. | If it fails, the reason is in the build log. See [Watching a deploy](/toft-docs/latest/user/deploy/watching.md). ## 3. Open it Copy the URL from the app's **Overview** tab rather than typing it — apps are served on their own address, which your administrator chooses. A new app is **Restricted**: only you can open it until you [share it](/toft-docs/latest/user/share/visibility.md). ## 4. Add configuration Keep secrets out of the repository. Add environment variables on **Settings → Environment variables**; your app reads them at startup. A change reaches the app at its next start. Until then the app page shows **Restart to apply**. ## If it failed **"Unsupported framework."** Toft did not recognize the repository. The message lists what it supports. Common cases: - **A front-end build with no server** (Vite, Create React App): there is `package.json` but no `start` script. Add a server with a `start` script, or commit the built output and deploy that folder as a [static site](/toft-docs/latest/user/build/static.md). - **Python with no entry point**: Toft looks for `app.py`, `main.py`, `streamlit_app.py`, `server.py` or `wsgi.py`. **It deployed but the page is broken.** Almost always links. Your app is served under `/apps/`, so `/styles.css` asks the platform, not your app. Read [Base paths](/toft-docs/latest/user/build/base-paths.md) once. ## Next - Deploy your working tree from a terminal: [From your machine](/toft-docs/latest/user/deploy/from-your-machine.md) - Rebuild on every push: [Deploying on every push](/toft-docs/latest/user/deploy/auto-deploy.md) - Let colleagues in: [Who can see your app](/toft-docs/latest/user/share/visibility.md) --- # Finding your way around > The app list, an app's tabs, your own pages, and the command palette. Source: /toft-docs/latest/user/start/finding-your-way/ Four places cover almost everything you do in Toft. ## The app list Where you land after signing in. - **Mine** — apps you own, a group you belong to owns, or that were shared with you. - **All apps** — every app on the install whose name you may see. An app you can see but not open is listed so you can [ask the owner for access](/toft-docs/latest/user/share/requesting-access.md). Filter by status, [tag](/toft-docs/latest/user/share/tags.md) and more; search by name. ## An app's page Tabs appear according to your access and what the install has enabled. A tab for something your install does not have is absent, not disabled. | Tab | What it answers | |---|---| | **Overview** | Is it running, its URL, the serving deployment, what needs attention. | | **Logs** | The run log, each deploy's build log, and the app's [events](/toft-docs/latest/user/run/events.md). | | **Deployments** | Every deployment, the one serving, rollback. | | **Usage** | Who opened it and what it consumes. | | **Automation** | The app's jobs: run one now, pause a schedule, read runs. | | **Dependencies** | What it installs and what your install's policy says about it. | | **Integrations** | Where this app may send messages and files. | | **Reach** | Everything the platform has given this app, in one place. | | **Access** | Visibility, who it is shared with, and pending requests. | | **Settings** | Name, source, environment variables, runtime, tags, deletion. | See [The app page](/toft-docs/latest/user/run/the-app-page.md) for detail. ## Your own pages Under **Your account** (in the user menu): | Tab | | |---|---| | **Overview** | Who you are and your role. | | **Access** | Everything you can reach on this install. | | **Storage** | [Storage](/toft-docs/latest/user/data/browsing-a-store.md) you hold a grant on. | | **Credentials** | Your [API keys](/toft-docs/latest/user/cli/api-keys.md). | | **Security** | Sign-in history, and signing out everywhere. | ## Command palette Press `⌘K` (`Ctrl+K` on Windows and Linux) to search pages, apps and settings. Word order does not matter: `idle sleep` and `sleep idle` find the same setting. ## Documentation **Documentation** links to these guides for your install's version — user, admin, and CLI & SDK — and to your organization's knowledge base, if your administrator has chosen one. The knowledge base is also linked in the header. --- # From a git repository > Point an app at a repository, a branch and optionally one folder of it; Toft clones and builds it. Source: /toft-docs/latest/user/deploy/from-git/ The standard way to deploy: the app names a repository and branch, and Toft clones it on every deploy. ## Create the app In the browser, **New app → Git repository**: | Field | | |---|---| | **Repository URL** | Any URL your install's source policy allows. A refused URL is refused at creation. | | **Branch** | Defaults to `main`. | | **Folder (optional)** | Build one folder of the repository. See [Monorepos](#monorepos). | | **Deploy through** | Shown when an administrator has connected a git host. Pick it and no token is needed. | | **Access token (private repos)** | Otherwise, a read token for a private repository. Stored encrypted. | From the terminal: ```bash toft create --repo https://github.com/acme/reports toft create --repo https://github.com/acme/platform --subdir apps/web --branch release ``` `toft create --repo` uses the repository's default branch unless you pass `--branch`. ## Monorepos Set **Folder** to build one app out of a repository that holds several: - Only that folder is deployed — no `.git`, nothing outside it. Detection, `toft.toml` and dependency files are read from the folder. - The slug defaults to the app name, or else the folder's name. - Paths containing `..` or `.git`, and folders that are symlinks, are refused. - `toft status` prints the `folder` an app builds from. > **Auto-deploy in a monorepo** > > A push to *any* folder of the repository rebuilds every auto-deploying app on > that branch, not only the app whose folder changed. ## Redeploy **Redeploy** on the app page builds the branch's current head. From the terminal: ```bash toft redeploy ``` `toft redeploy` uploads nothing. To send your working tree instead, use [`toft deploy`](/toft-docs/latest/user/deploy/from-your-machine.md). ## Change branch, folder or token **Settings → Source & deploys** holds **Tracked branch**, **Folder**, **Auto-deploy on push** and **Git access token**. Changes apply on the next deploy. The app keeps its slug, URL, history, variables and access. Pointing an app at a *different repository* is an administrator action. ## Next - [Deploying on every push](/toft-docs/latest/user/deploy/auto-deploy.md) - [Watching a deploy](/toft-docs/latest/user/deploy/watching.md) --- # From your machine > Deploy your working tree with toft deploy — uncommitted changes included, no git push needed. Source: /toft-docs/latest/user/deploy/from-your-machine/ `toft deploy` sends the folder you are in, exactly as it is on disk — uncommitted edits included, no commit or push required. It is the fast loop while you are iterating. ```bash toft login https://toft.your-company.com # once toft create # first time: create an app from this folder toft deploy # every time after ``` Install `toft` first: [Installing toft](/toft-docs/latest/user/cli/installing.md). The full command reference is in the CLI & SDK guide. ## Which app it deploys to `toft link ` records the app in `toft.toml`, which you commit: ```toml [app] slug = "sales-dashboard" ``` `toft create --link` does the same when it creates the app. `--app ` overrides it for one command. ## What gets sent - **`.gitignore` decides** — every `.gitignore` in the tree, plus `.git/info/exclude`. A `.toftignore` adds more patterns; it never replaces them. `toft` reads the files itself, so a folder that is not a git repository works too. - **Always dropped:** `.git/`, `.hg/`, `.svn/`, `node_modules/`, `venv/`, `.venv/`, `__pycache__/`, `*.pyc`, `.DS_Store`. - **Dropped when the root has a `package.json`:** `dist/`, `build/`, `out/`, `.next/`, `.nuxt/`, `.svelte-kit/` — the build recreates them. A static site whose `[static] root` points into one of these still ships it. The bundle can be at most 100 MB. > **Git-backed apps** > > `toft deploy` to an app created from a repository sends your tree once. It does > not detach the app: the next deploy from the repository replaces this code. ## Watching `toft deploy` streams progress and exits with the deploy's outcome. `--logs` interleaves the build log. If you lose the terminal, rejoin: ```bash toft deploy --watch ``` ## In a pipeline ```bash export TOFT_URL=https://toft.your-company.com export TOFT_TOKEN_FILE=/run/secrets/toft # or TOFT_TOKEN toft deploy --app reports ``` There is no `--token` flag, so secrets stay out of shell history and the process list. Use a [deploy key](/toft-docs/latest/user/cli/api-keys.md) (`toftd_…`) rather than your personal key, so the pipeline survives your leaving. | Exit code | Meaning | |---|---| | `0` | Deployed. | | `1` | The deploy ran and failed — read the build log. Also when another deploy of the app is still queued: wait, then retry. | | `2` | Usage or configuration: no app linked, a bad flag, an unknown deployment id. | | `3` | Not authenticated, or the credential was refused. | | `4` | The install did not answer. | --- # From RStudio > Publish from RStudio or Posit Workbench with the Publish button and rsconnect, as you would to Posit Connect. Source: /toft-docs/latest/user/deploy/from-rstudio/ Toft answers the same calls as Posit Connect, so RStudio's **Publish** button and `rsconnect::deployApp()` work unchanged. Add Toft as a server once, then publish as usual. You do not need `toft`. ## Before you start - You are a [publisher](/toft-docs/latest/user/account/becoming-a-publisher.md). - Personal API keys are switched on for your install — signing RStudio in creates one. - `rsconnect` is version 1.0 or later. ## Add Toft as a server 1. In RStudio, open **Tools → Global Options → Publishing → Connect…** and choose **Posit Connect**. 2. Enter your Toft address, such as `https://toft.your-company.com`, with no path. 3. A browser page titled **Sign in RStudio** opens in Toft. Select **Approve**. 4. RStudio finishes adding the account. > **Approve only your own request** > > Approving creates a personal API key that acts as you. If you did not just > start this sign-in, select **Deny**. From the R console instead: ```r rsconnect::addServer("https://toft.your-company.com", name = "toft") rsconnect::connectUser(server = "toft") ``` Or register an existing [personal key](/toft-docs/latest/user/cli/api-keys.md) and skip the browser: ```r rsconnect::connectApiUser(server = "toft", apiKey = "toftu_…") ``` ## Publish Select **Publish**, or: ```r rsconnect::deployApp(server = "toft") ``` The build log streams into RStudio's **Deploy** pane, and RStudio opens the app in Toft when it finishes. - The first publish **creates an app** named after the folder (or `appName`), at `/apps//`. It starts **Restricted** — [share it](/toft-docs/latest/user/share/visibility.md) from the **Access** tab. - **Packages come from the bundle's `manifest.json`.** No `renv.lock` needed. See [R](/toft-docs/latest/user/build/r.md). - **Publishing again** from the same folder updates the same app. The previous deployment serves until the new one is ready; see [Deployments and rollback](/toft-docs/latest/user/deploy/releases.md). ## Environment variables ```r rsconnect::deployApp(server = "toft", envVars = c("DB_PASSWORD")) ``` Each value is read from your R session and stored as **sensitive** — never shown back. Change it by publishing again or on [Settings](/toft-docs/latest/user/run/settings.md). ## Differences from Posit Connect - Sharing and visibility are managed in Toft, on the **Access** tab. - There are no custom URLs; the address is the app's name. - A name already in use is refused, not renamed. Choose another `appName`. - [Scheduled re-renders](/toft-docs/latest/user/build/reports.md) are set up in Toft. ## Signing RStudio out The key appears under **Your account → Credentials** as **RStudio (rsconnect)**. **Revoke** it to sign RStudio out. It does not expire unless your administrator has set a limit on how long keys may last; if it does expire, remove the account in RStudio and add it again. Posit Workbench works the same way; the approval page opens in the browser you use Workbench from. ## Troubleshooting | Message | Fix | |---|---| | Adding the server fails before a browser opens | Personal API keys are off. Ask your administrator. | | The approval page never appeared | Open the address RStudio prints in its console. | | `unknown token — add the account again` | The key was revoked, expired or denied. Remove the account and add it again. | | `the request's Date is … more than five minutes from this server's clock` | Fix the clock on the machine running RStudio. | | `the name "…" is already in use` | Publish with a different `appName`. | | `this key is read-scoped` | Register a full-scope key, or use the browser sign-in. | | The deploy fails at `library()` | The package was not recorded in the bundle. Load it at the top of your app and publish again. | --- # From an archive > Upload a .zip or .tar.gz to create an app, or to ship a new version of one. Source: /toft-docs/latest/user/deploy/from-an-archive/ Upload an archive when the code is not in git — a build produced elsewhere, a one-off, or an air-gapped transfer. For anything you iterate on, [`toft deploy`](/toft-docs/latest/user/deploy/from-your-machine.md) sends the same bytes without making an archive. ## Upload - **New app → Upload an archive** creates an app from a `.zip`, `.tar.gz` or `.tgz`. - **Deploy → Upload new version** on the app page ships a new archive to an existing uploaded app. If **Upload an archive** is missing, your administrator has switched uploads off. | Limit | | |---|---| | Archive size | 100 MB | | Unpacked size | 500 MB, 20,000 files | ## What Toft does to it - **Unwraps** a single top-level folder, so `myapp-main/` does not become the app's root. - **Removes** `node_modules/`, `venv/`, `.venv/`, `__pycache__/` and VCS folders — the build reinstalls dependencies. - **Drops** unsafe entries: absolute paths, paths escaping the root, symlinks pointing outside. What was removed is reported on the deploy. If a file is missing, look there first. ## An uploaded app has no repository It has no branch, folder or auto-deploy, and `toft redeploy` refuses it — use `toft deploy` or **Deploy → Upload new version**. An uploaded app cannot be converted into a git app; create a new app from the repository instead. --- # From the gallery > Deploy a working example in any supported framework, then make it your own. Source: /toft-docs/latest/user/deploy/from-the-gallery/ The **Gallery** lists apps the Toft team publishes. Deploy one to see what a working app in your framework looks like, then read and change its source. If the Gallery page says it is switched off, your administrator has not enabled it. ## Two kinds of item | | What it is | Who can deploy it | |---|---|---| | **Example** | A working app in one framework. Yours to change. | Anyone who can create apps | | **Extension** | A tool that adds a capability to the install. | Administrators only | Some installs list extensions only. Gallery items require Toft 0.7.0 or later. ## Deploy an example 1. Open **Gallery** and select **Deploy** on an item. 2. Set a **Name** and **Slug**, and fill in any variables it asks for. 3. Select **Deploy**. The result is an ordinary app: your slug, your settings, your sharing. Edit it, redeploy it, delete it. **Deploy another copy** makes a second one. An example has no update channel — once deployed, it does not change unless you change it. **Settings → Unlink from the gallery** removes the record of where it came from; nothing else changes. ## What an item cannot do A gallery item names code and may ask for environment variables. It cannot grant itself a permission, change a setting, or reach anything you could not. --- # Watching a deploy > The deploy console, its three phases, the build log, and what each failure reason tells you to do. Source: /toft-docs/latest/user/deploy/watching/ While a deploy runs, a console sits above the app's tabs: the current step, a phase bar and the live build log. Leave and come back — it is still there. ## The three phases | Phase | Steps | |---|---| | **Source** (**Upload** for an archive) | Receive or clone the code; drop dependency folders. | | **Scan** | Check dependencies against your install's [dependency policy](/toft-docs/latest/user/run/dependencies.md). | | **Deploy** | Detect, configure, build, package, start, health-check, go live. | The serving deployment keeps serving throughout. A deploy that fails, times out or is refused changes nothing for your users. ## The build log - Opened mid-build, the pane shows lines from when you arrived and says so. The full log is stored and offered when the build ends. - Afterwards, the **Logs** tab has **Build log** (one deploy, with its own link to share) and **Run log** (what the app prints). It opens on the build log while a deploy runs or after one fails. ## Failure reasons A failed deploy carries a reason and a sentence, on the app page and in the terminal. | Reason | What to do | |---|---| | `setup_failed` | The deploy could not find anywhere to run. Ask an administrator. | | `source_unavailable` | The code could not be fetched. Check the URL, branch and token. | | `source_blocked` | Your install's source policy refused it. Ask an administrator — editing code will not help. | | `dependency_blocked` | Packages were refused. The [Dependencies tab](/toft-docs/latest/user/run/dependencies.md) names them and the fixed versions. | | `unsupported_framework` | Toft does not recognize the repository. See [How Toft reads your repository](/toft-docs/latest/user/build/how-detection-works.md). | | `manifest_invalid` | [`toft.toml`](/toft-docs/latest/user/build/toft-toml.md) was refused. | | `runtime_version` | A pinned [language version](/toft-docs/latest/user/build/versions.md) this install does not have. | | `build_failed` | Your build or dependency install failed. **Read the build log.** | | `publish_failed` | The built app could not be stored. Retry. | | `start_failed` | The app did not start. | | `not_ready` | It started but never answered, or crashed while starting. **Read the run log**, not the build log. A crash fails the deploy within seconds, and the message includes your app's last 20 log lines. | | `internal_error` | A platform fault. Retry, then tell your administrator. | Warnings — for example a `toft.toml` section that was ignored — are shown alongside, and included in `--json` output. ## From the terminal ```bash toft deploy --watch # rejoin a running deploy toft logs --deploy # print one build's log ``` Rejoining a deploy that already failed still prints the reason, so `--watch` is safe in scripts. --- # Deploying on every push > Turn on auto-deploy and Toft rebuilds your app when its branch gets a new commit. Source: /toft-docs/latest/user/deploy/auto-deploy/ Turn on **Settings → Source & deploys → Auto-deploy on push**. New commits on the tracked branch deploy within about a minute. Only apps built from a git repository can auto-deploy. Apps from the gallery cannot while they are linked to it. ## How it notices Toft checks each auto-deploying app's branch on an interval your administrator sets. If your administrator has connected your git host with a webhook, a push triggers that check immediately. Nothing to configure per app. ## What it deploys The head of the tracked branch (and the app's folder, if set) — the same as selecting **Redeploy**. It goes through the same source rules and [dependency policy](/toft-docs/latest/user/run/dependencies.md) as any deploy. > **Monorepos** > > A push to any folder of the repository rebuilds every auto-deploying app on > that branch, not only the app whose folder changed. ## When a deploy fails - The serving deployment stays where it was. The failure appears in the app's [events](/toft-docs/latest/user/run/events.md). - Toft does **not** retry the same commit. Push a fix, or select **Redeploy**. - A [locked](/toft-docs/latest/user/run/locked.md) app refuses auto-deploys like any other deploy. ## When to use CI instead Auto-deploy does not run tests. If a push should pass checks first, have your CI run `toft deploy` or `toft redeploy` at the end, with a [deploy key](/toft-docs/latest/user/cli/api-keys.md). --- # Deployments and rollback > Every successful deploy is kept as a deployment. Roll back in seconds, download any deployment, and control how many are kept. Source: /toft-docs/latest/user/deploy/releases/ Every successful deploy leaves a **deployment** — the built app, stored by Toft. One is serving; the others are what you roll back to. The **Deployments** tab lists them, newest first. ## Roll back On **Deployments**, select **Make current** on a deployment. Nothing is rebuilt: the deployment starts, passes a health check, and traffic moves to it. ```bash toft rollback # the successful deployment before the serving one toft rollback # a specific one ``` `toft rollback` names the deployment before acting and asks for confirmation. Run it twice and it goes back twice; it never moves forward on its own. If there is nothing older, it says so and names the newer deployment, which you can switch to with `toft rollback `. In CI, pass `--yes` — without it, a non-interactive run refuses rather than hanging. ## What rollback does not undo Rollback changes the code only. It does not revert: - environment variables, - database changes or anything else your app wrote, - [app data](/toft-docs/latest/user/data/app-data.md) or [storage](/toft-docs/latest/user/data/app-storage.md). ## Restart is not redeploy A restart starts the same deployment again — no rebuild, no new dependency check. An app that has served for a month is not blocked from restarting by an advisory published overnight. Each start restores the deployment from Toft's stored copy. Editing files in a running app's directory does not survive a restart. ## How many are kept On the **Settings** tab, **Delete old deployments automatically** keeps the newest deployments up to **Deployments to keep** (the serving one included), within your install's limit. Turned off, nothing is deleted, and deploys are refused once the app reaches the limit. Only successful deployments count. A failed deploy stays in the list with its build log, so you can read what went wrong, but its files are removed when it fails — it can't be rolled back to, and it never pushes one you could roll back to out of the count. To clear space yourself, delete one deployment, or select **Delete old deployments** to remove everything but the serving deployment. A pending rollback's target is never deleted. ## Download a deployment **Download** on a deployment gets its source as a ZIP — the exact answer to "what is running right now". --- # How Toft reads your repository > The ordered checks that pick your app's framework, and what to do when the pick is wrong. Source: /toft-docs/latest/user/build/how-detection-works/ You don't declare a framework. Toft inspects the repository root — or the **Folder** you set for a monorepo — and the first check that matches wins. ## The order | # | Check | Framework | |---|---|---| | 1 | `[batch]` in `toft.toml` | A [batch app](/toft-docs/latest/user/automation/jobs.md): jobs only, no server | | 2 | `[static]` in `toft.toml` | [Static site](/toft-docs/latest/user/build/static.md), overriding everything below | | 3 | `next.config.js`/`.mjs`/`.ts`, or `next` in `package.json` | [Next.js](/toft-docs/latest/user/build/node.md) | | 4 | `package.json` with a `start` script | [Node](/toft-docs/latest/user/build/node.md) | | 5 | `go.mod` plus a `main` package | [Go](/toft-docs/latest/user/build/go.md) | | 6 | One `.Rmd` or R-engine `.qmd` at the root | [R document](/toft-docs/latest/user/build/notebooks.md) | | 7 | One `.ipynb` or Python `.qmd` at the root, or one marimo `.py` | [Notebook](/toft-docs/latest/user/build/notebooks.md) or [marimo](/toft-docs/latest/user/build/python.md) | | 8 | `app.R`, `ui.R` + `server.R`, `plumber.R`, or an annotated `.R` | [Shiny or plumber](/toft-docs/latest/user/build/r.md) | | 9 | `requirements.txt`, `pyproject.toml` or `Pipfile` | [Python](/toft-docs/latest/user/build/python.md) | | 10 | A folder of HTML with none of the files above | [Static site](/toft-docs/latest/user/build/static.md) | - **R wins over Python.** An `app.R` beside a `requirements.txt` is an R app. - **A notebook wins over a Python server.** A Flask repo with an `.ipynb` at its root deploys as a notebook. Move the notebook into a subfolder. ## Several candidates are refused When one shape has more than one candidate — two notebooks, two `.Rmd` files, several HTML files and no `index.html` — the deploy fails and lists them. Name the one you mean in [`toft.toml`](/toft-docs/latest/user/build/toft-toml.md). ## When it picks wrong | Symptom | Fix | |---|---| | "Unsupported framework" on a front-end repo | There's no `start` script. Commit the build output and declare [`[static] root`](/toft-docs/latest/user/build/static.md). | | Python repo refused with no entry point | Add one of `app.py`, `main.py`, `streamlit_app.py`, `server.py`, `wsgi.py` at the root. | | Detected as a notebook, you wanted a server | Move the notebook out of the root. If viewers need to change inputs, use marimo, Streamlit or Shiny. | | A `.qmd` detected as Python, you wanted R | Set its engine. See [Notebooks](/toft-docs/latest/user/build/notebooks.md). | > **Tip** > > Every build log states the framework it detected and the language version it > resolved. Read that line first, and again to confirm a fix took effect. --- # Node and Next.js > How Node and Next.js apps are detected, built and started. Source: /toft-docs/latest/user/build/node/ | Framework | Detected by | Started with | |---|---|---| | **Next.js** | `next.config.js`/`.mjs`/`.ts`, or `next` in `package.json` | `next start` | | **Node** | `package.json` with a `start` script | `npm run start` | If `package.json` has a `build` script, it runs during the deploy. ## The contract Listen on `PORT`, bound to `HOST`. Toft sets both. ```js app.listen(process.env.PORT, process.env.HOST); ``` Pass `HOST`. `app.listen(port)` alone binds every interface, where on an install that runs apps on its own host the app can be reached without going through Toft's sign-in. The build warns about it on every Node deploy. Your app is served under `/apps/`. For a plain Node app the proxy strips that prefix, so routes stay at `/`, but URLs you send to the browser must carry it. `BASE_PATH` holds it. See [Base paths](/toft-docs/latest/user/build/base-paths.md). **Next.js needs nothing.** Toft sets `basePath` in your Next config before the build, merged over whatever your config exports. ## Dependencies | Repository has | Toft runs | |---|---| | `package-lock.json` | `npm ci` | | no `package-lock.json` | `npm install` | Only `package-lock.json` is honored. With a `pnpm-lock.yaml` or `yarn.lock`, versions resolve from `package.json`, and the build log names the lockfile it passed over. > **Caution** > > Dependency lifecycle scripts (`postinstall` and similar) don't run unless your > administrator has enabled them for the whole install. Move that work into your > `build` script. ## A front end with no server A Vite, Create React App or Astro project with no `start` script has nothing to run. Commit the build output and serve it as a [static site](/toft-docs/latest/user/build/static.md), or add a server and a `start` script. ## Version ```toml # toft.toml [runtime] node = ">=22" ``` Node is matched on the major version. See [Language versions](/toft-docs/latest/user/build/versions.md). `engines.node` and `.nvmrc` are not read; a tree that pins only there gets a `node_version_unread` warning. ## What doesn't work - Dev servers as `start`: `next dev`, `vite`, `nodemon`. - Writing into your app's directory and expecting it to persist. A restart replaces the tree; use [app storage](/toft-docs/latest/user/data/app-storage.md). --- # Python > Flask, FastAPI, Streamlit, marimo and plain Python — how each is detected and started. Source: /toft-docs/latest/user/build/python/ | Framework | Detected by | Started with | |---|---|---| | **Streamlit** | `streamlit` in your dependencies | `streamlit run`, mounted under your app's path | | **FastAPI** | `fastapi` in your dependencies | `uvicorn` with `--root-path` set | | **Flask** | `flask` in your dependencies | `gunicorn` if your dependencies include it, otherwise `flask run` — see [Flask](#flask) | | **marimo** | a root `.py` that imports `marimo` and builds a `marimo.App` | `marimo run`, mounted under your app's path | | **Python** | anything else | `python ` | Dependencies are read from `requirements.txt`, else `pyproject.toml`, else `Pipfile`. `marimo` as a dependency alone is not the trigger — a Flask app that imports it stays Flask. ## Flask Toft never adds a web server you didn't ask for. If your dependencies include `gunicorn`, your Flask app is served by gunicorn; otherwise it runs on Flask's development server. The build log says which one your app got: ```text Serving with gunicorn (app), from this app's requirements. ``` To move to gunicorn, add `gunicorn` to `requirements.txt` and deploy again. gunicorn needs to know which object is your app. Toft looks in your entry point for a top-level `app = Flask(...)` (or an object named `app` or `application`), then for a `create_app()` or `make_app()` factory that takes no arguments. If it finds none, the app runs on Flask's development server even with gunicorn installed, and the build log says so. Toft sets the address gunicorn listens on. To choose workers, timeouts and other options, set `GUNICORN_CMD_ARGS` as an [environment variable](/toft-docs/latest/user/run/settings.md#environment-variables), for example `--workers 4 --timeout 60`. ## Entry point Every Python app except marimo needs one of these at the root, picked in this order: `app.py`, `main.py`, `streamlit_app.py`, `server.py`, `wsgi.py`. With none, the deploy is refused. ## The contract Listen on `PORT`, bound to `HOST`. Toft passes both to Streamlit, FastAPI, Flask and marimo for you; a plain Python app reads them from the environment. Anything you send to the browser must respect the `/apps/` prefix. See [Base paths](/toft-docs/latest/user/build/base-paths.md). ## Dependencies Toft creates a virtual environment with [uv](https://docs.astral.sh/uv/) and installs `requirements.txt` (`uv pip install -r`), or, if there's none, your `pyproject.toml` project (`uv pip install .`). A `Pipfile` is used for detection only. The [Toft SDK](/toft-docs/latest/user/cli/sdk.md) is installed for you. Pin your versions. An unpinned `requirements.txt` resolves differently over time. ## Version ```toml # toft.toml [runtime] python = ">=3.12,<3.14" ``` See [Language versions](/toft-docs/latest/user/build/versions.md). With no pin, `requires-python` in `pyproject.toml` is used only when it rules out the install's default Python. Then Toft builds with the newest version it allows. See [Python's `requires-python`](/toft-docs/latest/user/build/versions.md#pythons-requires-python). ## Work that finishes A web app must keep running — a script that computes something and exits looks like a crash. Run batch work as a [job](/toft-docs/latest/user/automation/jobs.md), and have the web app show the result. --- # Go > How a Go app is detected, built and versioned. Source: /toft-docs/latest/user/build/go/ Detected by `go.mod` plus a `main` package — at the module root, else the first `cmd/*` directory in alphabetical order. Toft runs `go build` and starts the binary. ## The contract Listen on `PORT`, bound to `HOST`. Use `BASE_PATH` for any absolute URL you emit. See [Base paths](/toft-docs/latest/user/build/base-paths.md). ```go addr := os.Getenv("HOST") + ":" + os.Getenv("PORT") http.ListenAndServe(addr, mux) ``` ## Version comes from `go.mod` The `go` line in `go.mod` is your version declaration. `go 1.25` means Go 1.25 or newer. A `go` key under `[runtime]` in `toft.toml` is refused. If no installed toolchain meets your `go.mod`, Toft hands the build to the `go` command: depending on your install's settings it downloads the required toolchain or fails naming both versions. See [Language versions](/toft-docs/latest/user/build/versions.md). ## Front ends Server-rendered pages work. A bundled SPA works if you commit its built assets and embed them — Toft won't run npm in a Go build. ## Module settings The module proxy, checksum database and private-module settings come from the install, not your repository. If a private module won't resolve, ask your administrator. --- # R > Shiny, plumber and R documents — what Toft detects, and why you need renv.lock. Source: /toft-docs/latest/user/build/r/ | Framework | Detected by | |---|---| | **Shiny** | `app.R`; `ui.R` and `server.R`; `server.R` with `www/index.html`; or an `.Rmd` with `runtime: shiny` | | **plumber** | `plumber.R`, or a root `.R` file with `#* @get`-style annotations | | **R Markdown** | one `.Rmd` at the root, rendered at deploy time | | **Quarto (knitr)** | one `.qmd` whose engine is knitr, rendered at deploy time | File names match in either case (`app.r` works). The two document types are covered in [Notebooks and documents](/toft-docs/latest/user/build/notebooks.md). There is no generic R app: a script run with `Rscript` exits, and an app that exits is a crash. An R repository matching none of the four is refused, with the four listed. ## Commit `renv.lock` > **Caution** > > Without `renv.lock`, no packages are installed. The deploy succeeds, the build > log says so, and your app fails at its first `library()` call. ```r renv::init() renv::snapshot() ``` Commit `renv.lock`. You don't need to commit the `renv/` library. Packages come from a repository your administrator configures. If one won't install, the build log names it. ## Version With no pin, Toft uses the R version recorded in `renv.lock` if the install has it, and the default otherwise. To pin explicitly: ```toml # toft.toml [runtime] r = ">=4.5" ``` Changing R versions reinstalls every package, so that deploy is slower. See [Language versions](/toft-docs/latest/user/build/versions.md). ## From Posit Connect or RStudio A bundle with a Posit Connect `manifest.json` and no `renv.lock` is translated into an `renv.lock` automatically. You can also publish straight from RStudio's **Publish** button — see [From RStudio](/toft-docs/latest/user/deploy/from-rstudio.md). --- # Static sites > Serve a folder of HTML, or the committed output of a front-end build. Source: /toft-docs/latest/user/build/static/ A folder of HTML, CSS, JavaScript and images deploys with no configuration. Toft serves the files itself. ## Two ways in **A plain folder.** Detected automatically when the root has HTML and none of `package.json`, `requirements.txt`, `pyproject.toml`, `Pipfile` or `go.mod`. **A built front end** (Vite, Astro, Hugo, Docusaurus and so on). Commit the build output and declare where it is: ```toml # toft.toml [static] root = "dist" ``` A `[static]` table overrides detection, so a repository with a `package.json` is served as static. > **Caution** > > Toft never runs your build for a static app. The output folder must be > committed, and not listed in `.gitignore`. ## Settings | Key | Default | Meaning | |---|---|---| | `root` | repository root | Folder to serve, relative to the repository. | | `entry` | `index.html`, else the only `.html` file in the root | The page served at `/apps//`. Several candidates and no `entry` fails the deploy. | | `fallback` | `false` | Serve `entry` for unmatched GET requests whose path has no file extension, unless the request asks only for a non-HTML type such as JSON. A missing file with an extension stays a 404. Turn on for single-page apps with client-side routing. | A `404.html` in the root is used for unmatched paths. A subfolder with its own `index.html` is served at its path; directory listings are off. ## Set your generator's base path Your site lives under `/apps//`. | Generator | Setting | |---|---| | Vite, Astro | `base` | | Next.js (static export) | `basePath` | | Hugo | `baseURL` | | Docusaurus | `baseUrl` | Relative URLs (`href="styles.css"`) need no setting. Root-absolute ones (`href="/styles.css"`) miss your app and return 404; the build log names the first few it finds. ## Never served Dotfiles and dot-folders (`.git/`, `.env`), `toft.toml` and `ecosystem.config.json` return 404 at any depth. ## Limits A static app answers `GET` and `HEAD` only. It has no process, so it can't declare [jobs](/toft-docs/latest/user/automation/jobs.md) or a `[runtime]` pin — either one fails the deploy. --- # Notebooks and documents > Publish a Jupyter notebook, Quarto or R Markdown document as a page. Source: /toft-docs/latest/user/build/notebooks/ A repository with one `.ipynb`, `.qmd` or `.Rmd` at its root is a **document app**: Toft runs it once during the deploy and serves the HTML it produces. | Source | Rendered with | |---|---| | `.ipynb` | nbconvert (Python), or Quarto if you declare `engine = "quarto"` | | `.qmd` | Quarto, with Python or R depending on its engine | | `.Rmd` | R Markdown | | `.Rmd` with `runtime: shiny` | Not a document — a [Shiny app](/toft-docs/latest/user/build/r.md) | For a document viewers can interact with, use marimo, Streamlit or Shiny instead. ## It runs at deploy time Visitors execute nothing; they read the output. - The numbers are from deploy time. Refresh them by redeploying, or with a [render schedule](/toft-docs/latest/user/build/reports.md). - A slow document is a slow deploy. Past the render timeout your administrator sets, the deploy fails. - A failing document fails the deploy, and the previously published version stays up. > **Output is published** > > The document runs with your app's environment variables. Anything it prints — > a connection string included — ends up on the page. The build log lists which > variables were available. ## Dependencies Toft installs the renderer (Jupyter, nbconvert or Quarto). Declare only what your document imports: `requirements.txt` for Python, `renv.lock` for R. Only the rendered output is served. Your source files, `renv.lock` and everything else in the repository return 404. ## Choosing the file With more than one candidate at the root, or a document in a subfolder, name it: ```toml # toft.toml [notebook] file = "reports/weekly.qmd" ``` ## Quarto: Python or R A `.qmd` runs as R when, in this order: 1. `toft.toml` sets `[notebook] engine = "knitr"` (or `"jupyter"` for Python). 2. The document's YAML header has `engine: knitr` or a top-level `knitr:` key. 3. It contains an R chunk (` ```{r} `). Otherwise it runs as Python. --- # Scheduled re-renders and report delivery > Refresh a published document on a schedule, and email a link to the people who read it. Source: /toft-docs/latest/user/build/reports/ A [document app](/toft-docs/latest/user/build/notebooks.md) shows the numbers from its last render. A **render schedule** re-renders it on a cron expression. ## Set a schedule 1. Open the app's **Settings** tab. 2. Under **Re-render on a schedule**, enter a five-field cron expression, in UTC. `0 6 * * 1-5` is 06:00 UTC on weekdays. 3. Select **Schedule**. The **On** switch pauses it; **Remove** deletes it. A re-render is a full deploy of the app's branch: the code is fetched again, dependencies are reinstalled, and the document runs from the top. It appears in the app's deployment history. If it fails, the published version stays up. ## Send it to people Once a schedule exists, add recipients under **Send it to**. After each successful scheduled render, each recipient gets an email with a link to the app. - **A link, never the file.** Recipients sign in as usual, so a private report stays private, and everyone on the list needs access to the app. - **Only a scheduled render sends.** A failed render or a manual redeploy mails nobody. - **Access is re-checked on every send.** Someone who has lost access, or has no email address, is skipped with a reason and stays on the list. - **Sends are rate-limited.** A render that comes too soon after the last delivery is recorded as skipped. - **Mail must be configured.** Without a mail relay on the install, every recipient is skipped with that reason. The latest delivery result is shown beside the recipient list. ## Render schedule vs. a scheduled job A render schedule rebuilds and republishes a document. A [job](/toft-docs/latest/user/automation/jobs.md) with a schedule runs a command from your deployment and leaves the published document as it is. Use a render schedule to refresh what readers see; use a job for work alongside it. --- # Language versions > Pin the Python, Node, R or Go version your app builds and runs on. Source: /toft-docs/latest/user/build/versions/ With no pin, your app gets the install's default version. To choose, declare a range in `toft.toml` — or, for Go, in `go.mod`. ```toml # toft.toml [runtime] python = ">=3.12,<3.14" node = "22" r = ">=4.5" ``` Toft picks the **highest** installed version that satisfies the range. ## How to write a range | Language | Compared on | Examples | |---|---|---| | Python, R | major.minor | `3.13`, `>=3.12,<3.14` | | Node | major | `22`, `>=20` | - Operators are `>=`, `<=`, `>`, `<` and `==`. A bare version means exactly that version. A comma means AND. - One wildcard is accepted: a trailing `.*` after `==`, as in `python = "==3.13.*"`, which means the 3.13 line — the same as `3.13`. - `^`, `~`, `~=`, `||`, `!=` and any other `*` are refused, each with the range to write instead. - Extra components are refused, not truncated: `3.12.4` for Python, `22.4` for Node. To stay on an older version, give an upper bound or an exact version (`python = "3.13"`). A bare `>=` floor always takes the newest. ## Node's `engines` and `.nvmrc` Toft does not read `engines.node` in `package.json`, `.nvmrc` or `.node-version`. A tree that states its Node version only there builds on the install's default, and `toft check` and the deploy say so with a `node_version_unread` warning naming the version it got. Pin it in `toft.toml` instead: `[runtime] node = "22"`. ## Python's `requires-python` With no `[runtime] python` pin, Toft uses the install's default Python unless `requires-python` in `pyproject.toml` rules it out. In that case it builds with the newest installed version that `requires-python` allows, and the build log and `toft check` say so. If no installed version is allowed, the deploy is refused and the error lists the versions the install has. A lower bound such as `>=3.8` never changes the version, because the default already satisfies it. To choose a version yourself, pin it with `[runtime] python`. ## Go Go reads the `go` line in `go.mod` as a minimum — `go 1.25` means 1.25 or newer. If no installed toolchain meets it, the `go` command either downloads one or fails, depending on your install. See [Go](/toft-docs/latest/user/build/go.md). ## When no version matches The deploy is refused before anything builds, and the error lists the versions the install has. Toft never substitutes a different version. ## Which versions exist Your administrator decides. The published Toft image ships three of each language, Go included, with the newest as the default: | Language | Versions | |---|---| | Python | 3.15, 3.14, 3.13 | | Node | 24, 22, 20 | | R | 4.6, 4.5, 4.4 | Your install may differ. ## What's running Your app's **Settings** tab, under **Runtime**, shows the exact version the serving deployment runs on. A restart keeps it. If your administrator removes a version, running apps keep it. Your next deploy re-resolves and is refused if the pin no longer matches — that's your cue to update the pin. --- # Base paths > Your app is served under /apps/. Here's what that means for links, redirects and cookies. Source: /toft-docs/latest/user/build/base-paths/ Your app is served at `/apps/` on the apps address, not at the root of a host. Most "it deployed but the page is broken" problems come from this. ## What your app is given | Variable | Holds | |---|---| | `PORT` | The port to listen on. | | `HOST` | The address to bind to. | | `BASE_PATH` | The prefix, e.g. `/apps/reports`. | For most frameworks the proxy strips the prefix before forwarding, so your routes stay at `/`. Next.js, Streamlit and marimo are configured with the prefix and receive it unchanged. ## Links carry the prefix Anything the browser resolves — links, form actions, script and stylesheet URLs — must land under the prefix. ```html ``` Relative URLs work everywhere. Use `BASE_PATH` when you can't be relative. ## Redirects don't need to The proxy adds the prefix to an absolute-path `Location` header, so you can redirect as if your app were at the root, in every framework: ```python return redirect("/dashboard") # right return redirect("/apps/reports/dashboard") # also right: never prefixed twice ``` A `Location` that already carries the prefix, from `url_for` or a framework's own helpers, is left as it is. Relative redirects (`new/`, `?page=2`) pass through untouched. ## By framework | Framework | What to do | |---|---| | Next.js | Nothing. `basePath` is set before the build. | | Streamlit, marimo | Nothing. | | FastAPI | Nothing. `root_path` is set, so `url_for` includes the prefix. | | Flask | Relative URLs, or wrap with `ProxyFix(app.wsgi_app, x_prefix=1)` so `url_for` reads the `X-Forwarded-Prefix` header. | | Node, Go, plain Python | Relative URLs, or prefix with `BASE_PATH`. | | Shiny, plumber | Nothing for routing; keep asset URLs relative. | | Static sites | Set your generator's base option. See [Static sites](/toft-docs/latest/user/build/static.md). | ## Cookies and WebSockets Cookies your app sets are scoped to its own path. The platform's sign-in cookies (`toft_*`) are removed before requests reach your app. WebSockets pass through. Connect to the same prefixed path the page was served from. --- # toft.toml > The optional manifest at your repository root, and every table it can carry. Source: /toft-docs/latest/user/build/toft-toml/ `toft.toml` is optional. Without it, Toft detects your app and uses defaults. Add it to tell Toft what detection can't work out. Every key is optional. ## `[app]` Which app the `toft` CLI deploys to, optionally per install. Only the CLI reads it; `toft link` writes it. ```toml [app] slug = "sales-dashboard" [app.installs."https://staging.toft.example.com"] slug = "sales-dashboard-stg" ``` ## `[runtime]` Version ranges for `python`, `node` and `r`. A `go` key is refused — Go reads `go.mod`. See [Language versions](/toft-docs/latest/user/build/versions.md). ## `[static]` Serve the repository, or a folder of it, as a static site. Overrides all other detection. | Key | Meaning | |---|---| | `root` | Folder to serve, relative to the repository. | | `entry` | Page served at `/apps//`. Defaults to `index.html`. | | `fallback` | `true` to serve `entry` for unmatched routes (single-page apps). | See [Static sites](/toft-docs/latest/user/build/static.md). ## `[notebook]` | Key | Meaning | |---|---| | `file` | The document to render, when there are several or it's in a subfolder. | | `engine` | `"nbconvert"` or `"quarto"` for an `.ipynb`; `"knitr"` (R) or `"jupyter"` (Python) for a `.qmd`. | See [Notebooks and documents](/toft-docs/latest/user/build/notebooks.md). ## `[batch]` Declares an app that is only jobs: no web server. Never inferred, and can't be combined with `[static]`. ```toml [batch] language = "python" # only needed if the repository has several languages [jobs.extract] command = "python extract.py" schedule = "0 2 * * *" ``` See [Schedule work with jobs](/toft-docs/latest/user/automation/jobs.md). ## `[jobs.]` One table per [job](/toft-docs/latest/user/automation/jobs.md). This file is the only place a job is defined; every deploy makes the app's jobs match it. | Key | Meaning | |---|---| | `command` | What to run. Required, unless `args` is given. | | `args` | Go apps only: arguments to the binary the build produced, instead of `command`. `["job", "extract"]`. | | `schedule` | Cron expression. UTC unless `timezone` is set. Leave it out for an on-demand job. | | `timezone` | IANA zone for `schedule`, e.g. `Europe/London`. | | `overlap` | `queue` (default) or `skip`: what a scheduled firing does while the last run is still going. | | `notify` | `none` (default), `failures` or `always`: when to email the app's owners about a run. | | `expect_within` | Report the job overdue after this long without a success: `26h`, `7d` (1 hour to 31 days). | | `timeout` | How long a run may take: `"45m"`, or seconds. Your install's default without it. | | `memory` | Memory limit in MB. | | `cpu` | CPU limit in millicores (50–32000). | Declare parameters as `[jobs..params.]` with `type` (`string`, `int`, `float` or `bool`), `required` or `default`, `choices` and `help`. A `timeout` or `memory` above what your administrator allows fails the deploy with `limit_exceeds_ceiling`. `retries`, `viewer_runnable`, `enabled` and a `[tasks]` table are no longer read; a deploy refuses them with `removed_key` and says what replaced each. > **Note** > > Parameters are passed to your process as values, never pasted into the > command line, so it's safe to let other people supply them. ## Errors and limits A malformed `toft.toml` fails the deploy and names the problem. Unknown tables are ignored, so a newer manifest still deploys on an older install. Environment variables belong on the app's **Settings** tab, not here. Nothing in this file grants permissions or changes build resources. --- # The app page > The tabs on an app's page, who sees each one, and where to find your app's address. Source: /toft-docs/latest/user/run/the-app-page/ Everything about one app lives on its page. You see only the tabs your role and this install allow; the rest are absent, not grayed out. | Tab | Shows | Who sees it | |---|---|---| | **Overview** | Address, status, last deploy, preview, notices | Anyone who can open the app | | **Usage** | Requests, viewers, CPU, memory | Anyone who can open the app | | **Automation** | The app's jobs and their runs | Editors | | **Logs** | Run log, build log, recent failures | Editors | | **Deployments** | Every deploy, rollback | Editors | | **Dependencies** | Packages and policy findings, when the install has a policy | Editors | | **Integrations** | What the app may send, and where | Editors | | **Reach** | Everything the app has been given | Editors | | **Audit** | The app's audit file, for an assessor | Editors | | **Access** | Visibility, sharing, access requests | Whoever may share | | **Settings** | Name, source, runtime, variables, deletion | Editors | ## Overview Opens on a **Summary** of the app — owner, who can see it, when it was created and, for editors, what is serving and whether it is the latest deployment. Under it, **Preview** shows the app inside the page; it stays closed until you open it, because loading the app wakes it and counts as a visit. Below that, an **Activity** section shows recent usage and failures. A [batch app](/toft-docs/latest/user/automation/jobs.md#where-a-job-runs) has no address and no Open, Start or Stop. The first button at the top of the page is what the app needs next: **Start app** when it is stopped, **Restart app** when it has crashed, **View build log** when its first deploy failed, and **Open** otherwise. **Open** is always there. Start, Restart and Stop are also in the **⋯** menu. An uploaded app has a **Deploy** menu with **Upload new version** and **Redeploy current version**; an app deployed from git has **Redeploy**. Notices that need you sit above the tabs: an unapplied variable change, a refused deploy, a blocked source, a lock. Notices about an app that is down come first. Your app's address is `/apps/` on the install's apps address, which may be a different port or hostname from Toft itself. Copy it from the page. > **Note** > > The deploy date is when the app was last deployed, not proof it matches your repository. Pushes deploy only with [auto-deploy](/toft-docs/latest/user/deploy/auto-deploy.md) on. ## Reach One list of what the app has been given: who may open it, the credentials and variables it holds, the storage attached to it, what it may send, and the keys that act as it. It shows names, never secret values, and what the app *can* do rather than what it did. The line at the top says what Reach can't see, such as outbound network access. ## Audit The app's **audit file**: what to hand an assessor about this one app. It covers who may open it and who gave each person access, where its code comes from, every deploy and who started it, its packages, what it can reach, how old its credentials are, where its output goes, who opened it, what deleting it would remove and what Toft can't see. A section you aren't allowed to read is shown as **Refused** with the reason, rather than left out. For example, the history of changes to the app is for administrators only. **Export** downloads the same reading as a JSON file with a digest anyone can recheck, and Toft keeps no copy. It lists who has access. Deciding whether each person should keep it is your organization's access review. ## Deleting an app **Settings → Danger zone**, type-to-confirm. It removes the app, its deployments and its logs, and detaches its storage. Data in a store or volume stays. --- # Status and sleeping > What each app status means, how sleeping and waking work, and why an app crashes. Source: /toft-docs/latest/user/run/status/ | Status | Meaning | |---|---| | **Building** | A deploy is in progress. | | **Running** | Up and serving. | | **Sleeping** | Idle, so Toft stopped the process. The next request wakes it. | | **Stopped** | Stopped by a person or a lock. Stays down until started. | | **Ready** | A [batch app](/toft-docs/latest/user/automation/jobs.md#where-a-job-runs) with a deployment. Runs jobs, serves nothing. | | **Crashed** | Exited, never came up, or kept running out of memory. | | **Failed** | The last deploy didn't produce a working deployment. | ## Sleeping A sleeping app still answers its address; the first request is slower while it starts. If that delay matters, make startup faster. Apps sleep only if your administrator set an idle timeout; **Settings → Runtime → Never sleep** shows it. An app never sleeps if it runs in isolated mode or an administrator exempted it. A [job's](/toft-docs/latest/user/automation/jobs.md) schedule does not keep it awake: jobs run in their own process. ## What visitors see when the app can't answer If your app is stopped, sleeping, starting, crashed, locked or still building, Toft answers in its place with a page saying so, with status `503`. That page always carries an `X-Toft-Interstitial` header naming the state — for example `stopped`, `sleeping`, `waking`, `crashed` or `deploy_failed` — and your app's own responses never do. A monitor or script can check for the header to tell Toft's page from a `503` your app sent itself. ## Why an app crashes The **Logs** tab has the reason. Usually: - It didn't listen on the port in `PORT`. - A variable is missing. Your app gets only what is set for it, never your shell's environment. - The process exited. A web app must keep running; for run-and-exit work, use a [job](/toft-docs/latest/user/automation/jobs.md). - It ran out of memory. A start that isn't up after five minutes reads **Crashed**, with the backend's reason. ## Restarts are bounded - Five restarts within fifteen minutes raises a **Restart loop** event. - An isolated app is restarted up to five times, then left down with a **Not restarting** event. - After three memory kills on one deployment, the app is stopped and marked **Crashed**. The memory ceiling is under **Settings → Runtime**; only an administrator can raise it. ## Starting and stopping On the app page, a stopped app's first button is **Start app** and a crashed app's is **Restart app**. Start, Restart and Stop are also in the **⋯** menu. From the command line: ```bash toft stop toft start toft restart --wait ``` A restart runs the same deployment again. It doesn't rebuild or re-check dependencies. --- # Logs > The run log, the build log, and the platform's own lines about your app. Source: /toft-docs/latest/user/run/logs/ Anything your app writes to stdout or stderr appears on the **Logs** tab, live. ## Run log and build log | | Shows | |---|---| | **Run log** | What the serving deployment is printing. | | **Build log** | The output of one deploy. Pick the build from the list; the address links straight to it. | The tab opens on the build log while a deploy is running or when the last one failed. Otherwise it opens on the run log. A deploy that fails has no run log, because nothing started. ## Your lines and the platform's The run log carries your app's output and the platform's lines about your app: the deployment it fetched, the interpreter it resolved, why a start was refused, when it was put to sleep. Filter with **All**, **App** or **Platform**. Each line carries the time it was written, and the two voices are interleaved in time order. Output your app wrote before it was last started on an older version of Toft has no time; the platform's lines are then shown above it. ```bash toft logs # both voices toft logs --source app # only your app toft logs --source platform # only Toft's lines toft logs --follow # keep streaming toft logs --lines 500 # the last 500 lines in all toft logs --deploy # a build log, whole toft logs --deploy --lines 50 # its last 50 lines ``` The number of lines is a total across your app and the platform, not per source. ## What to print Write to stdout and stderr. Don't write log files into your app's directory to read later: a deploy replaces it. Your administrator's log collector receives the same lines, so a line that matters can be found there after the tab has moved on. The **Logs** tab hides Toft's own credentials if your app prints them (`tofta_`, `toftu_`, `toftd_` tokens and signed storage links), and so does the copy; anything else you print is shown as printed. ANSI color is rendered on the Logs tab and in a terminal; `toft logs` removes it when its output is piped or saved to a file. > **Caution** > > Logs are not kept forever. Anything you need to keep, such as an audit trail or a report, belongs in storage, not the log. Below the log, **Recent failures** lists what Toft recorded going wrong. See [Events](/toft-docs/latest/user/run/events.md). --- # Events > The failures Toft recorded for your app, and how to filter them. Source: /toft-docs/latest/user/run/events/ Events are the things Toft noticed going wrong with your app: a failed deploy, a crash, a restart loop, a job or scheduled run that failed or was skipped, a deploy refused by policy. They appear under **Recent failures** on the **Logs** tab, newest first, and from the CLI: ```bash toft events toft events --since 24h --severity error toft events --kind deploy_failed --limit 50 ``` ## Why not just the log The log is what your process printed; an event is what Toft observed. If a scheduled run never started, only the events say so. Every event has a time, a kind and a severity, and you can filter on each. An event is recorded whether or not anybody was emailed about it, and a resolved one stays in the list. | Common kinds | Meaning | |---|---| | **Deploy failed** | The build or start of a deploy failed. | | **Crashed** | The workload went down. | | **Restart loop** | It restarted repeatedly in a short period. | | **Memory limit** / **Out of memory** | It was killed for memory, or stopped after repeated kills. | | **Not restarting** | Toft stopped trying to restart it. | | **Job run failed** / **Scheduled job failed** | A run exited with an error. | Events are pruned after a period your administrator sets. --- # Settings and environment variables > Name, slug, source, runtime and environment variables on an app's Settings tab. Source: /toft-docs/latest/user/run/settings/ The **Settings** tab holds everything you can change about an app. | Section | Contains | |---|---| | **Identity** | Name, slug, app ID, tags | | **Source & deploys** | Repository, branch, folder, auto-deploy, git token | | **Runtime** | Language version, execution mode, sleep, memory, credentials, environment variables | | **Notifications** | Email maintainers when the app breaks | | **Data** | The app's saved data | | **Danger zone** | Transfer ownership, delete | ## Environment variables Set them under **Runtime → Environment variables**. They are stored encrypted and read the usual way, such as `os.environ["API_KEY"]` or `process.env.API_KEY`. Click the lock icon to make a value **sensitive**: it can be replaced but never shown again. Saving doesn't restart the app. A **Saved, but not running yet** notice offers **Restart to apply**. > **Your app gets only three things** > > Your variables, the ones Toft supplies (`PORT`, `BASE_PATH`, `TOFT_*`), and [credentials an administrator attached](/toft-docs/latest/user/run/credentials.md). Never your shell's environment. - **One source per key.** You can't set a key an attached credential supplies. - **Names are letters, digits and underscores**, not starting with a digit. Anything else is refused. - **Toft's own names are refused**, because Toft sets them itself and your value would never reach the app: `PORT`, `HOST`, `BASE_PATH`, `TMPDIR`, `VIRTUAL_ENV`, `R_LIBS_SITE` and the `TOFT_*` variables it sets. See [Environment variables](/toft-docs/latest/cli/reference/environment.md) in the CLI & SDK guide. A variable you saved before this rule keeps working and doesn't block saving the rest. - **Defaults from the repository.** If the install has an environment tag such as `prod`, each deploy reads `.env.` (or `.env`) and adds variables the app doesn't have yet. It never overwrites a value set in Toft, and it skips an invalid or reserved name with a warning in the build log. ## Name and slug They are separate. The slug is the end of the address, so changing it breaks the old address at once, with no redirect. Slugs starting with `_` are reserved. ## Source and runtime Source is the repository, branch, optional folder and auto-deploy; see [From a git repository](/toft-docs/latest/user/deploy/from-git.md). Runtime shows the exact language version the serving deployment resolved. Execution mode, **Never sleep** and the memory limit are visible but only an administrator can change them. ## Tags and ownership Tags come from your administrator's [taxonomy](/toft-docs/latest/user/share/tags.md). An app is owned by one person or one group; see [Ownership and transfer](/toft-docs/latest/user/share/ownership.md). --- # Credentials an administrator attaches > Shared secrets an administrator gives your app without you ever holding them. Source: /toft-docs/latest/user/run/credentials/ Some values your app needs aren't yours to hold: a warehouse password, a shared API key, a service account. An administrator stores them centrally and **attaches** them to your app. ## What you see Under **Settings → Runtime → Credentials**, each attached credential shows its name and the keys it supplies, such as `DATABASE_URL` or `WAREHOUSE_PASSWORD`. Never the values: no route returns them, to anybody. ## What your app sees At start, the keys are in your app's environment like any other variable. They don't appear in your own variable list. ## Rules that affect you - **One source per key.** You can't set a variable a credential already supplies. The save is refused and names the credential. - **Detaching restarts your app** onto its current deployment. - **Changing a credential's values doesn't restart anything.** Your app shows the **Saved, but not running yet** notice until it restarts. - **A credential attached to apps can't be deleted** until it is detached. ## Asking for one Tell your administrator which app, which key names, and why it shouldn't be a variable you set. The usual reasons: several apps need the same value, or you shouldn't be able to read it. --- # Usage and resources > Who opens your app, how often, and what it consumes. Source: /toft-docs/latest/user/run/usage/ The **Usage** tab shows traffic and consumption over the last **7d**, **30d** or **90d**. Anyone who can open the app can see it. | Figure | Meaning | |---|---| | **Requests** | Requests to the app in the window. | | **Unique viewers** | Distinct people and keys that made them. | | **Requests / day** | The average. | | **CPU time** | CPU used, with the reservation if one is set. | | **Peak memory** | The highest reading, against the memory the app is allowed. | A daily chart sits below the figures. A batch app shows only CPU time and peak memory. The list of individual viewers is shown only to people who manage the app's sharing. They can also download it, one line per person per day, over any range of dates: choose **From** and **To** and select **Download CSV**. A year is the default. If your install keeps visits for a shorter time, the file starts at the oldest day it keeps. ## What doesn't count - **Reachability probes.** The platform checking that a public app answers. - **Security scans** that identify themselves. Neither is somebody using the app, so neither makes it look busy or keeps it from looking [quiet](/toft-docs/latest/user/run/quiet.md). ## Missing is not zero A dash means nobody could take the reading, not that the reading was zero. When part of the window wasn't observed, the tab says **These figures are a lower bound**. > **Note** > > Usage is not product analytics. There are no funnels, page breakdowns or custom events. If you need those, build them into your app. --- # Dependencies and policy > How Toft checks your app's packages against advisories, and what to do when a deploy is refused. Source: /toft-docs/latest/user/run/dependencies/ The **Dependencies** tab lists the packages your app installs (npm, PyPI, Go, CRAN) and any advisories that match. It appears only when your administrator has turned on a dependency policy. > **Not a security review** > > It reads package names and versions, not your code. It won't find a bug you wrote or a secret you committed. The top line shows the mode (**Blocking** or **Warning only**), the advisory data's age, the serving deployment, and the last check. **Check again** re-runs the check without redeploying. ## What a policy can do In blocking mode, a policy refuses a **deploy** above a severity (optionally only when a fix exists) and, separately, for malicious packages. A restart is not a deploy, and stale advisory data never blocks. One setting, off by default, goes further: a background check **locks and stops** an app whose serving deployment has a malicious package, or a critical or high advisory if set that way. Waived findings never lock, and **Check again** never locks. An unlock alone is undone by the next check; the way out is a fixed deploy or a waiver. See [When an app is locked](/toft-docs/latest/user/run/locked.md). ## When your deploy is refused The app page shows **Your last deploy was refused**; the build log names the package, version and advisory. 1. **Upgrade the package.** Usually the whole fix. 2. **Ask for a waiver.** Only an administrator grants one, and it expires. 3. **Argue it doesn't apply**, if you don't use the vulnerable code. Take it to your administrator. ## SBOM Each successful deployment has **Download SBOM** in its **⋯** menu on the **Deployments** tab: a CycloneDX 1.6 file listing the packages that release installed. Where Toft could not read everything, the file says **incomplete** or **unknown** rather than complete. That happens, for example, with an app locked with pnpm or Yarn, an app with no lockfile, or vendored code. The deploy's warnings name what was missed. Downloading the same release twice gives the same file. API: `GET /api/v1/apps/{id}/deployments/{deploymentID}/sbom`. A personal API key or a deploy key can read it. ## When there's no verdict | Message | Means | |---|---| | **This app declares no dependencies** | A static site with no manifest. | | **This app's packages cannot be listed** | An R app with no `renv.lock`. Commit one (`renv::snapshot()`). | | **This deployment's packages haven't been recorded** | The next deploy will record them. | | **Nothing found, but coverage is thin** | CRAN advisory data is sparse, so silence means little. | | **Nothing has been checked yet** | No advisory data has loaded on this install, so an empty list is not a clean result. The deploy's warnings say so too. Ask your administrator to refresh it. | --- # When an app is locked > What a lock blocks, why you can't lift it, and how to get your app back. Source: /toft-docs/latest/user/run/locked/ An administrator can **lock** an app. Locking stops it, and it stays stopped until the lock is lifted. Only an administrator can lift it. ## Locked is not stopped | | Stopped | Locked | |---|---|---| | Who can undo it | Any editor, with Start | Only an administrator | | Visitors see | — | The lock message | | After it ends | — | The app stays stopped until somebody presses Start | ## What a lock blocks - Deploying, uploading and auto-deploy - Starting and restarting - **Rolling back**, because it would change what is served - Running jobs: scheduled runs don't fire, queued runs are canceled, and a run in progress is stopped - Changing the app's settings, including its visibility and environment variables Every refusal includes the lock message: the administrator's own words, or the install's default if they didn't write one. Read it; it is written for you. From the CLI, a locked app exits with code **2**, not 1, and prints that nothing was built. ## Who locked it The refusal says which of two things placed the lock: - **An administrator.** Reply to them; Toft has no way around a lock. - **This install's dependency policy.** The serving deployment installs a package the policy won't run, and the **Dependencies** tab says which. Deploy again without it, or ask for a waiver. An unlock alone is undone by the next check. See [Dependencies and policy](/toft-docs/latest/user/run/dependencies.md). ## Retired apps An app that is no longer needed is retired by locking it; there is no separate archived state. If the message doesn't say whether your app was retired or locked for a reason, ask the administrator. --- # Quiet apps > How long since anybody used your app, where Toft shows it, and what it does not mean. Source: /toft-docs/latest/user/run/quiet/ Toft tracks how long it has been since anybody showed evidence of wanting your app. When the app has had no requests in 30 days, the **Usage** card in the **Activity** section on **Overview** says how long it has been quiet: - **No visitor in 214 days**: the last visit was that long ago. - **Never opened since it was deployed 90 days ago**: deployed, never visited. - **Never opened, and no successful deploy**: created and never used. - **Not opened in the 365 days of visits this install keeps**: your administrator keeps visits for a set time, and none from before then survive. ## What counts A visit, a successful deploy, or the app's creation, whichever is latest. A failed deploy doesn't count. Neither do [reachability probes](/toft-docs/latest/user/share/visibility.md) or security scans that identify themselves. ## Quiet is not a verdict Nothing happens to an app because it is quiet. Nothing is stopped, locked or deleted automatically. Your administrator can see a list of quiet apps across the install, and retiring one is a [lock](/toft-docs/latest/user/run/locked.md) a person applies. ## Quiet is not sleeping [Sleeping](/toft-docs/latest/user/run/status.md) is measured in minutes and ends at the next request. Quiet is measured in days or months. An app can be awake and quiet, or asleep and busy. ## If you're asked about a quiet app The usual answers: - **It's finished.** Say so, and let it be retired. - **It's seasonal.** A quarterly report is quiet most of the year. --- # Who can see your app > The three visibilities on the Access tab, and what can keep an app from being public. Source: /toft-docs/latest/user/share/visibility/ Every app has one visibility, set under **Access → Who can view this app**. New apps are **Restricted**. | Visibility | Who can open it | |---|---| | **Restricted** | You, and the people and groups you [share it with](/toft-docs/latest/user/share/people.md). | | **Anyone signed in** | Anyone who can sign in to this install. | | **Public** | Anyone with the link, no sign-in. | Toft enforces visibility before the request reaches your app. Your app never sees a request from someone who may not open it. ## When Public is not available An administrator can turn off public access for every app. The **Public** option is then greyed out, and an app that is already public is served to signed-in people only, with the notice **This install doesn't publish apps**. It becomes public again when public access returns. ## When a public app is not reachable from outside If your install checks reachability from outside and two checks in a row fail, the app is served to signed-in people only, and the Access tab shows **This app isn't public at the moment**. The cause is nearly always DNS, a firewall, a proxy or TLS; show it to your administrator. To publish again, choose another visibility and then **Public**. ## Embedding **Access → Embedding** lists the other sites that may show your app in an `