This is the full developer documentation for Suta # Suta documentation > Update, monitor and fix devices in the field as easily as you deploy to the cloud. Suta lets you update, monitor and fix devices in the field as easily as you deploy to the cloud. Describe what your devices should run, review exactly what will change, then roll it out in stages. Every device checks each update itself and rolls back on its own if anything breaks, even when it can’t reach Suta. Your team, your CI and your AI agents all work through Suta, with only the access you give them. ## Get started Supported hardware Suta currently runs on 64-bit Raspberry Pi devices, such as the Pi 5, Pi 4 and Pi Zero 2 W. See [Requirements](/add-devices/requirements/). [Quickstart](/get-started/quickstart/)Create a workspace and get your first device online. [Deploy your first app](/get-started/first-deploy/)Write a suta.yaml, preview the plan and roll it out. [Example project](/get-started/example/)Serve your own page from every device, and deploy each change from CI. New to Suta? Read [Key concepts](/get-started/concepts/) for the few ideas the rest of the documentation builds on. ## Guides Add devices * [Requirements](/add-devices/requirements/) * [Set up a new Pi](/add-devices/new-pi/) * [Install on an existing Pi](/add-devices/existing-pi/) * [Fix a device’s network](/add-devices/repair-network/) Deploy software * [Write suta.yaml](/deploy/suta-yaml/) * [Plan and roll out](/deploy/rollouts/) * [Roll back, retry and pin](/deploy/device-software/) * [Move a device to Suta OS](/deploy/migrate/) Operate devices * [Devices and tags](/operate/dashboard/) * [Monitor devices](/operate/monitoring/) * [Remote access](/operate/remote-access/) * [Alerts and webhooks](/team/alerts/) Teams and automation * [People and roles](/team/people-and-roles/) * [Activity and recordings](/team/activity/) * [Connect an AI agent](/ai-agents/connect/) * [API tokens for CI](/ai-agents/api-tokens/) * [Deploy from GitHub Actions](/ai-agents/github-actions/) ## Reference [Suta CLI](/reference/cli/)Every command and option. [suta.yaml](/deploy/suta-yaml/#top-level-settings)Every setting for apps, sites and devices. [Permissions](/reference/permissions/)What each permission allows, and which roles include it. [Limits and timings](/reference/limits/)Durations, sizes and retention periods. # Quickstart > Create a workspace and get a Raspberry Pi online in Suta in about five minutes. In this quickstart, you create a Suta workspace and connect a Raspberry Pi to it. At the end, the Pi shows as **Online** in your dashboard, ready for you to [deploy your first app](/get-started/first-deploy/). ## Before you begin You need: * A Raspberry Pi with a 64-bit processor, such as a Pi 5, Pi 4 or Pi Zero 2 W. * An internet connection for the Pi, over Ethernet or Wi-Fi. * A computer running macOS or Linux. For the full list, see [Requirements](/add-devices/requirements/). ## Create a workspace A workspace holds your team and your projects. A project is one fleet of devices and the software they run. 1. Go to [app.suta.dev](https://app.suta.dev) and select **Create an account**. 2. Enter a **Workspace name**, such as the name of your team, and a **First project**, such as `Delivery drones`. Select **Create workspace**. You can rename both later. ## Connect a device Choose the tab that matches your Pi. * New Pi Start from a blank SD card. You flash the card with the Suta CLI, then set the Pi up over Bluetooth. 1. On your computer, install the Suta CLI: ```sh curl -fsSL https://suta.dev/install.sh | bash ``` When asked what to install, choose **Suta CLI**. 2. Insert an SD card of 16 GB or more, then run: ```sh suta flash ``` Choose your card with the arrow keys, then confirm that you want to erase it. 3. Put the card in your Pi and turn it on. The first boot takes a minute or two. 4. In Chrome on your Mac, go to [app.suta.dev](https://app.suta.dev), open your project, then select **Add device** > **Set up a new device**. 5. Select **Find my device** and choose your Pi in Chrome’s Bluetooth window. 6. Name the device, then select **Claim this device**. 7. Choose **Ethernet** or **Wi-Fi**, and wait for **Device connected**. Tip Not using Chrome on a Mac? Run `suta device add` in your terminal instead. It walks you through the same steps. See [Set up from the terminal](/add-devices/new-pi/#set-up-from-the-terminal). * Pi that's already running Use this path if your Pi already runs 64-bit Raspberry Pi OS (Bookworm or Trixie) and you can connect to it over SSH. Suta doesn’t change your operating system, network settings or services. 1. Open an SSH session to your Pi. 2. Run the installer: ```sh curl -fsSL https://suta.dev/install.sh | bash ``` When asked what to install, choose **Suta agent**. 3. Open the registration link that the installer prints. Check the device’s workspace, enter a name for it, choose its project if the workspace has more than one, then select **Register device**. 4. Keep the SSH session open until the installer reports that the device is connected. ## Check that it worked Open your project at [app.suta.dev](https://app.suta.dev), then select **Devices**. Your Pi appears with a green **Online** status. If it shows **Waiting for connection**, the device is registered but hasn’t reached Suta yet. Check that it has internet access, then see [Device stays offline](/troubleshooting/#device-stays-offline). ## Next steps * [Deploy your first app](/get-started/first-deploy/) to the device. * [Turn on monitoring](/operate/monitoring/) to see CPU, memory, temperature and service logs. * [Invite your team](/team/people-and-roles/) and choose what each person can do. # Deploy your first app > Describe an app in suta.yaml, preview the change, roll it out and see the device check it. In this tutorial, you deploy a web server to a device. Along the way you write a `suta.yaml`, review a plan, start a rollout and watch the device accept the change. It takes about ten minutes. ## Before you begin * Complete the [Quickstart](/get-started/quickstart/), so you have a device that shows as **Online**. This guide calls it `kitchen-pi`; use your device’s name instead. * Sign in to the CLI with `suta auth login`. If you created the workspace, you’re an admin and have every permission you need. Otherwise, you need the Device manager role. * If the device runs Raspberry Pi OS, it needs Trixie. Podman, which runs your apps, is too old on Bookworm; [move a Bookworm device to Suta OS](/deploy/migrate/) first. ## Describe the app 1. Create a directory for your fleet’s configuration, and go to it: ```sh mkdir my-fleet && cd my-fleet ``` 2. Create a file named `suta.yaml` with this content: suta.yaml ```yaml # yaml-language-server: $schema=https://suta.dev/schema/suta.json project: my-fleet os: latest apps: hello: image: nginx:1.27 ports: ["8080:80"] probe: http: / port: 80 ``` Replace `my-fleet` with your project’s slug. You’ll find it in the dashboard on the project’s **Overview**, and with `suta project list`. This describes one app, `hello`, that runs the public `nginx` image and serves it on port 8080 of the device. The `probe` tells the device how to check that the app works: it must answer HTTP requests on `/`. ## Preview the change Run: ```sh suta plan --device kitchen-pi ``` The output shows what the device will get: ```text + kitchen-pi · update to 3f2a9c1e0b7d OS 2026.09.1 Apps: + hello Plan: 1 to update, 0 unchanged, 0 pinned. Plan 8c41… Deploy it with suta deploy 8c41… ``` `+` means the device will update, and the lines under it say what changes. The plan pinned `nginx:1.27` to an exact image digest and recorded it in a new file, `suta.lock`. Planning hasn’t changed anything on the device yet. Note On Raspberry Pi OS, the plan records a Suta OS version, but the device keeps running Raspberry Pi OS. It only changes OS when you [move it to Suta OS](/deploy/migrate/). ## Roll it out Start a rollout of the plan you just reviewed, using the plan ID from the output, and follow it: ```sh suta deploy PLAN_ID --wait ``` The device downloads the image, starts the app, and then watches it for a probation period of one minute. When the app has stayed healthy and the device has stayed connected for that long, the device accepts the change, and `suta deploy` exits. ## Check the result 1. See what the device runs now: ```sh suta device generations kitchen-pi ``` Deploy #1 is **accepted**, and the `hello` app is running and healthy. 2. Open `http://DEVICE_ADDRESS:8080` in a browser on the same network as the device. You see the nginx welcome page. The device’s address is on its **Overview** tab in the dashboard, under **Connection**. You can see the same information on the device’s **Software** tab in the dashboard, and the rollout under the project’s **Deployments**. ## See a failed change roll back To see what happens when a change is broken, break the probe. In `suta.yaml`, change the probe path to one that doesn’t exist: ```yaml probe: http: /does-not-exist port: 80 ``` Then plan and deploy again in one step: ```sh suta deploy --device kitchen-pi --wait ``` `suta deploy` shows the plan and asks you to confirm. The device switches to a new generation as deploy #2, the probe fails, and the device puts the generation from deploy #1 back on its own. `suta deploy` exits with an error, and `suta device generations` shows deploy #2 as **rolled back** with the reason. In the dashboard, open the rollout under **Deployments**: the device’s row shows the same reason, with a link to the device’s logs around the time it failed. The device didn’t wait for Suta to tell it to roll back; it decided on its own. Change the probe back to `/` before you go on. ## Keep your configuration in a repository Commit `suta.yaml` and `suta.lock` to version control. The lock file keeps image digests fixed, so the next plan changes only what you changed in `suta.yaml`, even if a tag like `nginx:1.27` moves. ## Next steps * [Example project](/get-started/example/): serve your own page from every device, and deploy each change from CI. * [Write suta.yaml](/deploy/suta-yaml/): configuration, secrets, volumes and per-site settings. * [Plan and roll out](/deploy/rollouts/): stage a rollout across many devices. * [Deploy from CI](/ai-agents/api-tokens/) with an API token. # Example project > Serve your own web page from every device in a project, change it, roll it out in stages and deploy it from CI. In this example, every device in a project serves a web page you write. You try it on one device, roll it out to the rest, change the page, then let CI deploy each change for you. It builds on [Deploy your first app](/get-started/first-deploy/). ## Before you begin * A project with a few devices that show as **Online**. This example calls the project `my-fleet` and one of its devices `lab-pi`; use your own names. * The Suta CLI, signed in with `suta auth login`, with a role that can deploy software, such as Operator. ## Set up the project 1. Create a directory for the project, and go to it: ```sh mkdir my-fleet && cd my-fleet ``` 2. Create `index.html`, the page each device serves: index.html ```html

Hello from Suta

``` 3. Create `suta.yaml`: suta.yaml ```yaml # yaml-language-server: $schema=https://suta.dev/schema/suta.json project: my-fleet os: latest apps: web: image: nginx:1.27 ports: ["8080:80"] files: /usr/share/nginx/html/index.html: source: index.html probe: http: / port: 80 ``` `files` puts your `index.html` where nginx serves it from. The `probe` checks that the page loads; a device keeps a change only if it passes for the whole probation. ## Try it on one device 1. Plan for one device only: ```sh suta plan --device lab-pi ``` ```text + lab-pi · update to 5d1e07a9c2b4 OS 2026.09.4 Apps: + web Plan: 1 to update, 0 unchanged, 0 pinned. Plan 2b7f… Deploy it with suta deploy 2b7f… ``` 2. Deploy that plan, using the plan ID from the output: ```sh suta deploy PLAN_ID --wait ``` 3. Open `http://DEVICE_ADDRESS:8080` in a browser on the same network. You see **Hello from Suta**. The device’s address is on its **Overview** tab in the dashboard, under **Connection**. ## Roll out to every device ```sh suta deploy --wait ``` `suta deploy` shows the plan and asks you to confirm. `lab-pi` is already up to date, so it’s listed as `= unchanged`. With five or more devices, one goes first on its own; the rest follow ten at a time, each wave only after the one before has succeeded. If a device fails, the rollout pauses and that device goes back to what it ran before. See [Plan and roll out](/deploy/rollouts/). ## Change the page 1. Edit `index.html`: index.html ```html

Hello again from Suta

``` 2. Plan and deploy: ```sh suta deploy --wait ``` Each device lists `Apps: ~ web`: only the web app changes. 3. Refresh the page in your browser to see the new text. ## Deploy from CI Commit the project, including `suta.lock`, which pins the exact image the plan used: * my-fleet/ * .github/workflows/deploy.yml * index.html * suta.yaml * suta.lock Then let GitHub Actions deploy every change to `main`: 1. [Create an API token](/ai-agents/api-tokens/#create-a-token) with the Operator role, and save it as a repository secret named `SUTA_TOKEN`. 2. Add a workflow: .github/workflows/deploy.yml ```yaml name: Deploy on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install the Suta CLI run: | curl -fsSL https://suta.dev/install.sh | bash -s -- --cli echo "$HOME/.local/bin" >> "$GITHUB_PATH" - name: Deploy run: suta deploy --yes --wait env: SUTA_TOKEN: ${{ secrets.SUTA_TOKEN }} ``` Now editing `index.html` and pushing to `main` updates every device. The job fails if any device rolls back, so a broken change shows up as a failed check. ## Next steps * [Write suta.yaml](/deploy/suta-yaml/) to add secrets, volumes and settings per site. * [Set up alerts](/team/alerts/) for failed deploys and devices that go offline. # Key concepts > Workspaces, projects, devices, generations, plans and rollouts, and how Suta decides who can do what. Suta has three parts: the **Suta app** at app.suta.dev, the **Suta agent** that runs on each device, and the **Suta CLI** on your computer. People, CI jobs and AI agents all work through the app, which checks what each of them is allowed to do. ## Workspaces A workspace holds your projects and the people who manage them. Everyone in a workspace has a [role](/team/people-and-roles/) that controls what they can do. You can belong to more than one workspace and switch between them, for example to keep separate customers or teams apart. ## Projects A project is one fleet: its devices, the `suta.yaml` that describes what they run, and every plan and rollout that changed them. Use a project for each kind of device you run, for example one for your delivery drones and one for your warehouse robots. Each device belongs to exactly one project, and each project deploys on its own. See [Projects](/get-started/projects/). ## Devices When the Suta agent first starts, it creates a private key on the device. The key never leaves the device and is the device’s identity. The first workspace to claim a device owns it. The agent checks in with Suta every few seconds. A device shows as **Online** only while those check-ins arrive. If they stop for 30 seconds, it shows as **Offline**, and anything you see about it is labelled as the last value it reported. Devices connect out to Suta over HTTPS. You never need to open a port or set up a VPN to reach them. ## Generations A **generation** is everything a device runs, fixed in one version: the Suta OS version, each app’s container image and settings, the secrets it uses and its persistent volumes. Every change to a device, however small, gives it a new generation. Because a generation pins exact image digests and secret versions, you always know what a device runs, and two devices on the same generation run the same thing. ### Names you choose and names Suta makes Every deploy-related name is either yours or Suta’s, never a mix: | Name | Who makes it | Example | What it names | | ----------------------------------- | -------------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------- | | Deploy message | You, with `suta plan -m` or `suta deploy -m` | “Fix sensor calibration” | A plan and its rollout. Shown in quotes. | | App, device, site and project names | You, in `suta.yaml` or the dashboard | `web`, `kitchen-pi` | What they say | | Generation ID | Suta: the hash of the generation’s contents | `3f9a2c71b0de` | One generation, the same on every device that runs it | | Deploy number | Suta: counts the deploys to one device | deploy #7 | The 7th time this device was given a generation, by a rollout, rollback, retry or move to Suta OS | | Plan and rollout IDs | Suta | `8c41…` | A plan or rollout, for the CLI | The same settings always make the same generation ID, so many devices can share one generation. Deploy numbers belong to one device: the same generation can be deploy #3 on one device and deploy #9 on another. Select a generation’s ID anywhere in the dashboard to open [its page](/operate/dashboard/#generation-pages). Without a message, the dashboard names a rollout after what it changes, such as `web · Suta OS 2026.09.1`. ## Plans and rollouts You describe what a project’s devices should run in a file called [`suta.yaml`](/deploy/suta-yaml/). Changing devices then takes two steps: 1. A **plan** shows exactly which generation each device would get and what changes. Making a plan changes nothing on devices. 2. A **rollout** applies exactly that plan, to a few devices first and then in batches. It pauses if devices fail. This is the same idea as `terraform plan` and `apply`: you review a change, then apply the change you reviewed. ## Devices check every change A device doesn’t simply do what it’s told. When it gets a new generation, it: 1. Downloads and checks everything it needs, while the current generation keeps running. 2. Switches over, restarting only the apps that changed. 3. Watches the new generation for a **probation** period. Every app must stay healthy and the device must still reach Suta. 4. Accepts the new generation, or, if anything failed, returns to the previous one on its own. The device makes this decision itself, so a change that cuts it off from the network still gets rolled back. A power cut during an update leaves the device on the old generation or the new one, never in between. See [Plan and roll out](/deploy/rollouts/#what-each-device-does). ## Raspberry Pi OS and Suta OS **Suta OS** is a minimal operating system built for Suta. It keeps two copies of the OS on the card, so Suta can update the OS the same safe way it updates apps, and roll it back if the new version doesn’t start. A new Pi you flash with `suta flash` runs Suta OS from its first boot. A Pi that already runs Raspberry Pi OS can [install Suta alongside it](/add-devices/existing-pi/): there, Suta deploys your apps but leaves the OS alone. When you’re ready, [move that device to Suta OS](/deploy/migrate/) without reflashing it. ## Off until you turn it on Features that reach into a device start switched off: * **Monitoring** collects metrics and logs only after someone turns it on for that device. See [Monitor devices](/operate/monitoring/). * **Remote access** opens only for a time window you choose, such as one hour. Terminals and commands work only while it’s open. See [Remote access](/operate/remote-access/). ## Access is checked and recorded Every request is checked against a set of [permissions](/reference/permissions/): * **People** get permissions from their role. * **AI agents** get only the access you approve, and never more than the person who approved it. See [Connect an AI agent](/ai-agents/connect/). * **CI jobs** use an [API token](/ai-agents/api-tokens/) that takes its permissions from a role. Deploys, commands, service restarts and changes to the workspace are recorded in [Activity](/team/activity/), and terminal sessions are recorded in full. # Projects > Keep each fleet's devices, suta.yaml and deploys together, and apart from other fleets. A project is one fleet: its devices, the `suta.yaml` that describes what they run, and every plan and rollout that changed them. Use one project for each kind of device you run, such as your delivery drones and your warehouse robots, or one for each customer. * Each device belongs to exactly one project. * A plan covers only the devices of the project its `suta.yaml` names. * Each project runs one rollout at a time, and projects roll out independently. * A project has its own overview, devices, deployments, logs, alerts and activity in the dashboard. People, roles, API tokens, secrets and alert settings belong to the workspace and apply to every project in it. ## Your projects The workspace’s **Projects** page lists every project with its devices colored by health, how many are online and have failing apps, alerts that need attention and its latest rollout. Open a project to see its **Overview**: the fleet at a glance, which generations it runs, its latest deployment, what needs attention and recent activity. Every workspace starts with the project you name when you create it. ## Create a project Only admins can create projects. 1. On the workspace’s **Projects** page, select **New project**. 2. Enter a **Project name**, such as `Warehouse robots`. Suta suggests a **Slug**, such as `warehouse-robots`; change it if you like. 3. Select **Create project**. Or, from the CLI: ```sh suta project create "Warehouse robots" ``` ## Deploy to a project Name the project’s slug at the top of its `suta.yaml`: suta.yaml ```yaml project: warehouse-robots ``` `suta plan` and `suta deploy` then cover only that project’s devices. See [Write suta.yaml](/deploy/suta-yaml/). ## Add devices to a project When you add a device from a project, it joins that project. When you approve a device’s registration, or add one with `suta device add`, choose its project if the workspace has more than one. ## Save Wi-Fi networks Open the project, then **Project settings** > **Wi-Fi networks**, and select **Add network**. When you set up a device in the project, its saved networks are offered first, so you don’t type the password again. You can also save a network while setting up a device. See [Save Wi-Fi for a project](/add-devices/new-pi/#save-wi-fi-for-a-project). ## Move a device to another project Open the device, go to **Settings**, choose a project under **Project**, then select **Move device**. Plans from the new project’s `suta.yaml` can deploy to it from then on. A rollout already under way still finishes with it. ## Rename or delete a project Open the project, then **Project settings**. Admins can change its name and slug. Caution Changing the slug changes the name `suta.yaml` uses. Update `project:` in the project’s `suta.yaml`, or its next plan won’t find the project. To delete a project, first move or remove its devices. Deleting a project also deletes its plans and deployment history; its activity stays in the workspace’s timeline. A workspace always keeps at least one project. # Install on an existing Pi > Add a Raspberry Pi that already runs Raspberry Pi OS, without changing its operating system, network or services. Use this guide when your Raspberry Pi is already online and you can connect to it over SSH. The installer adds the Suta agent alongside what’s already there. It doesn’t upgrade the OS, reboot, change network settings or touch your services. If Podman isn’t installed, it installs it from Raspberry Pi OS’s own packages, so the device can run your apps. Once the device is in Suta, you can deploy apps to it. Your existing services keep running as they are; Suta only manages the apps you describe in [`suta.yaml`](/deploy/suta-yaml/). ## Before you begin * Check that the Pi meets the [requirements](/add-devices/requirements/). * Make sure you have a Suta workspace. If you don’t, create one at [app.suta.dev](https://app.suta.dev). ## Install the agent 1. Open an SSH session to the Pi, using an account that can run `sudo`. 2. Run the installer: ```sh curl -fsSL https://suta.dev/install.sh | bash ``` 3. When asked what to install, choose **Suta agent**. The installer checks the Pi, explains what it will add, then asks you to continue. 4. Open the registration link that the installer prints. Sign in if asked. 5. On the **Register your device** page, choose the device’s **Workspace**, or choose **Create a workspace…** to start a new one. Enter a **Display name**, choose the device’s **Project** if the workspace has more than one, and optionally add some tags. To compare the identity with the one shown on the Pi, expand **Check device identity**. 6. Select **Register device**. 7. Keep your SSH session open until the installer reports that the device is connected. The device now appears in its project’s **Devices**. Monitoring and remote access start off. Turn them on from the device page when you need them. Caution If the Pi already has an unrelated account or file named `suta`, the installer stops without changing anything. Rename or remove the conflicting item first. ## Update Suta on the device To update the Suta agent, run the installer again. It keeps the device’s identity, registration and settings. ```sh curl -fsSL https://suta.dev/install.sh | bash ``` To update without prompts, for example from a script, pass `--agent --yes`. `--yes` never resets registration. ```sh curl -fsSL https://suta.dev/install.sh | bash -s -- --agent --yes ``` After you [move a device to Suta OS](/deploy/migrate/), the agent is part of the OS, and you update it by deploying a newer Suta OS version instead. ## Finish an interrupted registration If registration didn’t finish, for example because you closed the browser, run: ```sh sudo suta register ``` ## Uninstall ```sh sudo suta uninstall ``` This removes the Suta software but keeps the device’s identity and settings, so you can reinstall later. It doesn’t remove the device from your workspace. To revoke its access, [remove it in the dashboard](/add-devices/remove/). # Set up a new Pi > Flash an SD card with Suta OS and connect a new Raspberry Pi from your browser or terminal. To set up a new Raspberry Pi, you flash an SD card with Suta OS, start the Pi, then connect it to your workspace from your browser or terminal. [Suta OS](/get-started/concepts/#raspberry-pi-os-and-suta-os) is a minimal operating system built for Suta, so Suta can update the OS and your apps together and roll either back. The image contains no accounts, passwords or Wi-Fi details, and SSH is turned off: to work on the device, use [remote access](/operate/remote-access/). Each device creates its own identity the first time it starts. ## Install the Suta CLI On your computer, run: ```sh curl -fsSL https://suta.dev/install.sh | bash ``` Choose **Suta CLI** when asked. If the installer prints a `PATH` hint, follow it, then open a new terminal window. To install without prompts, for example in a script, run `curl -fsSL https://suta.dev/install.sh | bash -s -- --cli`. To update the CLI later, run `suta update`. Updating keeps you signed in. ## Flash the SD card 1. Insert an SD card of 16 GB or more. 2. Run: ```sh suta flash ``` 3. Use the arrow keys to choose the card, then press `Enter`. 4. Confirm that you want to erase the card. **No** is selected by default, so move to **Yes** first. Press `Esc` to cancel at any time. `suta flash` downloads the image the first time and reuses it after that. It works with built-in and USB card readers, and unmounts the card for you if your computer opened it. It refuses to write to internal disks, your startup disk or a partition. Setting up many devices? You can clone a freshly flashed card, **as long as it has never been booted**. A card that has booted holds a device identity and possibly Wi-Fi passwords. ## Start the Pi Put the card in the Pi. Connect Ethernet if you plan to use it, then connect power. Wait a minute or two for the first boot to finish. ## Connect the device * Browser (Chrome on macOS) 1. In Chrome, open [app.suta.dev](https://app.suta.dev), open the project the device belongs to, then select **Add device** > **Set up a new device**. 2. Select **Find my device**, then choose your Pi in Chrome’s Bluetooth window. macOS might ask you to allow Bluetooth or to pair. 3. Enter a **Device name** and, optionally, tags. Check the **Project** and **Workspace**, or choose **Create a workspace…**, then select **Claim this device**. 4. Choose a connection: * **Ethernet**: select **Check connection**. * **Wi-Fi**: if the project has [saved networks](#save-wi-fi-for-a-project), choose one and select **Connect to Wi-Fi**. Otherwise choose **Another network**, select **Find networks**, choose your network, enter the password, then select **Connect to Wi-Fi**. 5. Wait for **Device connected**. Bluetooth is used only for setup. The Pi uses Ethernet or Wi-Fi for internet. * Terminal []() 1. Run: ```sh suta device add ``` If you’re not signed in, your browser opens so you can sign in. 2. The CLI looks for nearby devices over Bluetooth and on your local network over Ethernet for up to 30 seconds. Choose your Pi. 3. If your workspace has more than one project, choose the device’s project. To choose it up front, run `suta device add --project SLUG`. 4. Enter a name and optional tags, then choose Ethernet or Wi-Fi. The project’s saved networks are listed first, marked **saved**, and don’t ask for a password. 5. Wait for the CLI to confirm that the device is connected. If you typed a Wi-Fi password, the CLI asks whether to save the network for the project. On macOS, allow your terminal app under **System Settings** > **Privacy & Security** > **Bluetooth**. Ethernet discovery needs mDNS on your network. A direct cable between your computer and the Pi works, but can take a little longer to connect. Wi-Fi country You don’t need to choose a country. The Pi picks it from the Wi-Fi network you select, or keeps the one it already has. ## Save Wi-Fi for a project When you type a Wi-Fi password during setup, **Save for** *project* is selected, so the network is saved once the device joins it. The next time you set up a device in that project, choose the saved network instead of typing the password again. To add, change or remove saved networks, open the project’s **Project settings** and go to **Wi-Fi networks**. Suta stores saved passwords encrypted and never shows them again. When a device joins a saved network, Suta encrypts the password so that only that device can read it, for that network only; your browser or terminal passes it on without being able to read it. Saving networks needs the **Configure networking** permission. See [Roles and permissions](/reference/permissions/). ## If setup stops partway **Waiting for connection** means the device is registered but hasn’t reached Suta yet. Check its network, or select **Check network settings**. You can also select **Finish later**: the device keeps its registration. If you reflashed a card, your computer might remember an old Bluetooth pairing. Forget the device in your Bluetooth settings and try again. For more fixes, see [Troubleshooting](/troubleshooting/). ## Next steps * [Deploy your first app](/get-started/first-deploy/) to the device. # Remove or re-add a device > Revoke a device's access, and add it back later with a new identity. ## Remove a device 1. Open the device and go to the **Settings** tab. 2. Select **Remove device**, then **Remove and revoke access**. The device disappears from your device list and can no longer contact Suta. Its software and data stay on the Pi; Suta doesn’t erase it. To see removed devices, select **Removed devices** on the project’s **Devices** page. Caution A removed device’s identity can’t be added again. To bring it back, give it a new identity using one of the options below. ## Add a removed device again * **Pi with an existing OS**: in an SSH session on the device, run the installer and answer `y` when asked **Reinstall as a new device?** ```sh curl -fsSL https://suta.dev/install.sh | bash ``` Then open the registration link to approve the new identity. Remote access and monitoring start off again. * **Pi flashed with Suta OS**: reflash the SD card and [set it up again](/add-devices/new-pi/). ## Reflashing a device you didn’t remove Reflashing always creates a new identity. The old entry stays in your device list, offline, until you remove it. # Fix a device's network > Change Wi-Fi or Ethernet settings on a device that can't get online. If a device running Suta OS can’t reach the internet, for example after a Wi-Fi password change, you can fix it from a computer nearby. The device keeps its owner and identity throughout. Note This guide is for devices running Suta OS. A Pi where you [installed Suta on an existing system](/add-devices/existing-pi/) keeps its own network tools; change its network settings the way you normally would. ## Before you begin * You need the **Configure networking** permission in the workspace that owns the device. See [Roles and permissions](/reference/permissions/). * Your computer needs internet access, and must be close enough for Bluetooth or on the same local network as the device. ## Repair the network 1. On the device, open a 15-minute repair window. Use a keyboard and screen, or an SSH session if one still works: ```sh sudo suta setup ``` 2. On your computer, run: ```sh suta device network kitchen-pi ``` Replace `kitchen-pi` with the device’s name or ID. 3. Choose the device when the CLI finds it, then update its Wi-Fi or Ethernet settings. Networks saved in the device’s project are listed first and don’t ask for a password. The CLI checks that the device it finds has the key it expects, so it never changes the wrong device. If a new Wi-Fi setting fails, the device goes back to its previous connection. If the Wi-Fi password changed, update it in the project’s saved networks too, under **Project settings** > **Wi-Fi networks**. Devices that are already connected keep the password they have. ## Things that don’t affect ownership Rebooting, entering a wrong Wi-Fi password and unplugging Ethernet all leave the device’s owner unchanged. Only reflashing the SD card creates a new identity. If the repair permission expires before you finish, run `suta device network` again to get a fresh one. # Requirements > Supported Raspberry Pi models, operating systems, computers and networks. ## Devices Suta supports any Raspberry Pi with a 64-bit processor, such as the Pi 5, Pi 4, Pi 3 and Pi Zero 2 W. How you add a device depends on whether it’s already set up: | | New Pi | Pi that’s already running | | ---------------- | ------------------------------------------------------------ | ------------------------------------------------------ | | Guide | [Set up a new Pi](/add-devices/new-pi/) | [Install on an existing Pi](/add-devices/existing-pi/) | | Operating system | Supplied by Suta: Suta OS | 64-bit Raspberry Pi OS, Bookworm or Trixie | | Storage | SD card of 16 GB or more | Writable root file system | | Setup | Bluetooth from Chrome, or Bluetooth or Ethernet from the CLI | SSH with `sudo` | | Internet | Ethernet, or Wi-Fi (WPA personal or open) | Already online | Enterprise Wi-Fi and networks with a captive portal aren’t supported. An existing Pi also needs these system libraries, which Raspberry Pi OS normally includes: `libdbus-1-3`, `libsystemd0`, `libssl3` (or `libssl3t64`) and `libyaml-0-2`. The installer tells you if any are missing. It never upgrades your operating system. ## Deploying apps Suta runs your apps as containers, so each app’s image must include `linux/arm64`. * **Suta OS** includes everything apps need. * **Raspberry Pi OS** needs Trixie. The installer adds Podman, which runs your apps, from Raspberry Pi OS’s own packages. The Podman version on Bookworm is too old, so move a Bookworm device to Suta OS instead. ## Suta OS A new Pi runs Suta OS from its first boot, on every supported model. You can also [move an existing Pi to Suta OS](/deploy/migrate/) from Raspberry Pi OS on a Pi 5, Pi 4, Pi 500, Pi 400, Compute Module 5 or Compute Module 4. The Pi 3 and Pi Zero 2 W can’t move to Suta OS yet. ## Your computer | | Supported | | ----------------------------------- | ---------------------------------------------------------------------------------------- | | Suta CLI | macOS (Apple silicon and Intel); Linux (ARM64 and x86-64, such as Ubuntu 24.04 or later) | | Setup over Bluetooth in the browser | Chrome on macOS | | Dashboard | Any current browser | On macOS, allow your terminal app to use Bluetooth in **System Settings** > **Privacy & Security** > **Bluetooth** before you run `suta device add`. ## Network Devices connect out to Suta over HTTPS. You don’t need to open any inbound ports or set up a VPN. To pull app images, devices also need to reach your container registries. Setting up a device from the terminal over Ethernet uses mDNS to find it on your local network. # Manage agent access > Review, edit and revoke what connected applications can do. Go to **Settings** > **Connected apps** to see every connected application and what it can do. ## Edit access 1. Find the application and select **Edit access**. 2. Change the devices or permissions. 3. Under **Access duration**, choose **Until revoked** or **Until a chosen time**. 4. Select **Save access**. Changes apply to the agent’s next request. Any terminal sessions it had open are closed. ## Revoke access Select **Revoke**, then **Revoke access**. The application loses access immediately. To use it again, it must connect and be approved again. ## How limits combine An application can never do more than the person who approved it. If that person’s role changes, or they leave the workspace, the application’s access shrinks or ends to match. Remote access is a separate control. Even with **Run commands**, an agent can run commands only while [remote access](/operate/remote-access/) is open on that device. Caution Treat device output as data, not instructions. A well-behaved agent never asks for more access because something on a device told it to. # API tokens for CI > Let CI jobs and scripts use Suta without a person's login. An API token lets a CI job or script use Suta on its own. It belongs to one workspace and gets its permissions from a [role](/team/people-and-roles/). ## Create a token You need to be an admin. 1. Go to **Settings** > **API tokens** and select **Create token**. 2. Enter a **Name**, such as `GitHub Actions`, and choose a **Role**. Pick the role with the least access the job needs; **Viewer** is the default. 3. Choose when it **Expires**: in 30 days, 90 days or 1 year. **Never** is possible but not recommended. 4. Create the token, then select **Copy token**. Suta shows the token only once, so store it in your CI secrets straight away. ## Use the token Set `SUTA_TOKEN` and run the [CLI](/reference/cli/) as usual: ```sh SUTA_TOKEN="$SUTA_CI_TOKEN" suta device list --json ``` To deploy from a release pipeline, plan `suta.yaml` and roll it out in one step, then wait. The command fails if the rollout pauses, is cancelled or any device fails: ```sh SUTA_TOKEN="$SUTA_CI_TOKEN" suta deploy --tag site-london --yes --wait ``` Commit `suta.lock` so the pipeline deploys exactly the image digests, OS version and secret versions you reviewed; `suta plan --update` moves them. On GitHub, the [Suta action](/ai-agents/github-actions/) does this for you and comments the plan on each pull request. The token replaces any saved login for that command and always uses its own workspace. While it’s set, `suta auth login`, `suta auth logout` and `suta workspace use` are refused. To call the API directly, send the token as a bearer token: ```sh curl -H "Authorization: Bearer $SUTA_CI_TOKEN" https://app.suta.dev/v1/organizations ``` ## What a token can do A token can do what its role allows, for these tasks only: * List and read devices, rename and tag them, and remove them. * Read monitoring data and logs, and change monitoring settings. * List reported services. * Create and read plans, start and control [rollouts](/deploy/rollouts/), and roll back, retry or pin a device. * Read activity, operation history and alerts. Tokens can’t open terminals, run commands, open remote access, or change settings, people, roles, webhooks, secrets or other tokens. Those need a person. Everything a token does is recorded in [Activity](/team/activity/) under the token’s name. ## Change or revoke a token Editing the token’s role changes what it can do straight away. To stop a token, find it in **Settings** > **API tokens**, select **Revoke**, then **Revoke token**. It stops working on its next request. Tip Use one token per job or repository, so you can revoke one without breaking the others. # Connect an AI agent > Let Claude, ChatGPT or another MCP client work with your devices, with access you approve. Suta has a remote [MCP](https://modelcontextprotocol.io) server. Connect an AI agent to it and the agent can list devices, read logs and metrics, and, if you allow it, run commands or deploy software. The agent gets only the access you approve, and never more than you have yourself. **MCP server URL** ```text https://app.suta.dev/mcp ``` ## Connect * Claude 1. In Claude, open **Settings** > **Connectors** and add a custom connector. 2. Paste `https://app.suta.dev/mcp` as the URL, then connect. 3. Sign in to Suta when asked. * ChatGPT 1. In ChatGPT, add a custom connector (app) that uses a remote MCP server. 2. Paste `https://app.suta.dev/mcp` as the URL, then connect. 3. Sign in to Suta when asked. * Other MCP clients Point your client at `https://app.suta.dev/mcp`. It uses Streamable HTTP and OAuth, and supports standard OAuth discovery at `/.well-known/oauth-protected-resource`. A new connection can only **View device details**. The agent asks for anything more when it needs it. ## Approve more access When the agent needs more, for example to read logs or run a command, it asks for access and gives you a link. 1. Open the link. It shows what the agent is asking for and why. 2. Adjust the devices and permissions if you want to give less. 3. Under **Keep access for**, choose **Until revoked** or a time limit from 15 minutes to 7 days. When a time limit ends, the agent stays connected but can only see your devices until it asks again. 4. Select **Approve access**, or **Decline request**. 5. Tell the agent you’ve approved it. It picks up the change on the same connection; you don’t need to reconnect. Start small Read-only access is a good start. Add **Run commands** or **Run commands with sudo** only for the task at hand, and remember that remote access still needs to be open on the device. ## What agents can do | Tool | What it does | Permission needed | | ------------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------- | | `list_devices`, `device_details`, `access_status` | Find devices, read their details and check remote access | View device details | | `app_details` | A combined view of one service: device, service and optional logs | View device details, plus each section’s own permission | | `read_metrics`, `processes` | Read metrics and process usage | Read metrics | | `read_logs` | Read service logs | Read logs | | `list_services` | See the services a device reports | View services | | `restart_service` | Restart one service | Restart services | | `monitoring_settings`, `configure_monitoring` | Read or change monitoring | Change monitoring settings | | `open_access`, `close_access` | Open or close the remote access window | Manage remote access | | `exec_command` | Run a command without sudo | Run commands | | `sudo_command` | Run a command as root | Run commands with sudo | | `read_audit` | Read operation history | Read activity and recordings | | `device_software` | See a device’s generation, OS, apps and deploy history | View device details | | `device_config` | Read the `suta.yaml` and `suta.lock` behind a device’s generation | View device details | | `rollback_device` | Return a device to an earlier generation | Deploy software | | `list_projects` | See the projects in the workspace and their slugs | View device details | | `list_alerts` | See open and resolved alerts | View device details | | `list_rollouts`, `rollout_details` | Follow rollouts and see each device’s stage and result | View device details | | `plan_deploy` | Preview what a `suta.yaml` spec changes on each device | Deploy software | | `start_rollout`, `control_rollout` | Roll out a reviewed plan; pause, resume, retry or cancel a rollout | Deploy software | | `permissions`, `request_access`, `agent_guide` | Check and request access, read the guide | None | Agents can’t add devices, change network settings, manage secrets or move a device to Suta OS. Those stay with people. The full guide that agents read is at [app.suta.dev/mcp/agent-guide.md](https://app.suta.dev/mcp/agent-guide.md). ## Your agent’s app settings are separate Your AI client may ask before using a tool. That’s the client’s own setting. Suta decides what the agent **may** do; your client decides when to ask you first. # Deploy from GitHub Actions > Comment the plan on every pull request and deploy it when the pull request merges. The Suta action plans your `suta.yaml` on each pull request and comments the plan, so reviewers see which devices change before anyone merges. After the merge, it deploys exactly what was reviewed and waits for the rollout to finish. ## Before you begin * Commit `suta.yaml` and the `suta.lock` that `suta plan` writes beside it. * Create an [API token](/ai-agents/api-tokens/) with the **Operator** role or another role that can deploy. * In your repository, go to **Settings** > **Secrets and variables** > **Actions** and add the token as a secret named `SUTA_TOKEN`. ## Add the workflow 1. Create `.github/workflows/suta.yml`: ```yaml name: Suta on: pull_request: paths: [suta.yaml, suta.lock] push: branches: [main] paths: [suta.yaml, suta.lock] concurrency: group: suta-${{ github.ref }} jobs: plan: if: github.event_name == 'pull_request' runs-on: ubuntu-latest permissions: contents: read pull-requests: write steps: - uses: actions/checkout@v4 - uses: suta-dev/suta/action@main with: command: plan token: ${{ secrets.SUTA_TOKEN }} deploy: if: github.event_name == 'push' runs-on: ubuntu-latest permissions: contents: read steps: - uses: actions/checkout@v4 - uses: suta-dev/suta/action@main with: command: deploy token: ${{ secrets.SUTA_TOKEN }} message: ${{ github.event.head_commit.message }} ``` 2. Open a pull request that changes `suta.yaml`. The **plan** job comments the plan on the pull request and updates the same comment on every push. 3. Merge the pull request. The **deploy** job plans again from the committed `suta.lock`, starts the rollout and waits. It fails if the rollout pauses, is cancelled or any device fails, so the failed run shows on the commit. Both jobs fail when `suta.lock` is missing or out of date, because the deploy would then pin images or versions nobody reviewed. Run `suta plan` locally and commit `suta.lock` to fix it. To move to newer images, run `suta plan --update` and commit the new lock in the pull request. Caution GitHub doesn’t give secrets to pull requests from forks, so the **plan** job can’t plan them. Don’t switch to `pull_request_target` to work around this: it would let untrusted changes use your token. ## Inputs | Input | Default | Description | | -------------- | --------------- | ------------------------------------------------------------------------------------------------------------------ | | `command` | required | `plan` comments the plan on the pull request. `deploy` plans and rolls out, then waits. | | `token` | required | The Suta API token. | | `file` | `suta.yaml` | The `suta.yaml` to plan. Its `suta.lock` sits beside it. | | `tags` | all | Only devices with these tags, one per line. | | `sites` | all | Only devices in these sites, one per line. | | `devices` | all | Only these device names or IDs, one per line. | | `message` | none | What the deploy is for. It names the plan and its rollout. Only the first line is used, so a commit message works. | | `canary` | suits the fleet | Devices to update first, from 0 to 20. | | `batch` | suits the fleet | Devices to update at a time after the canary, from 1 to 500. | | `max-failures` | `0` | Failures to allow before the rollout pauses, from 0 to 100. | | `github-token` | `github.token` | Posts the plan comment. It needs `pull-requests: write`. | The action sets the `plan-id` output to the plan it commented or deployed. Follow a rollout with `suta rollout list` or in the dashboard under **Deployments**. To deploy several projects from one repository, run the action once per `suta.yaml` with `file`. Each file gets its own comment. # Roll back, retry and pin > See what a device runs and the suta.yaml behind it, follow a deploy, and roll back, retry or pin one device. Everything a device runs is one [generation](/get-started/concepts/#generations). Each time a device is given a generation is a numbered **deploy** on that device: deploy #1, #2 and so on. To see a device’s deploys, open the device and select the **Software** tab, or run `suta device generations DEVICE`. Rollouts change many devices at once from `suta.yaml`. See [Plan and roll out](/deploy/rollouts/). This page covers changes to one device at a time. ## What’s on the Software tab * **Running now**: the accepted generation’s ID and deploy number, the Suta OS version, the board, the device’s site and the agent version. * **Deploying**: while a new generation is on its way, its progress through **Preparing**, **Activating**, **Rebooting** (only when the OS changes), **Probation** and **Committing**. If the new generation fails its checks, the device shows **Restoring** and puts the previous one back on its own. * **Apps**: each app’s state, restarts, health check result and the last error. * **Configuration**: the `suta.yaml` and `suta.lock` behind the running generation. See [See a device’s suta.yaml](#see-a-devices-sutayaml). * **History**: every deploy to the device, newest first, with its result, how it was assigned (a rollout, a rollback, a retry or a move to Suta OS), who assigned it, when, why it failed, and a summary of what changed. Select a generation’s ID to open [its page](/operate/dashboard/#generation-pages): its full contents and every device given it. ## See a device’s suta.yaml **Configuration** on the **Software** tab shows the `suta.yaml` that produced the generation the device runs, as it was planned, with who planned it and a link to the rollout that applied it. Select **suta.lock** for the image digests, OS version and secret versions that plan pinned. **Copy** and **Download** give you either file as is. To see an older generation’s `suta.yaml`, select **suta.yaml** on it in **History**. From the CLI: ```sh suta device config DEVICE # the running generation's suta.yaml suta device config DEVICE 5 # deploy #5's suta device config DEVICE --lock # its suta.lock suta device config DEVICE > suta.yaml # save it to plan from ``` The output is valid YAML: the lines about the device and plan are comments, so you can save it and deploy it again, or run `suta device explain DEVICE -f suta.yaml` against it. A few things to know: * Files you wrote with `source:` appear inline as `content:`, because Suta receives their content, not the file. * Comments and formatting from your original file aren’t kept. The settings are the same. * A rollback or retry reuses a generation, so it shows the `suta.yaml` of the plan that generation came from, not a new one. * The file covers the whole project: its `sites:` and `devices:` overrides for other devices too. Use `suta device explain` to see which values apply to this device. Anyone who can view the device can read its `suta.yaml`. Secret values are never part of it, only secret names. ## Compare generations To see exactly what changed between two generations, select **Changes since deploy #N** on a deploy in **History**. It lists changes to images, environment variables, secret versions, files, ports, volumes and health checks. Secret values are never shown. From the CLI: ```sh suta device diff DEVICE # the previous generation against the current one suta device diff DEVICE 5 8 # the generations of deploys #5 and #8 ``` ## Roll back Select **Roll back** to return to the accepted generation before the current one. The dialog names its deploy number. To go further back, select **Roll back to this** on an older accepted generation in **History**. From the CLI: ```sh suta rollback DEVICE suta rollback DEVICE --to 5 ``` A rollback is a new deploy with a new number. The device checks it during probation like any other generation, and keeps the current one if the older one fails. ## Retry a failed generation A device won’t try a generation again after it failed or was rolled back. Select **Retry** on that generation in **History** to assign it once more, for example after fixing a database it depends on. From the CLI, run `suta device retry DEVICE NUMBER`. ## Pin a device Select **Pin** to hold a device at its current generation, with an optional reason. Plans list a pinned device as **Pinned** and rollouts skip it until someone selects **Unpin**. You can still roll back a pinned device. From the CLI: ```sh suta device pin DEVICE --reason "Trade show demo" suta device unpin DEVICE ``` Note Rolling back, retrying and pinning need the **Deploy software** permission. Without it, the Software tab is read-only. ## When a deploy fails A failed or rolled-back generation shows its reason in **History**, and the **Deploy failed** condition can open an [alert](/team/alerts/). The device keeps running its previous generation. # Move a device to Suta OS > Replace Raspberry Pi OS on a device with Suta OS, without reflashing, and fall back to Raspberry Pi OS if it fails. On Raspberry Pi OS, Suta deploys your apps but leaves the operating system alone. Moving a device to Suta OS lets later deploys update the OS too, and roll it back if a new version doesn’t start. The move happens on the same SD card, over the network, so you don’t need to visit the device. ## Before you begin * The device is a Pi 5, Pi 4, Pi 500, Pi 400, Compute Module 5 or Compute Module 4. The Pi 3 and Pi Zero 2 W can’t move to Suta OS yet. * The device runs a generation from [`suta deploy`](/deploy/rollouts/). The move installs the Suta OS version that generation names. If you haven’t deployed to the device yet, deploy a `suta.yaml` that contains only `os: latest`. * You have the **Deploy software** permission. * Move any services you still need from Raspberry Pi OS into containers in `suta.yaml`. See [What carries over](#what-carries-over). The device checks that it has room for Suta OS before it downloads anything, and reports if there isn’t enough. ## Move the device 1. Run: ```sh suta migrate DEVICE ``` Or open the device’s **Software** tab and select **Migrate to Suta OS**. 2. Follow the progress: ```sh suta device generations DEVICE ``` The device downloads Suta OS and prepares it, then restarts into it once as a trial. 3. If your apps stay healthy and the device keeps reaching Suta for the whole probation, the device accepts Suta OS and starts it from then on. If Suta OS doesn’t start, your apps fail or the device can’t reach Suta, the device restarts into Raspberry Pi OS and reports the move as rolled back, with the reason. Fix the cause and run `suta migrate` again. ## What carries over * The device’s identity, name, tags and site. * Your apps, their volumes and their secrets. * Wi-Fi networks you saved on Raspberry Pi OS. * Hardware settings, such as the overlays for a display or HAT. Nothing else from Raspberry Pi OS runs on Suta OS: packages you installed, your own services, cron jobs and files in home directories stay behind. ## Go back to Raspberry Pi OS Raspberry Pi OS stays on the card after a successful move. To start it again, mount the card’s boot partition on another computer and copy `config-raspios.txt` over `config.txt`. # Plan and roll out > Preview exactly what changes on each device, then roll it out in stages that pause on failure. Deploying is two steps, like `terraform plan` and `apply`: you make a **plan** that shows exactly what each device will get, then start a **rollout** that applies exactly that plan, a few devices at a time. ## Make a plan 1. In the directory with your [`suta.yaml`](/deploy/suta-yaml/), run: ```sh suta plan -m "Fix sensor calibration" ``` `-m` is optional: one line saying what the deploy is for. It becomes the name of the plan and its rollout in the dashboard and CLI, shown in quotes as your words. Without it, a rollout is named after what it changes. In CI, you can pass the commit subject: `-m "$(git log -1 --format=%s)"`. To plan for some devices only, add `--device NAME`, `--tag TAG` or `--site SITE`. Tags must all match; devices and sites may match any. 2. Review the output. Each device is listed as: * `+ update`: it gets a new generation. The lines below say what changes: the Suta OS version and [hardware settings](/deploy/suta-yaml/#hardware-settings) (and whether the device restarts), apps added, changed or removed, volumes and probation. * `= unchanged`: it already runs exactly this generation. * `! pinned`: it’s [pinned](/deploy/device-software/#pin-a-device) and keeps its generation. Below the totals, the plan names every device that will restart. Their apps stop until each device is back. 3. Commit `suta.lock`, which the plan updated. Plans don’t change anything on devices, and cover only the devices of the project named in `suta.yaml`. You can also review a plan in the project’s **Deployments**: plans waiting for a rollout appear under **Ready to deploy**, and every plan is listed under **Plans**. ## Start a rollout Start a rollout of a plan from the CLI: ```sh suta deploy PLAN_ID --wait ``` Or open the plan in the project’s **Deployments** and select **Start rollout**. Without a plan ID, `suta deploy` makes a plan, shows it and asks before starting. When the plan restarts devices, the question says how many and defaults to no; in the dashboard, you tick the restart notice before **Deploy** is available. | Option | Default | Description | | ---------------- | ----------------- | --------------------------------------------------------------- | | `--canary` | Sized to the plan | Devices that deploy first, alone, 0 to 20. | | `--batch` | Sized to the plan | Devices that deploy at a time after the canary, 1 to 500. | | `--max-failures` | `0` | Failures to allow before the rollout pauses, 0 to 100. | | `--wait` | | Follow the rollout; exit with `1` unless every device succeeds. | ### Choose stages Unless you choose otherwise, Suta sizes the stages to how many devices the plan updates: | Devices updating | Stages | | ---------------- | ------------------------------------------------------------------------------------------ | | 1 | The device deploys on its own. | | 2 to 9 | One device first, then the rest together. | | 10 or more | About 5% first (1 to 20 devices), then the rest in up to four even batches of at most 500. | In the web app, **Start rollout** offers this as the recommended choice, beside **All at once** and **Custom**, and shows each stage before you start. With a single device there is nothing to choose. Each stage starts only when every device in the previous one accepted its new generation. Each project runs one rollout at a time; rollouts in different projects run side by side. ## What each device does Each device first checks that the change really comes from Suta. Then: 1. **Preparing**: downloads the Suta OS image if it changes, pulls every container image and fetches its secrets. Nothing running changes yet. 2. **Activating**: restarts only the apps that changed. A new Suta OS version or new hardware settings restart the device into them instead. 3. **Probation**: every app must stay running and pass its probe, and the device must reach Suta, for the probation time. 4. **Committing**: the device accepts the generation. If anything fails, the device restores the previous generation on its own, without waiting for Suta, and won’t try the failed one again unless you [retry it](/deploy/device-software/#retry-a-failed-generation). A power cut or a hang during the first boot of a new OS or new hardware settings also returns the device to the previous ones. ## When devices are skipped A rollout skips a device, rather than guessing, when: | Reason | Meaning | | -------------------- | ---------------------------------------------------------------------------------------- | | `offline` | It hadn’t checked in for two minutes when its wave started. **Retry** includes it again. | | `pinned` | It was pinned after the plan was made. | | `changed_since_plan` | Its software changed after the plan was made. Make a new plan. | | `removed` | It was removed from the workspace. | ## Follow a rollout and see why a device failed A project’s **Deployments** lists every rollout, newest first, with what it shipped, how many devices took it, who started it and how long it took. Filter by status or search for an app, a person or an ID. Open a rollout to see: * **Devices**: each device’s state. A failed device shows the reason it reported, with links to **Logs around failure** and its **Device software**. * **Timeline**: when each stage started and the outcome each device reported, in order. * **What it ships**: each generation the rollout assigns, with its apps and settings. Select a generation’s ID to open [its page](/operate/dashboard/#generation-pages). Failed and rolled-back deploys also appear in [Activity](/team/activity/) with the device’s reason. ## Pause, resume and cancel A rollout pauses by itself when more devices fail than `--max-failures` allows, or when a device hasn’t finished within 60 minutes. Then you can: * **Resume** to continue, accepting the failures so far. * **Retry** to include failed and offline devices again. * **Cancel** to stop starting new devices. Devices already deploying finish on their own. ```sh suta rollout show suta rollout resume ``` Tip In CI, use an [API token](/ai-agents/api-tokens/) with the **Deploy software** permission and run `suta deploy --yes --wait` after your images are pushed. ## Roll back To return one device to its previous generation: ```sh suta rollback DEVICE ``` To roll back many devices, check out the previous `suta.yaml` and `suta.lock` from your repository, then plan and roll them out. See [Roll back, retry and pin](/deploy/device-software/) for more. # Write suta.yaml > Describe the apps, configuration, secrets, hardware settings and Suta OS version your devices run. `suta.yaml` describes what the devices in one project run: containers, their settings, secrets, persistent data, hardware settings and the Suta OS version. Keep it in a repository next to your code. When you plan, Suta turns it into one **generation** per device in the project, pinning every image by digest so each device runs exactly what you reviewed. ```yaml # yaml-language-server: $schema=https://suta.dev/schema/suta.json project: drone # the project this file deploys os: latest # or an exact Suta OS version, such as 2026.09.1 probation: 2m # how long a change must stay healthy hardware: i2c: true # for the sensor's /dev/i2c-1; changing it restarts devices apps: web: image: ghcr.io/acme/web:1.4 ports: ["80:8080"] env: LOG_LEVEL: info secrets: - name: db-password env: DB_PASSWORD files: /etc/web/config.toml: source: config/web.toml volumes: /var/lib/web: web-data probe: http: /health port: 8080 sensor: image: ghcr.io/acme/sensor:2.0 devices: [/dev/i2c-1] network: host probe: exec: [sensor, --check] registries: ghcr.io: username: acme-ci password: ghcr-token # the name of a Suta secret sites: london: apps: web: env: {REGION: eu-west} devices: lab-pi: probation: 30s apps: web: image: ghcr.io/acme/web:1.5-rc ``` The first line lets editors with YAML language support, such as VS Code with the Red Hat YAML extension, complete settings and flag mistakes as you type using the [`suta.yaml` schema](https://suta.dev/schema/suta.json). `suta plan` still checks what the schema can’t, such as whether images and secrets exist. ## Top-level settings | Setting | Default | Description | | ------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------- | | `project` | required | The slug of the project to deploy, shown in the dashboard under the project’s name. Plans cover only that project’s devices. | | `os` | `latest` | Suta OS version. `latest` is pinned to the newest release when you first plan. | | `probation` | `60s` | How long a change must stay healthy before a device accepts it: `90s`, `2m`, `1h` or seconds, from 10 seconds to 1 hour. | | `hardware` | all off | Interfaces and overlays the device starts with. See [Hardware settings](#hardware-settings). | | `apps` | none | The containers to run, by name. | | `registries` | none | Credentials for private registries, by registry host. | | `sites` | none | Overrides for devices in a site. | | `devices` | none | Overrides for one device, by device name. | App, site, secret and volume names use lowercase letters, digits and hyphens, up to 40 characters. ## Apps | Setting | Description | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `image` | A container image, by tag (`nginx:1.27`, `ghcr.io/acme/web:1.4`) or digest. Images must include `linux/arm64`. | | `command` | Replaces the image’s command, as a list. | | `env` | Environment variables. | | `secrets` | Secrets by name. A plain name mounts the value as the file `/run/secrets/NAME`; `{name, env}` sets an environment variable instead. `name@3` pins version 3. | | `files` | Read-only configuration files by container path, with `source` (a path relative to `suta.yaml`) or `content`, and an optional octal `mode` such as `"0600"`. Up to 64 KiB each. | | `ports` | `HOST:CONTAINER`, or `HOST:CONTAINER/udp`. | | `volumes` | Container path to volume name. Add `:ro` for read-only. | | `devices` | Host device nodes, such as `/dev/i2c-1` or `/dev/ttyUSB0`. | | `network` | `bridge` (default, with published ports) or `host`. | | `privileged` | `true` gives the container full access to the device. Use sparingly. | | `probe` | The health check used during probation: `http: /path` with `port`, `tcp: PORT` or `exec: [command]`. | | `enabled` | `false` leaves the app out unless a site or device override enables it. | Without a probe, an app is healthy when its container keeps running. An app that exits and restarts three times fails its probation. ## Hardware settings Some hardware is off until the device starts with it turned on: SPI for an e-ink display, I2C for a sensor, or the device-tree overlay a HAT’s instructions tell you to add to `config.txt`. Declare these under `hardware`, next to the apps that use them: ```yaml hardware: spi: true overlays: - hifiberry-dac - gpio-fan,gpiopin=14,temp=60000 apps: frame: image: ghcr.io/acme/frame:1.0 devices: [/dev/spidev0.0, /dev/gpiochip0] ``` | Setting | Description | | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `spi` | `true` turns on SPI: `/dev/spidev0.0` and `/dev/spidev0.1`. | | `i2c` | `true` turns on I2C on the GPIO header: `/dev/i2c-1`. | | `overlays` | Device-tree overlays in the order they load. Write each one as it appears after `dtoverlay=` in the HAT’s instructions: its name, then any parameters. It must be an overlay that Suta OS includes. | The device reads these settings only when it starts, so changing them restarts it, and the plan says so. Each device tries the new settings the same way it tries a new Suta OS version: it starts with them once, and keeps them only if your apps stay healthy and it keeps reaching Suta for the whole probation. It also checks that `/dev/spidev0.0` or `/dev/i2c-1` appears when you turn on SPI or I2C. Otherwise it restarts with its previous settings and reports why. A device refuses an overlay that its Suta OS doesn’t include before it changes anything. If an app lists `/dev/spidev0.0`, `/dev/spidev0.1` or `/dev/i2c-1` under `devices` but the setting that creates it is off, the plan warns you. Hardware settings apply to devices that run Suta OS. A device on Raspberry Pi OS keeps its own `config.txt` until you [move it to Suta OS](/deploy/migrate/); the plan warns you when that’s the case. A device that moved to Suta OS also keeps the hardware settings it had on Raspberry Pi OS. Caution On the Pi Zero 2 W and Pi 3, the device can’t undo a setting that stops the kernel before Suta OS starts; you’d need to fix its SD card by hand. Try a new overlay on one device first, with a rollout canary or `--device`. ## Volumes A volume is a directory under `/data/volumes` on the device. Volumes keep their data across generations and are never rolled back, so a new version of your app must still work with the data an older version wrote, and the other way round. ## Secrets A workspace admin creates secrets with the CLI or in **Settings** > **Secrets**. Then refer to them by name in `suta.yaml`: ```sh suta secret set db-password ``` A plan pins the latest version of each secret it uses. Setting a new value creates a new version; devices get it with your next plan and rollout. Secret values are encrypted in Suta and only sent to devices whose generation names them. ## Sites and devices Give a device a site with `suta device site DEVICE SITE` or on its **Settings** tab. A plan resolves each device’s settings in this order: 1. The top level of `suta.yaml`. 2. The device’s site under `sites`. 3. The device’s own entry under `devices`. Overrides can set `os`, `probation` and `hardware`, and for existing apps `image`, `command`, `env`, `files` and `enabled`. Set an environment variable or file to `null` to remove it. A `hardware` override changes only the settings it names; its `overlays` replace the whole list. To see where each value comes from, run: ```sh suta device explain lab-pi apps.web ``` ## suta.lock `suta plan` writes `suta.lock` next to `suta.yaml`. It records the image digests, Suta OS version and secret versions the plan used. Commit it: the next plan keeps those pins, so an image tag that moves doesn’t change your devices by surprise. Run `suta plan --update` to pick up new images, OS releases and secret versions. Caution Every app your devices run comes from `suta.yaml`. A device runs exactly the apps in its generation and stops any others that Suta started earlier. # Use the dashboard > Find your way around workspaces and projects, and find, filter and organize devices. ## Workspace and project pages The bar at the top shows where you are: your workspace, then the project you’re in, if any. Select either name to switch to another workspace or project, create one, or open **Workspace settings**. The bell beside search counts alerts that need attention; select it to see them. See [Alerts](/team/alerts/#see-alerts). On workspace pages, tabs below the bar take you to: | Tab | What’s there | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | | **Projects** | Every project, with its devices colored by health, alerts that need attention and latest rollout, beside the workspace’s recent activity | | **Activity** | What changed in every project, and every command run on a device. See [Activity](/team/activity/) | | **Alerts** | Open and past alerts in every project, and the conditions and notifications that apply to all of them. See [Alerts](/team/alerts/) | | **Settings** | People, roles, API tokens, connected apps, secrets and remote access for the whole workspace | The workspace summary counts **New alerts**. Reported app failures also appear on each project card and under the project’s **Needs attention**. In a project, a sidebar with the project’s name takes you to: | Section | What’s there | | -------------------- | ----------------------------------------------------------------------------------------------------------------- | | **Overview** | The fleet at a glance, which generations it runs, the latest deployment, what needs attention and recent activity | | **Devices** | The project’s devices. See [The device list](#the-device-list) | | **Deployments** | Plans and rollouts. See [Plan and roll out](/deploy/rollouts/) | | **Logs** | Service logs from every device in the project. See [Monitor devices](/operate/monitoring/#search-a-projects-logs) | | **Alerts** | Open and past alerts for the project’s devices | | **Activity** | What changed in the project | | **Project settings** | Its name, its slug, its saved **Wi-Fi networks** and **Delete project**. See [Projects](/get-started/projects/) | **All projects** and **Workspace settings** at the bottom of the sidebar take you back to the workspace. A new project’s **Overview** walks you through adding a device, writing its `suta.yaml` and deploying. The project overview keeps a reported app failure under **Needs attention** when its device goes offline, labelled as the state at last contact. To find anything from anywhere, press `Cmd`+`K` (macOS) or `Ctrl`+`K` and search for projects, devices, sections, people or activity. ## The device list A project’s **Devices** starts with a strip of cells, one per device, colored by its health: failing apps, offline, past a resource limit, deploying, healthy, or not connected yet. Hover over a cell to see the device and its state, and select it to open the device. The list below shows each device’s status, network, resources, software and when it was last seen. **Software** shows the device’s generation ID and Suta OS version, how many apps it runs and how many are failing, a pin icon if it’s [pinned](/deploy/device-software/#pin-a-device), and **Deploying** while a new generation is on its way. Select a row to open that device. To narrow the list: * Type in the filter box to match a name, ID, tag, site or network. * Choose a status, a tag or a site. Besides online and offline, statuses include **Needs attention**, **Apps failing**, **Deploying** and **Pinned**. * Group by tag, site or model, and sort by name, status or last seen. Each filter appears as a chip above the list. Remove a chip to clear that filter, or select **Clear all**. ## Tags Tags group devices, for example `office`, `meeting-room` or `camera`. You can filter and group by them, and a plan can target the devices with a tag. * A device can have up to 10 tags. * Tags use lowercase letters, numbers, hyphens and underscores. To change tags, open the device, go to **Settings**, then select **Add tags** or **Edit tags**. Clear the field to remove all tags. You can also add tags when you set up a device. ## Sites A site says where a device is, such as `london` or `store-12`. Each device has at most one. Plans apply the matching `sites:` overrides from `suta.yaml`, and you can filter and group the device list by site. To set it, open the device, go to **Settings**, then select **Set site** or **Change site** next to its tags. Leave the field empty to remove the site. ## The device page Open alerts for the device appear under its name, one line each; select one to open it. The page has these tabs: | Tab | What’s there | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Overview** | Resources, a one-line software summary, remote access, connection, system information and recent activity | | **Software** | The running generation, a deploy in progress, apps, the `suta.yaml` behind it and the generation history. See [Roll back, retry and pin](/deploy/device-software/) | | **Metrics** | System metrics and processes, once [monitoring](/operate/monitoring/) is on | | **Logs** | Messages from the services the device collects | | **Settings** | Device ID, identity key, project, tags, site and **Remove device** | To rename a device, select the pencil icon next to its name. The new name also appears on the device itself, in the Suta prompt and the `suta` summary. It doesn’t change the device’s hostname. **Connection** shows Wi-Fi and Ethernet separately, with each link’s addresses and gateway. Bridges and virtual links that containers create aren’t listed, unless one carries the device’s default route. **System** shows the hostname, operating system, uptime, memory, storage and CPU temperature as last reported by the device. Storage is the disk that holds app data and Suta’s state, not the read-only Suta OS image. Note When a device is offline, its details are the last values it reported, with the time of that report. Values a device never reported show as unavailable. ### Device history A device’s **Overview** shows its eight most recent events. Select **Show older activity** for the next eight. On **Software**, the five most recent generations appear first; select **Show older generations** to reach earlier versions and their rollback actions. App health and restart counts are visible on phones too. ## Generation pages Wherever the dashboard names a generation, its short ID links to that generation’s page: in plans, rollouts, a device’s **Software** tab, a project’s **Overview** and a failed deploy’s alert. Paste an ID, or its first six characters or more, into search to find a generation a device runs. A generation’s page shows: * Whether it’s running, and on how many devices. * Its full ID, to copy. * **Devices**: each device given the generation, with the result of its latest assignment, its number on that device, how it was assigned and when. Devices that run it now show **Running now**. * **Rollouts**: each rollout that assigns it. * **Contents**: its Suta OS version, hardware settings, probation, volumes, registries, and each app’s image and settings. Secret values are never part of a generation. ## Links and bookmarks Every page has its own address, such as `/projects/delivery-drones/devices/`, `/projects/delivery-drones/generations/` or `/alerts/history`. You can bookmark and share them, and they survive refreshes and sign-in. The filters on a project’s **Logs** page are part of its address too. Your chosen workspace is remembered in your browser, not in the address. ## Light and dark mode Select your avatar in the top bar, then the theme control, to switch between system, light and dark. Your choice is saved in your browser. # Monitor devices > Turn on metrics, process usage and service logs for a device. Monitoring is off until you turn it on for a device. You choose what to collect: system metrics, process usage and logs from selected services. ## Turn on monitoring * Dashboard 1. Open the device and go to its **Metrics** tab. 2. Select **Choose what to collect**. 3. Choose how often to collect **System metrics**: every 15, 30 or 60 seconds. 4. Optionally, select **Process metrics** and choose services under **Service logs**. 5. Select **Turn on monitoring**. The status changes to **Starting collection**, then **Collecting** once the device confirms the change. * CLI Turn on system metrics: ```sh suta device monitoring enable kitchen-pi ``` Also collect logs from specific services: ```sh suta device monitoring enable kitchen-pi camera.service inference.service ``` Check the result: ```sh suta device monitoring status kitchen-pi ``` Each app in [`suta.yaml`](/deploy/suta-yaml/) logs to its own service, named `suta-app-` followed by the app’s name, such as `suta-app-web.service`. ## What you can see A device’s **Metrics** tab shows CPU usage, memory, disk space, temperature, network activity and disk activity for the last hour, 6 hours or 24 hours. Gaps in collection stay blank, and device restarts are marked. A device’s **Logs** tab shows messages from the services you selected. You can: * Search for text, or exclude it. * Filter by service and severity. * Select **Pause** to stop new messages while you read, then **Resume live**. * Select **Load earlier logs**, or **Show nearby logs** around one message. **Processes**, on the **Metrics** tab, shows CPU and memory per process, if you turned on process metrics. It shows names and process IDs only, never command lines or environment variables. ## Search a project’s logs A project’s **Logs** searches every device in the project that collects logs, and shows their messages together, newest first, with the device and service of each. Filter by device, service, severity and time range, or search for text. **Live** follows new messages; select it again to pause at the current moment. The filters are part of the page’s address, so you can share a view. **Logs around failure** on a failed deploy opens this page for that device, ending just after it failed. A search covers up to 25 devices at once, those that reported most recently; choose a device to search any other. From the CLI: ```sh suta device metrics kitchen-pi 6h suta device logs kitchen-pi camera.service --since 15m suta device logs kitchen-pi --search timeout ``` ## Change what’s collected On the device’s **Metrics** or **Logs** tab, select **Settings**. You can change the sample interval, turn process metrics on or off, and choose services. To select many services at once, select **Edit configuration** and edit the JSON directly. With the CLI, write the complete settings to a file and apply it: settings.json ```json { "enabled": true, "services": ["camera.service", "inference.service"], "process_metrics": false, "sample_interval_seconds": 30 } ``` ```sh suta device monitoring apply kitchen-pi settings.json ``` ## Turn off monitoring Select the toggle on the device’s **Metrics** or **Logs** tab, then **Turn off monitoring**, or run: ```sh suta device monitoring disable kitchen-pi ``` Your service choices are kept for next time. An offline device stops collecting when it reconnects. ## Data retention Suta keeps metrics and logs for 24 hours, then deletes them automatically. Data collected before you turned monitoring off stays visible until it’s 24 hours old. Logs can contain sensitive data Choose services carefully. Anyone with the **Read logs** permission can read what those services write to their logs. # Remote access > Open a time-limited window to a device, then use a terminal, forward a port, run commands or restart services. Remote access lets you work on a device from anywhere, through Suta. It’s off by default. You open it for a limited time, and terminals, port forwards, commands and service restarts only work while it’s open. Everything is recorded. ## Before you begin You need these permissions, which the **Operator** and **Device manager** roles include: * **Manage remote access** to open and close the window. * **Run commands** for terminals, port forwards and commands without sudo. * **Run commands with sudo** for full administration (Device manager only). * **Restart services** to restart a service without sudo. See [Roles and permissions](/reference/permissions/). ## Open remote access * Dashboard 1. Open the device. On the **Overview** tab, find **Remote access**. 2. Select **Enable remote access**. 3. Choose how long: **15 minutes**, **1 hour** or **4 hours**. 4. Select **Enable access**. The status shows **Enabling on device…** until the device confirms, then **Enabled** with the time it closes. * CLI ```sh suta device access enable kitchen-pi # 1 hour suta device access enable kitchen-pi 15m # or 15m, 4h suta device access status kitchen-pi ``` Until I turn it off The dashboard also offers **Until I turn it off**. Access then stays open across restarts until someone disables it. Use a time limit whenever you can. To extend or shorten the window, select **Change duration**. Refreshing a page or opening a terminal never extends it. ## Open a terminal * Dashboard 1. Open the device and, under **Remote access**, select **Open terminal**. 2. Choose **User** for a normal shell, or **Sudo** for full administration. 3. Choose a session length of up to 2 hours, then select **Open terminal**. If remote access is off, opening a terminal also enables it for at least the session length (15 minutes, 1 hour or 4 hours), which needs **Manage remote access**. The terminal connects once the device confirms. * CLI ```sh suta device ssh kitchen-pi suta device ssh kitchen-pi --sudo --for 15m ``` If remote access is closed, the CLI offers to open it for one hour. Terminal sessions are recorded in full, including what you type. Don’t type passwords or secrets into a recorded session. ## Forward a port Reach a web interface or other service on the device from your computer, as with `ssh -L`: ```sh suta device forward kitchen-pi 8080 # localhost:8080 → device port 8080 suta device forward kitchen-pi 9000:80 # localhost:9000 → device port 80 suta device forward kitchen-pi 8080 --for 15m ``` Then open `http://localhost:8080`. The forward listens only on `127.0.0.1` on your computer and reaches only that one port on the device’s own loopback interface (`127.0.0.1`), as the unprivileged user account. It can’t open a shell, reach other ports, or reach other machines on the device’s network. Services must listen on `127.0.0.1` or all interfaces on the device. The forward runs until you press Ctrl-C, the session time runs out (1 hour by default, up to 2 hours), or remote access closes. It counts as one of the device’s four sessions, however many connections you make through it. Throughput is limited to about 1 MB/s, so it suits web interfaces and APIs rather than bulk transfers. Suta records who forwarded which port and when, but not the traffic: the connection is encrypted between the CLI and the device, so Suta can’t see it. ## Run a single command Use `suta device exec`. Everything after `--` is passed to the device as-is: ```sh suta device exec kitchen-pi -- uname -a suta device exec kitchen-pi -- sh -c 'uptime; df -h /' suta device exec kitchen-pi --sudo -- systemctl status camera.service ``` Use `sh -c` when you need pipes, `;` or variables. Commands time out after one minute by default; use `--timeout` to allow up to five minutes. The exit code of the remote command becomes the exit code of `suta`. Lost the response? `suta` prints a request ID before it sends a command. If your connection drops, check what happened before running it again: ```sh suta workspace activity --request REQUEST_ID ``` To retry safely, pass the same `--request-id`. Suta never runs the same request ID twice. ## Restart a service Restarting a service needs only the **Restart services** permission, not sudo: ```sh suta device service list kitchen-pi suta device service restart kitchen-pi camera.service ``` ## Close remote access Select **Disable access** on the device page, or run: ```sh suta device access disable kitchen-pi ``` Closing access ends all terminal sessions and port forwards on the device. It can’t undo changes already made. ## Block remote access for the whole workspace Admins can turn remote access off for every device in **Settings** > **Remote access** > **Block remote access**. ## How sessions are separated * **User** sessions, port forwards and commands run as a separate unprivileged account that can’t use `sudo`. * **Sudo** sessions and commands can make any change to the device. * To turn remote access off permanently on one device, create the file `/etc/suta/remote-access.disabled` on it as root. Suta never removes it. # Activity and recordings > See who did what, review commands and replay terminal sessions. ## Activity The workspace’s **Activity** shows what changed in every project and every command run on a device, newest first. * **Timeline** lists changes such as devices added, renamed, moved or removed, people invited, projects created, plans, rollouts, rollbacks, pins and secret changes. It also shows each deploy a device failed or rolled back, with the reason the device gave, and each device’s first connection. Filter by **Deploys**, **Devices**, **People & workspace** or **Connected apps**, choose a project, or search, including for text in a failure reason. Select an entry to open what it’s about, or **Details** to see everything recorded with it. * **Device operations** lists commands, service restarts, terminal sessions and device changes such as turning on remote access, grouped by day. Each shows who ran it, the app that acted for them, the device and the result, with the reason when one failed or was interrupted. Filter by device or by result, or search commands, people and devices. Select **Details** for exact times, the exit code and the operation’s IDs. Each project has its own **Activity** with the same timeline for its devices, plans and rollouts, which you can filter by device. Each device page also shows its own recent activity, including deploys it accepted. From the CLI: ```sh suta workspace activity suta workspace activity kitchen-pi --since 1d suta workspace activity --request REQUEST_ID ``` ## Terminal recordings Every terminal session is recorded, including input, output and window size. On **Device operations**, select **View recording** to replay a session. You need the **Read activity and recordings** permission. Caution Recordings include everything typed, which can include passwords. Treat the **Read activity and recordings** permission as sensitive. ## How long records are kept Device operations and recordings are kept for 30 days. Suta doesn’t store the output of commands run with `suta device exec`, only the command, who ran it and the result. # Alerts and webhooks > Get notified in Slack or your own service when a device goes offline or needs attention. Suta checks your devices about every 20 seconds. When a problem starts, it opens an alert. When the problem clears, it resolves the alert on its own. ## See alerts The bell at the top of every page counts the alerts that need attention: open alerts that nobody has acknowledged or snoozed. Select it to see alerts assigned to you first, then the rest. Select an alert to open it, or select the check mark to acknowledge it from there. The workspace’s **Alerts** covers every project, and each project has its own **Alerts** for its devices. * **Open** lists alerts that need attention first. Choose **All open**, **Needs attention** or **Assigned to me**, search, or filter by condition. * **History** lists past and current alerts. A project’s **Overview** and each device’s page show their open alerts too. Snoozed alerts stay off the **Overview** until their snooze ends. ## Open an alert Each alert has its own page, with a link you can share. It shows: * **What happened**: for a failed deploy, the generation and the reason the device reported; for memory, storage and temperature, the reading when the alert opened, the reading now and the limit; for an offline device, when it last checked in. * **Timeline**: when Suta opened the alert, each time the condition happened again, what your team did and wrote, which webhooks were notified, and when it resolved. * **Details**: its status, who it’s assigned to, the device and project, and when it opened. * **Look into it**: links to the device, its **Deploy history**, **Metrics**, **Logs** around the time it happened and its **Activity**. If a deploy to a device fails again while its alert is open, Suta updates the alert with the latest failure rather than opening another, and the alert needs attention again. ## Handle an alert Anyone who can see a device can handle its alerts. Your team shares each alert’s status, and every step appears on its timeline. | Action | What it does | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Acknowledge** | Tells your team you’ve seen it. The alert stops counting in the bell until it resolves or, for a failed deploy, another deploy fails. Select **Undo** to take it back. | | **Snooze** | Hides the alert for 1 hour, 4 hours, 1 day or 1 week. When the snooze ends, it needs attention again if it hasn’t resolved. Select **End snooze** to bring it back early. | | **Assign** | Hands the alert to a teammate, or to you. It appears first in their bell under **Assigned to you**. | | **Copy link** | Copies a link to the alert, to send to a teammate. | | **Add note** | Leaves a note on the timeline, such as what you found or tried. You can add notes after an alert resolves. | Alerts resolve on their own when the device recovers. You can’t close or delete an alert yourself; acknowledge or snooze it instead. ## Choose conditions Go to the workspace’s **Alerts** > **Conditions**. Turn each condition on or off and set its limit, then select **Save changes**. Only admins can change conditions. | Condition | Default | Range | Resolves when | | ----------------------- | ---------- | -------------- | -------------------------------------- | | **Device offline** | 10 minutes | 2–1440 minutes | The device is back online | | **High memory use** | 90% | 50–99% | Use drops 5 points below the limit | | **Storage almost full** | 90% | 50–99% | Use drops 5 points below the limit | | **High temperature** | 80 °C | 50–95 °C | Temperature drops 5 °C below the limit | | **Deploy failed** | On | On or off | The device accepts a later generation | Conditions apply to every device in every project of the workspace. Turning a condition off resolves its open alerts. Alerts use only fresh readings from online devices; missing or old readings never open or resolve an alert. ## Send alerts to Slack 1. In Slack, [create an incoming webhook](https://api.slack.com/messaging/webhooks) for the channel you want, and copy its URL. 2. In Suta, go to the workspace’s **Alerts** > **Notifications** and select **Add webhook**. 3. Enter a **Name**, such as `#ops-alerts`, and paste the URL into **Webhook URL**. 4. Under **Notify when an alert**, choose **Opens**, **Resolves** or both. Under **For these conditions**, keep **All conditions** or choose **Only some**. 5. Select **Add webhook**, then **Done**. 6. Select **Send test** to check that a message arrives. Slack shows the message text directly. You don’t need anything in between. Only admins can manage notifications, and a workspace can have up to 10 webhooks. ## Send alerts to your own service Add a webhook the same way, using your own HTTPS URL. Suta sends a JSON `POST` for each alert that matches the webhook’s events and conditions. When you add the webhook, select **Copy secret** and store the signing secret safely. Suta shows it only once. Your service should: 1. [Verify the signature](/reference/webhooks/#verify-signatures) on every request. 2. Respond with any `2xx` status within 10 seconds. 3. Handle the same alert more than once. Delivery is at least once. If delivery fails, Suta retries after 1, 2, 4, 8 and 16 minutes. See [Webhook reference](/reference/webhooks/) for the payload and a signature checker you can try in your browser. ## Manage a webhook Each webhook shows its status (**Active**, **Paused**, **Failing** or **Retrying**), its last delivery and its counts for the last 24 hours. Select **Deliveries** to see recent deliveries. From the **…** menu you can: * **Edit** the name, URL, events or conditions. * **Pause** deliveries, and **Resume** them later. * **Rotate signing secret**. The new secret is shown once, and it signs every delivery from then on, so update your service straight away. * **Delete** the webhook. Note Webhook URLs must use HTTPS and a public address. Suta doesn’t follow redirects. # People and roles > Invite people to your workspace and control what they can do. Everyone in a workspace has a role. A role is a set of [permissions](/reference/permissions/) that you define once and apply to many people. ## Built-in roles Each new workspace starts with four roles. Choose one to see what it allows. **Admin** is protected: you can’t edit or delete it, and a workspace always keeps at least one admin. You can edit the other three and create your own. ## Invite someone You need to be an admin. 1. Go to **Settings** > **People** and select **Invite someone**. 2. Enter their **Email address** and choose a **Role**. 3. Select **Send invite**. The person receives an email. They must accept it within seven days, signed in with that email address. To cancel an invitation, find it under **Pending invitations** and select **Revoke**. ## Your display name Suta shows people by their display name: on deployments, alerts, terminals, activity and the audit log. Your email address appears only on the **People** page and your own account page. A new account takes its name from your sign-in provider, or the start of your email address if the provider has none. To change it, open the account menu (your initial, top right), select your name, and edit **Display name**. The new name shows everywhere at once, including on things you did before. API tokens are shown by their name, marked **Token**. ## Change or remove someone Change someone’s role from the picker next to their name in **People**, or open them and select **Remove from workspace**. The change takes effect immediately and closes any terminal sessions they have open. ## Create or edit a role 1. Go to **Settings** > **Roles** and select **Create role**, or **Edit** on an existing role. 2. Under **Start from**, choose an existing role to copy its permissions, then name the role and adjust its permissions. 3. To make it the default for new invitations, select **Use for new invitations**. 4. Select **Create role** or **Save role for everyone**. Editing a role changes access for everyone who has it, straight away. You can’t delete a role that’s in use or is the default. Limit access to some services Under **Advanced: service and log restrictions**, you can limit log reading and service restarts to named services, such as `camera.service`, or limit logs to a fixed time range. # Suta CLI > Every command in the Suta CLI, with examples. The `suta` command runs on your computer. Install it with `curl -fsSL https://suta.dev/install.sh | bash` and choose **Suta CLI**. Run `suta help ` for examples of any command. For the `suta` command on a device, see [On-device commands](/reference/device-cli/). `` is always a device name or ID. If two devices share a name, use the ID; Suta never guesses. ## Global options These work with every command. | Option | Description | | ------------------ | ---------------------------------------------------------- | | `--workspace NAME` | Use this workspace for one command. Also `SUTA_WORKSPACE`. | | `--json` | Print one JSON result. Never prompts or opens a browser. | | `--no-input` | Never prompt or open a browser. Also `SUTA_NO_INPUT=true`. | ```sh suta device list --workspace Lab suta device list --json SUTA_NO_INPUT=true suta device inspect kitchen-pi ``` ## Sign in and workspaces `suta auth login` opens your browser to sign in. The session lasts 30 days and is saved in `~/.config/suta/session.json`, readable only by you. | Command | Description | | --------------------------- | --------------------------------------------- | | `suta auth login` | Sign in through your browser. | | `suta auth status` | Show your account, workspace and permissions. | | `suta auth logout` | Sign out and close your remote sessions. | | `suta workspace list` | List your workspaces. | | `suta workspace use ` | Set your default workspace. | The CLI acts as you, with your role’s permissions. Anything that runs `suta` on your computer, including an AI agent, has the same access. ```sh suta auth login suta auth status suta workspace list suta workspace use Workshop ``` ## Projects | Command | Description | | ------------------------------------------ | ----------------------------------------------------------------- | | `suta project list` | List projects with their devices, open alerts and latest rollout. | | `suta project create [--slug SLUG]` | Create a project. Admins only. | A project’s slug is the name `suta.yaml` uses for it, in `project:`. See [Projects](/get-started/projects/). ```sh suta project list suta project create "Warehouse robots" ``` ## Set up devices | Command | Description | | ---------------------------------- | ---------------------------------------------------------------------------------------------------- | | `suta flash [disk] [--image FILE]` | Flash Suta OS to an SD card. | | `suta device add [--project SLUG]` | Find a new device nearby and add it to a project. Asks which project when the workspace has several. | | `suta device network ` | Repair a device’s network from nearby. | `suta flash` accepts `.img` and `.img.xz` files. Downloaded images are cached in `~/.suta/cache/images/`; delete that folder to free space. These commands are interactive and need a terminal. See [Set up a new Pi](/add-devices/new-pi/) and [Fix a device’s network](/add-devices/repair-network/). ```sh suta flash suta flash /dev/disk4 --image suta-os-2026.09.5-pi.img.xz suta device add --project delivery-drones suta device network kitchen-pi ``` ## Inspect devices | Command | Description | | -------------------------------------------- | ----------------------------------------------------------------- | | `suta device list` | List devices, their generation, apps and when each was last seen. | | `suta device inspect ` | Show identity, network and last reported measurements. | | `suta device metrics [1h\|6h\|24h]` | Show collected metrics. | | `suta device service list ` | List the services the device last reported. | These read what the device last reported. They don’t contact the device. ```sh suta device list suta device inspect kitchen-pi suta device metrics kitchen-pi 6h suta device service list kitchen-pi ``` ## Read logs ```text suta device logs [service] [options] ``` | Option | Default | Description | | ---------- | ------- | ---------------------------------------------- | | `--since` | `1h` | A duration such as `15m`, or an RFC 3339 time. | | `--until` | now | An RFC 3339 time (exclusive). | | `--search` | | Only messages that contain this text. | | `--limit` | `100` | 1 to 200 entries. | | `--before` | | The cursor from the previous page. | Logs are kept for 24 hours. [Monitoring](/operate/monitoring/) must be on and collecting the service. ```sh suta device logs kitchen-pi suta device logs kitchen-pi camera.service --since 15m suta device logs kitchen-pi --search timeout --limit 200 ``` ## Remote access | Command | Description | | -------------------------------------------------- | ------------------------------------------- | | `suta device access status ` | Show whether access is open and until when. | | `suta device access enable [15m\|1h\|4h]` | Open access. Default `1h`. | | `suta device access disable ` | Close access and end sessions. | See [Remote access](/operate/remote-access/). ```sh suta device access enable kitchen-pi suta device access status kitchen-pi suta device access disable kitchen-pi ``` ## Terminals and commands | Command | Description | | -------------------------------------------------------- | ---------------------------------------------------------------------- | | `suta device ssh [--sudo] [--for 1h]` | Open a recorded terminal, 1 minute to 2 hours. | | `suta device exec [--sudo] -- [args]` | Run one program and print its output. | | `suta device forward <[local:]port> [--for 1h]` | Forward a local port to a port on the device’s loopback, until Ctrl-C. | | `suta device service restart ` | Restart a service without sudo. | `exec` options: | Option | Default | Description | | -------------- | -------- | --------------------------------------------------- | | `--timeout` | `1m` | Stop waiting after up to `5m`. | | `--request-id` | new UUID | Reuse after a lost response to avoid running twice. | Output is limited to 1 MiB. The remote exit code (0–255) becomes `suta`’s exit code. `suta` exits with `1` if the result is unknown, `2` for invalid arguments and `130` if you cancel. ```sh suta device ssh kitchen-pi suta device ssh kitchen-pi --sudo --for 15m suta device exec kitchen-pi -- uname -a suta device exec kitchen-pi -- sh -c 'uptime; df -h /' suta device exec kitchen-pi --sudo --timeout 5m -- apt-get update suta device forward kitchen-pi 8080 suta device forward kitchen-pi 9000:80 --for 15m suta device service restart kitchen-pi camera.service ``` ## Monitoring | Command | Description | | ---------------------------------------------------- | ------------------------------------------ | | `suta device monitoring status ` | Show settings and what the device reports. | | `suta device monitoring enable [services…]` | Turn on metrics, plus logs for services. | | `suta device monitoring disable ` | Turn off collection, keeping your choices. | | `suta device monitoring apply ` | Apply a complete settings file. | ```sh suta device monitoring enable kitchen-pi camera.service suta device monitoring apply kitchen-pi settings.json ``` settings.json ```json { "enabled": true, "services": ["camera.service"], "process_metrics": false, "sample_interval_seconds": 30 } ``` ## Activity ```text suta workspace activity [device] [options] ``` | Option | Default | Description | | ----------- | ------- | --------------------------------------------- | | `--since` | | A duration such as `1d`, or an RFC 3339 time. | | `--limit` | `20` | 1 to 200 entries. | | `--before` | | The cursor from the previous page. | | `--request` | | Find one operation by its request ID. | ```sh suta workspace activity suta workspace activity kitchen-pi --since 1d suta workspace activity --request 3f0c… ``` ## Plan and deploy You describe what devices run in `suta.yaml`. `suta plan` shows what would change on each device; `suta deploy` applies a plan in stages. | Command | Description | | --------------------------------- | ---------------------------------------------------------- | | `suta plan [options]` | Preview the change for each device and update `suta.lock`. | | `suta deploy [plan] [options]` | Deploy a plan: a canary first, then in batches. | | `suta rollback [--to N]` | Return a device to an earlier accepted generation. | | `suta migrate [--yes]` | Move a device from Raspberry Pi OS to Suta OS. | `plan` and `deploy` read `./suta.yaml`, or the file you pass with `-f`. Files named with `source:` are read relative to `suta.yaml`; each must stay inside its directory, be UTF-8 text and be 64 KiB or smaller. Planning pins image digests, the OS release and secret versions in `suta.lock`, next to `suta.yaml`. Commit both files. Later plans reuse the pins until you pass `--update`. Nothing changes on devices until you deploy. In the plan, `+` marks a device that updates, `=` one that already runs the plan and `!` a pinned device, which keeps its generation. Options for choosing devices, for both commands: | Option | Description | | ----------------- | ------------------------------------------------------------------------------------------------ | | `-f`, `--file` | The spec to read. Default `suta.yaml`. | | `--device` | Only this device. Repeat for several. | | `--tag` | Only devices with this tag. Repeat to require several. | | `--site` | Only devices in this site. Repeat for several. | | `--update` | Pin the newest images, OS release and secret versions. | | `-m`, `--message` | One line saying what the deploy is for, up to 200 characters. It names the plan and its rollout. | With a plan ID, `deploy` starts exactly that plan. Without one, it plans first, shows the plan and asks you to confirm. With `--json` or `--no-input`, pass `--yes` to confirm in advance. `deploy` options: | Option | Default | Description | | ---------------- | ----------------------------------------------------------------------- | ------------------------------------------------------- | | `--canary` | Sized to the plan; see [Choose stages](/deploy/rollouts/#choose-stages) | Devices to update first, 0 to 20. | | `--batch` | Sized to the plan | Devices to update at a time after the canary, 1 to 500. | | `--max-failures` | `0` | Failures to allow before pausing, 0 to 100. | | `--wait` | | Wait until the rollout finishes or pauses. | | `--yes`, `-y` | | Deploy without asking for confirmation. | `deploy --wait` exits with `1` if the rollout pauses, is cancelled or any device fails. `migrate` asks you to confirm. Pass `--yes` in scripts. See [Move a device to Suta OS](/deploy/migrate/). ```sh suta plan suta plan -m "Fix sensor calibration" suta plan --site london --update suta deploy 3f2a9c1e-… suta deploy --tag kiosk --wait suta deploy --yes --canary 2 --batch 20 --json suta rollback kitchen-pi suta rollback kitchen-pi --to 5 suta migrate kitchen-pi ``` suta.yaml ```yaml os: latest apps: web: image: ghcr.io/acme/web:1.4 env: LOG_LEVEL: info secrets: [db-password] files: /etc/web/app.conf: source: config/app.conf ports: ["80:8080"] sites: london: apps: web: env: { REGION: eu } ``` ## Device generations | Command | Description | | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | `suta device generations ` | Show the device’s generations, apps and OS. | | `suta device diff [from] [to]` | Compare the generations of two deploys, by deploy number. | | `suta device config [number] [--lock]` | Print the `suta.yaml` (or with `--lock`, `suta.lock`) behind the running generation or deploy `number`. | | `suta device explain [key] [-f FILE]` | Show each value `suta.yaml` gives the device and where it comes from. | | `suta device pin [--reason TEXT]` | Keep the device on its current generation. | | `suta device unpin ` | Let plans and rollouts update the device again. | | `suta device retry ` | Run a failed or rolled-back generation again. | | `suta device site \|--clear` | Set or clear the device’s site. | By default, `diff` compares the previously accepted generation with the current one, or with the one being deployed. It shows environment variables and secret names and versions, never secret values. `explain` sources are `spec`, `site `, `device `, `lock` and `default`. Pass a key prefix such as `apps.web.env` to show fewer values. `explain` doesn’t change `suta.lock`. `retry` runs a generation the device refused after it failed there. Fix the cause first. ```sh suta device generations kitchen-pi suta device diff kitchen-pi suta device diff kitchen-pi 5 8 suta device config kitchen-pi > suta.yaml suta device explain kitchen-pi apps.web.env suta device pin kitchen-pi --reason "Trade show demo" suta device site kitchen-pi london ``` ## Rollouts `suta deploy` starts a rollout. Use these commands to follow and control it. | Command | Description | | ----------------------------------------------------- | ------------------------------------------ | | `suta rollout list` | List recent rollouts. | | `suta rollout show [rollout]` | Show progress, stages and every device. | | `suta rollout wait [rollout]` | Wait until the rollout finishes or pauses. | | `suta rollout pause\|resume\|retry\|cancel [rollout]` | Control a rollout. | Without an ID, these commands use the current rollout, or the most recent one. `wait` exits with `1` if the rollout pauses, is cancelled or any device fails. See [Rollouts](/deploy/rollouts/). ```sh suta rollout show suta rollout wait suta rollout retry ``` ## Secrets | Command | Description | | -------------------------------------------- | -------------------------------------------------------------- | | `suta secret list` | List secrets, their latest version and whether they’re in use. | | `suta secret set [--value-file FILE]` | Store a new version of a secret. | | `suta secret remove ` | Delete a secret and every version. | `set` reads the value from standard input or from `--value-file`, exactly as stored. In a terminal, it prompts without showing what you type. Values are never accepted as arguments, so they don’t appear in your shell history. Each `set` creates a new version. Run `suta plan --update` to move existing pins to it. You can’t remove a secret while a device’s generation uses it. Managing secrets needs a workspace admin. ```sh suta secret set db-password suta secret set db-password < password.txt suta secret set tls-key --value-file key.pem suta secret list ``` ## Alerts | Command | Description | | ------------------------------------------------------------------ | ---------------------------------------------- | | `suta alert list [--all] [--kind K] [--device D]` | List open alerts, or every alert with `--all`. | | `suta alert conditions [options]` | Show alert conditions, or change them. | | `suta alert webhook list` | List webhooks and their recent deliveries. | | `suta alert webhook add ` | Add a webhook and print its signing secret. | | `suta alert webhook test\|pause\|resume\|rotate\|remove ` | Manage a webhook. | `conditions` takes `--offline MINUTES`, `--memory PERCENT`, `--storage PERCENT` and `--temperature CELSIUS`, each also accepting `off`, and `--deploy-failed on|off`. `webhook add` takes `--on opened,resolved` and `--kind` to limit which alerts it receives. Changing conditions and webhooks needs a workspace admin. See [Alerts](/team/alerts/). ```sh suta alert list suta alert list --all --kind storage suta alert conditions --storage 85 --temperature off suta alert webhook add Ops https://hooks.slack.com/services/… suta alert webhook test Ops ``` ## API tokens | Command | Description | | -------------------------------------------------- | --------------------------------- | | `suta workspace token list` | List API tokens. | | `suta workspace token create --role ` | Create a token and print it once. | | `suta workspace token revoke ` | Revoke a token immediately. | `--expires` is `30d`, `90d` (default), `365d` or `never`. Managing tokens needs a workspace admin. See [API tokens](/ai-agents/api-tokens/). ```sh suta workspace token create CI --role Operator --expires 30d suta workspace token revoke CI ``` ## Maintenance | Command | Description | | ------------------------------------ | ------------------------------------ | | `suta update` | Update the CLI. Keeps you signed in. | | `suta completions ` | Print a shell completion script. | `suta update` updates the CLI on your computer, not the software on devices. ```sh suta update suta completions zsh > "${fpath[1]}/_suta" ``` ## JSON output With `--json`, `suta` prints exactly one JSON object to standard output. Progress and messages go to standard error. A failed remote command also includes `data`, with the state, exit code and any output. Human-readable output escapes control characters from devices. JSON output keeps the original text. Success ```json { "ok": true, "data": { "…": "…" } } ``` Failure ```json { "ok": false, "error": { "code": "permission_denied", "message": "…" } } ``` Environment variables `SUTA_TOKEN` uses an [API token](/ai-agents/api-tokens/) instead of your login, `SUTA_WORKSPACE` sets a default workspace, `SUTA_NO_INPUT=true` disables prompts, and `SUTA_SESSION_FILE` stores your session somewhere else. # On-device commands > The suta command on the device itself. Every device with Suta has its own `suta` command. When you open a terminal on the device, through [remote access](/operate/remote-access/) or SSH, you see a short summary of the device. Run `suta` any time to see it again. | Command | Description | | ---------------------- | ------------------------------------------------------------------------------------------------------------ | | `suta` | Show a short summary of the device. | | `suta status` | Show identity, hostname, interfaces, storage, the running generation and the last failed deploy. | | `suta apps` | Show each app on the device: whether it’s running, its health check, restarts and why it last failed. | | `sudo suta logs` | Show recent logs from the Suta agent. | | `sudo suta logs APP` | Show recent logs from one app. | | `sudo suta logs --all` | Show recent logs from the agent, every app and Suta’s other services, interleaved. | | `suta name` | Print the device’s name in your workspace. | | `suta id` | Print the device ID, or its public identity before it’s registered. | | `suta version` | Print the agent version. | | `sudo suta register` | Finish registering the device in your browser. | | `sudo suta setup` | Open a 15-minute window to [repair the network](/add-devices/repair-network/). Devices running Suta OS only. | | `sudo suta uninstall` | Remove Suta, keeping the device’s identity and settings. | `suta logs` shows the last 100 lines. Add `--lines N` (`-n N`) for more or fewer, and `--follow` (`-f`) to keep printing new lines until you press Ctrl-C. For example, to watch an app named `camera` restart: ```sh sudo suta logs camera --follow ``` Logs come from the device’s system journal, so they need `sudo`. `suta apps` reads the same snapshot as `suta status` and doesn’t. Output is plain when piped or when `NO_COLOR=1` is set. The device’s name in Suta appears in the Suta prompt and summary. It doesn’t change the Pi’s hostname or other accounts. # Limits and timings > Durations, sizes and retention periods in Suta. ## Devices | | Limit | | ----------------------------------------- | ------------------------------ | | Heartbeat interval | About every 5 seconds | | Shown as offline after | 30 seconds without a heartbeat | | Tags per device | 10 | | Network repair window (`sudo suta setup`) | 15 minutes | ## Remote access | | Limit | | -------------------- | ------------------------------------------------------------------- | | Remote access window | 15 minutes, 1 hour or 4 hours, or until turned off (dashboard only) | | Terminal session | 1 minute to 2 hours | | Command timeout | 1 second to 5 minutes, default 1 minute | | Command output | 1 MiB, standard output and error combined | | Terminal recording | 25 MiB per session; the session ends if it’s reached | ## Monitoring | | Limit | | --------------------------- | --------------------- | | Sample interval | 15, 30 or 60 seconds | | History | 24 hours | | Log entries per CLI request | 1 to 200, default 100 | ## Deploys | | Limit | | ----------------------------------------- | ------------------------------------------------------ | | Probation | 10 seconds to 1 hour, default 60 seconds | | Rollouts | One running or paused rollout per project | | Canary | 0 to 20 devices | | Batch | 1 to 500 devices | | Time for a device to finish | 60 minutes; after that, the rollout pauses | | Offline devices | Skipped if silent for 2 minutes when their wave starts | | Configuration file | 64 KiB each | | Names of apps, sites, secrets and volumes | Up to 40 characters | ## Workspace | | Limit | | ----------------------- | -------------------------------------- | | Invitation expiry | 7 days | | Webhooks | 10 per workspace | | Activity and recordings | Kept for 30 days | | CLI sign-in | 30 days | | AI agent approval | Until revoked, or 15 minutes to 7 days | | API token expiry | 30 days, 90 days, 1 year or never | # Permissions > Every permission in Suta, what it allows and which roles include it. Permissions control what people, AI agents and API tokens can do. People and [API tokens](/ai-agents/api-tokens/) get permissions from a [role](/team/people-and-roles/). AI agents get the permissions you approve, limited to what the approving person has. ## All permissions | Permission | ID | Allows | | ---------------------------- | ------------------ | ---------------------------------------------------------------------------------------- | | View device details | `device.read` | Identity, version, network and connection details. Every other permission includes this. | | View services | `services.read` | The services a device last reported. | | Read metrics | `metrics.read` | System and process measurements. | | Read logs | `logs.read` | Collected service logs. | | Restart services | `services.restart` | Restarting services, without general sudo. | | Change monitoring settings | `monitoring.write` | Turning collection on or off and choosing services. | | Manage remote access | `access.write` | Opening, changing and closing remote access. | | Run commands | `commands.exec` | Commands, terminals and port forwards as an unprivileged user. | | Run commands with sudo | `commands.sudo` | Commands and terminals with full `sudo`. | | Read activity and recordings | `audit.read` | Operation history and terminal recordings. | | Edit device details | `device.write` | Renaming devices and changing tags. | | Remove devices | `device.remove` | Removing devices and revoking their access. | | Enroll devices | `device.enroll` | Adding devices to the workspace. | | Configure networking | `network.write` | Approving local network repair and saving a project’s Wi-Fi networks. | | Deploy software | `deploy.apply` | Deploying generations, rolling back and pinning devices. | ## Scope Each permission applies to all devices in the workspace, including future ones, or to devices you choose. Three permissions can also be limited to named services: **View services**, **Read logs** and **Restart services**. **Read logs** can also be limited to a fixed time range. Caution Shell access can read anything on the device, beyond any log or service limits. Give **Run commands** and **Run commands with sudo** only to people and agents who need them. # Webhooks > The alert webhook payload, headers and how to verify signatures. Suta sends a JSON `POST` to each webhook when an alert opens or resolves, and when you select **Send test**. To add a webhook, see [Alerts and webhooks](/team/alerts/). ## Payload | Field | Description | | --------------- | ---------------------------------------------------------------------------------------------------------------------- | | `type` | `alert.opened`, `alert.resolved` or `test`. | | `text` | A one-line summary. Slack displays it as the message. For a failed deploy, it includes the reason the device reported. | | `alert.kind` | `offline`, `memory`, `storage`, `temperature` or `deploy_failed`. | | `alert.status` | `open` or `resolved`. | | `alert.details` | The reading and the limit that triggered the alert. | | `device` | The device’s ID, name and project (`id`, `slug` and `name`). | | `workspace` | The workspace’s ID and name. | | `url` | A link to the alert in Suta, where your team can acknowledge, snooze or assign it. | alert.opened ```json { "type": "alert.opened", "text": "[Suta] Alert: Lobby camera storage is 96% full (limit 90%)", "alert": { "id": "8c1f…", "kind": "storage", "status": "open", "summary": "Lobby camera storage is 96% full (limit 90%)", "details": { "percent": 96.0, "threshold": 90.0 }, "opened_at": "2026-09-23T10:00:00Z", "resolved_at": null }, "device": { "id": "…", "name": "Lobby camera", "project": { "id": "…", "slug": "lobby-kiosks", "name": "Lobby kiosks" } }, "workspace": { "id": "…", "name": "Acme Robotics" }, "url": "https://app.suta.dev/projects/lobby-kiosks/alerts/8c1f…" } ``` ## Headers | Header | Value | | ---------------- | -------------------------------- | | `Content-Type` | `application/json` | | `User-Agent` | `Suta-Alerts/1` | | `Suta-Signature` | `t=,v1=` | ```http POST /hooks/suta HTTP/1.1 Content-Type: application/json User-Agent: Suta-Alerts/1 Suta-Signature: t=1790157600,v1=6f1d… ``` ## Verify signatures `v1` is an HMAC-SHA256 of `.`, keyed with the webhook’s signing secret, as lowercase hex. To verify a request: 1. Read the raw request body before parsing it as JSON. 2. Split `Suta-Signature` into `t` and `v1`. 3. Compute the HMAC of `t`, a `.`, and the raw body. 4. Compare it with `v1` in constant time. 5. Reject the request if `t` is more than five minutes old. * Node.js ```js import crypto from "node:crypto"; export function verify(secret, header, rawBody) { const { t, v1 } = Object.fromEntries( header.split(",").map((part) => part.split("=")), ); const expected = crypto .createHmac("sha256", secret) .update(`${t}.${rawBody}`) .digest("hex"); const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300; return ( fresh && v1.length === expected.length && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected)) ); } ``` * Python ```python import hashlib, hmac, time def verify(secret: str, header: str, raw_body: bytes) -> bool: parts = dict(p.split("=", 1) for p in header.split(",")) t, v1 = parts["t"], parts["v1"] expected = hmac.new( secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256 ).hexdigest() fresh = abs(time.time() - int(t)) < 300 return fresh and hmac.compare_digest(v1, expected) ``` * Go ```go func Verify(secret, header string, rawBody []byte) bool { var t, v1 string for _, part := range strings.Split(header, ",") { k, v, _ := strings.Cut(part, "=") if k == "t" { t = v } if k == "v1" { v1 = v } } ts, err := strconv.ParseInt(t, 10, 64) if err != nil || math.Abs(float64(time.Now().Unix()-ts)) > 300 { return false } mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(t + ".")) mac.Write(rawBody) expected := hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(v1), []byte(expected)) } ``` ## Delivery * Suta treats any `2xx` response as delivered. * A different status, a timeout after 10 seconds or a connection error is retried after 1, 2, 4, 8 and 16 minutes, then marked as failed. * Delivery is at least once, so the same alert can arrive more than once. Use `alert.id` and `type` to ignore duplicates. * Suta doesn’t follow redirects, and only sends to public HTTPS addresses. # Troubleshooting > Fixes for common setup, connection and access problems. ## Setup ### Chrome doesn’t find my device * Check that you’re using Chrome on macOS at `https://app.suta.dev`. * Allow Chrome to use Bluetooth in **System Settings** > **Privacy & Security** > **Bluetooth**. * Wait for the Pi to finish its first boot. This can take a couple of minutes. * Close any other browser tab or terminal that is setting up the same device. * If you reflashed the card, forget the old device in your Mac’s Bluetooth settings, then try again. ### `suta device add` doesn’t find my device * On macOS, allow your terminal app to use Bluetooth in **System Settings** > **Privacy & Security** > **Bluetooth**. * For Ethernet, your computer and the Pi must be on the same network, and the network must allow mDNS. Over a direct cable, wait a little longer. * When the 30-second scan ends, choose to scan again. ### The installer stops with a conflict The Pi already has an account or file named `suta` that Suta didn’t create. Suta won’t take it over. Rename or remove it, then run the installer again. ## Connection ### Device stays offline A device is online only while it sends heartbeats. Check that: 1. The Pi has power and has finished starting. 2. It has internet access. On the Pi, `curl -I https://app.suta.dev` should work. 3. The Suta agent is running: `systemctl status suta-agent`. Its recent logs are in `sudo suta logs`. To change its Wi-Fi, see [Fix a device’s network](/add-devices/repair-network/). ### Measurements look old When a device is offline, Suta shows the last values it reported, with the time of the report. They update when the device reconnects. ## Deploys ### `suta plan` says an image has no `linux/arm64` build Devices need 64-bit ARM images. Build your image for `linux/arm64`, for example with `docker buildx build --platform linux/arm64,linux/amd64`, then plan again. ### `suta plan` says a secret doesn’t exist Create it first with `suta secret set NAME`. Only workspace admins can manage secrets. ### A deploy says Podman is not installed On Raspberry Pi OS, run the installer on the device again; it adds Podman. On Bookworm, whose Podman is too old, move the device to Suta OS instead. See [Requirements](/add-devices/requirements/#deploying-apps). ### A new generation was rolled back The device found a problem during probation and returned to the previous generation. To see why, open the device’s **Software** tab, or run `suta device generations DEVICE`. Common causes are an app that exits, a probe that never passes, and a change that stops the device reaching Suta. On the device itself, `suta apps` shows each app’s state and `sudo suta logs APP` shows what it printed. After you fix the cause, plan and deploy again, or [retry the generation](/deploy/device-software/#retry-a-failed-generation). ### A rollout skipped a device See [When devices are skipped](/deploy/rollouts/#when-devices-are-skipped). For `changed_since_plan`, make a new plan. ### `suta migrate` is refused * **Deploy first**: the device doesn’t run a generation yet. Deploy one, even with no apps, then try again. * **Raspberry Pi 4 or 5 required**: the device’s model can’t move to Suta OS yet. * **Already runs Suta OS**: there’s nothing to do. ## Access ### “Permission denied” in the dashboard or CLI Your role doesn’t include that permission. Run `suta auth status` to see your permissions, and ask a workspace admin to change your role. See [Permissions](/reference/permissions/). ### Commands say remote access isn’t ready Open remote access first with `suta device access enable `, then wait until `suta device access status ` shows it’s ready. ### Commands say the device hasn’t answered Remote access is open, but the device stopped answering. The error says when it last answered. Nothing ran on the device. Usually the device lost its network. A command that changes Wi-Fi settings can cause this on a device whose only connection is that Wi-Fi. Commands work again once it reconnects. If it stays [offline](#device-stays-offline), restart it or repair its network. ### My AI agent says it needs approval The agent should give you a link. Open it, review the request and select **Approve access**. Then tell the agent to continue. See [Connect an AI agent](/ai-agents/connect/#approve-more-access). ### A CI job gets “403” with an API token Tokens can’t open terminals, run commands or open remote access, and they can only do what their role allows. See [API tokens for CI](/ai-agents/api-tokens/). ## Still stuck? Open an issue on [GitHub](https://github.com/suta-dev/suta/issues), with the device ID from `suta id` and what you tried. # Set up with an AI assistant > Paste one prompt into your AI coding assistant, and it writes suta.yaml for your app and deploys it with you. An AI coding assistant, such as Claude Code, Cursor or Codex, can set up a Suta project for you. It installs the CLI, writes `suta.yaml` for your app, and plans and deploys it, asking you before anything changes on a device. ## Copy the prompt Open your assistant in the repository for your app. Copy this prompt, add what your devices should run at the end, and send it. Prompt ```text Help me deploy software to my devices with Suta, from this repository. Before you start, read the Suta documentation at https://docs.suta.dev/llms-full.txt. Use only the commands, options and suta.yaml settings it describes; don't guess. The suta.yaml schema is at https://suta.dev/schema/suta.json. 1. Check that the Suta CLI is installed with `suta --version`. If it isn't, install it with `curl -fsSL https://suta.dev/install.sh | bash -s -- --cli`. 2. Run `suta auth status`. If I'm not signed in, ask me to run `suta auth login`, which opens my browser. 3. Run `suta project list` and ask me which project to deploy to. 4. Write suta.yaml for what I describe below. Use images that include linux/arm64, give every app a probe, and keep data that must survive updates in volumes. If an app needs a secret, ask me to create it with `suta secret set NAME`; never ask for or print its value. 5. Run `suta plan --device DEVICE` for one device I choose. Show me the output and explain what will change. 6. Only after I confirm, deploy that plan with `suta deploy PLAN_ID --wait`. If the device rolls the change back, run `suta device generations DEVICE`, tell me why, and fix suta.yaml. 7. Once it works on one device, ask me before rolling it out to the rest of the project. When we're done, commit suta.yaml and suta.lock. What my devices should run: ``` You still sign in and create secrets yourself; the assistant asks you when it needs you to. ## Give an assistant the docs These docs are also published as plain text for AI assistants. Point an assistant at one of these files, or add it to its context, when you ask it about Suta. | File | What’s in it | | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | [`llms.txt`](/llms.txt) | Where to start: what Suta is, and links to the other files. | | [`llms-full.txt`](/llms-full.txt) | The complete documentation in one file. | | [`llms-small.txt`](/llms-small.txt) | The same, with tips and notes removed, for assistants with less room. | | [`_llms-txt/reference.txt`](/_llms-txt/reference.txt) | One section of the docs, such as the reference. `llms.txt` lists a file for each section, for an assistant that only needs part of them. | For `suta.yaml`, editors and assistants can also use the [JSON Schema](https://suta.dev/schema/suta.json). ## Let an agent work on your devices To let an assistant read logs, check metrics or follow a rollout for you, connect it to Suta’s MCP server. It gets only the access you approve. See [Connect an AI agent](/ai-agents/connect/).