# Automaton Automaton is a self-hosted platform for building, deploying and publishing web apps. The user runs their own server (Studio, API, app store) and one or more Runners that run the apps. Documentation: https://automaton.monster/docs/ . This file is the index for AI agents; every page below is one task with exact commands and a check after each step. ## Start here Every page below is one task with exact commands and a check after each step. Follow them in order and do not skip the checks. **Installing a server for the user.** Pages, in order: [Install the server](https://automaton.monster/docs/install-server) (section "For AI agents"), [Create the first administrator](https://automaton.monster/docs/first-admin), [Install and authenticate the CLI](https://automaton.monster/docs/automaton-cli), [Add a Runner](https://automaton.monster/docs/deploy-runner). Overview for the user: [Install with your AI agent](https://automaton.monster/docs/install-with-ai). - Ask the user for the server's SSH access, the domain and the certificate email (also which email the administrator uses). - Never print, read back or store the generated administrator password. - Finish with the one-time link from `server password-link` and show it in your last message. **Starting an Automaton app.** Pages, in order: [Build an Automaton app](https://automaton.monster/docs/build-an-automaton-app), [Deploy and publish an app](https://automaton.monster/docs/deploy). Overview for the user: [Your first app](https://automaton.monster/docs/getting-started). - Wait until the app is `Running` before reporting; `deploy` has no `--yes` flag. - Publish only if the user asked. - A team server's API URL comes from its administrator; do not assume the public project website is the user's server. ## Working in an app folder Before implementing, read the generated `AGENTS.md`, `.automaton/agents/skills/automaton-app/SKILL.md` and `automaton.json`. They are the authoritative workflow for the project. Choose the language and tools from the user's requirements and the Runner environment; do not assume local tools exist on the Runner. For an existing remote app use `automaton link ` instead of creating another app. More options: [Deploy and publish an app](https://automaton.monster/docs/deploy). Never paste or print API keys, tokens or passwords. Self sign-up is disabled; users come only from an administrator. ## Documentation - [Install with your AI agent](https://automaton.monster/docs/install-with-ai): Give your AI agent a server, a domain and an email. It installs Automaton and hands you a one-time link to choose your password. - [Your first app](https://automaton.monster/docs/getting-started): Describe an app to your AI agent; it builds, deploys and runs it on your server. - [Automaton by the numbers](https://automaton.monster/docs/numbers): Measured install, update and restore times, server resources, and project activity. - [Install the server](https://automaton.monster/docs/install-server): Install Automaton (Studio, API, app store, PostgreSQL, RabbitMQ, Traefik) on one x86_64 Linux server with a domain, using one command. - [Create the first administrator](https://automaton.monster/docs/first-admin): Open the one-time setup link after installation, create the administrator account and sign in to Studio. - [Add a Runner](https://automaton.monster/docs/deploy-runner): Install a Machine Management Runner on a Linux host with the CLI so Automaton can run your apps. - [Build an Automaton app](https://automaton.monster/docs/build-an-automaton-app): The app contract in practice; static sites, servers with a runtime, durable data and what to tell a coding agent. - [Deploy and publish an app](https://automaton.monster/docs/deploy): Validate, deploy and publish an Automaton app from the CLI, check its status, set a marketplace listing, and delete it. - [Update the server](https://automaton.monster/docs/update-server): Update an installed Automaton server to the latest release from the CLI or Studio, with SSH as the fallback. - [Back up and restore](https://automaton.monster/docs/backup-restore): Nightly server backups, how to take and list one, and how to restore the database and keys from a backup. - [Secrets and permissions](https://automaton.monster/docs/secrets-and-permissions): Give an app its credentials with automaton secrets and request platform permissions that an administrator approves. - [Automaton CLI reference](https://automaton.monster/docs/automaton-cli): CLI commands for creating, linking, validating, deploying, and publishing Automaton apps. - [automaton.json reference](https://automaton.monster/docs/automaton-json): Reference for the Automaton app identity and generic build, runtime, health, and storage contract. - [Troubleshooting](https://automaton.monster/docs/troubleshooting): Fixes for apps that do not start after deploy, failing requests, login problems and blank pages. - [Current limitations](https://automaton.monster/docs/limitations): What Automaton does not do yet, stated specifically. - [Security overview](https://automaton.monster/docs/security): How an Automaton installation isolates apps, protects secrets and app permissions, audits changes, and what is out of scope. All of these pages in one file: https://automaton.monster/llms-full.txt # Automaton app contract ## Application contract The `application` object in `automaton.json` describes how Automaton builds, runs, checks, and stores the app. Automaton does not prescribe a language, framework, package manager, source layout, build system, or application architecture. ## Generic contract ```json { "application": { "build": { "image": "runtime-image:tag", "command": { "executable": "build-tool", "arguments": ["build"], "workingDirectory": ".", "environment": {}, "useShell": false }, "outputDirectory": "dist", "timeoutSeconds": 900 }, "runtime": { "image": "runtime-image:tag", "command": { "executable": "app-server", "arguments": [], "workingDirectory": ".", "environment": {}, "useShell": false }, "port": 8080 }, "health": { "path": "/health", "startupTimeoutSeconds": 60 }, "storage": { "dataDirectory": "data", "mounts": [] } } } ``` `build.command` is optional. When present, Automaton runs its executable with its arguments, working directory, environment, shell setting, and timeout. When `build.image` is omitted, `runtime.image` is used. `build.outputDirectory` is optional and identifies the build artifact to use. `runtime.command` names the process to start. It is required for a dynamic HTTP application. For a new container deployment it requires an explicit `runtime.image`; the image owns the application process. Historical stored contracts without an image remain readable, but must declare one before they can be deployed again. Those apps must provide or depend on an HTTP server in the runtime artifact and declare it through this contract. A static site instead declares a build command and `build.outputDirectory`, with no runtime command or Compose contract. Its build requires an explicit build image; the Machine Management Runner publishes only the declared output through a shared static serving container. Actions-only or other finite-work applications with no HTTP endpoint may also omit the runtime command. Automaton supplies the container listening port in `AUTOMATON_PORT`, regardless of the requested `runtime.port`. The process must listen on that port and bind to `0.0.0.0` (or the equivalent all-interfaces address) inside its container. Binding only to container loopback (`127.0.0.1`) is not reachable through Docker bridge publication. Runtime command fields use the same execution rules as the build command. Automaton forwards each HTTP request to the runtime. Small responses arrive complete; responses that stream (Server-Sent Events, or a body with no known length or larger than one message) are relayed to the browser chunk by chunk as the app writes them. WebSocket upgrades are relayed too. Request bodies are still sent whole and are limited in size. One app may hold a limited number of open streams and WebSockets at once, and one with no traffic for five minutes is closed, so send a heartbeat. Keep ordinary requests within the server's configured request timeout. `health.path` is optional. When configured, readiness requires a successful 2xx HTTP response on that path. `health.startupTimeoutSeconds` limits startup. `storage.dataDirectory` is the durable application data directory. Automaton supplies its path in `AUTOMATON_APP_DATA_DIR`. Data there survives runtime replacement. Build and runtime artifacts are disposable. `AUTOMATON_API_URL` is the server's public API base URL (no trailing slash), set in every runtime and Action. Use it to call the Automaton API with an approved permission key; do not ask the user for it or guess it from your own host. Use structured commands by default. Set `useShell` only when shell semantics are required. Keep every managed path inside the application workspace. Run `automaton validate`, then `automaton deploy`. Deployment is asynchronous. Success means the deployment operation reaches `Succeeded`. Publishing is a separate action; publish when authorized by the user's requested delivery. ## Linked guidance - `llms/automaton-mcp.md` - `llms/automaton-actions.md` - `llms/automaton-studio-editor.md` --- Source: https://automaton.monster/docs/install-with-ai # Install with your AI agent Your AI agent can take an empty server to a working Automaton in a few minutes. You do not run any commands. ## What you provide 1. **A server you can reach with SSH.** A Linux server (Ubuntu 22.04 or 24.04, or Debian 12) with at least 2 GB of memory. 2. **A domain with two DNS records** pointing to the server: `yourdomain.com` and `*.yourdomain.com`, DNS only (no proxy). 3. **An email address** for certificate notices. It is not an account. Your AI will also ask which email you want for your administrator account. ## What your AI does - Installs Automaton on your server. - Creates your administrator account. It never sees your password: it uses a temporary one that stops working when you choose yours. - Adds a Runner, the part of Automaton that runs your apps. - If you ask, builds and deploys a first app ([Your first app](./getting-started)). ## What you get A working Automaton with Studio at `https://studio.yourdomain.com`, and a one-time link in your AI's last message. 1. Open the link and choose your password. 2. Sign in to Studio with your administrator email and the new password. The link works once and for 30 minutes. If it expired, ask your AI for a new one. ## Try it Give your AI something like: > Install Automaton on my server `203.0.113.10` (SSH as root). My domain is `yourdomain.com`, certificate email `me@yourdomain.com`. Follow https://automaton.monster/llms.txt, then give me the link to choose my password. ## Technical details For people who want to run or check the steps, and for agents: - [Install the server](./install-server) (agent steps included) - [Create the first administrator](./first-admin) - [Add a Runner](./deploy-runner) - [llms.txt](https://automaton.monster/llms.txt) for agents - [Troubleshooting](./troubleshooting) --- Source: https://automaton.monster/docs/getting-started # Your first app You need an Automaton server with a Runner and an AI agent that can use it. [Install with your AI agent](./install-with-ai) sets all of that up. ## What happens 1. **You describe the app.** Say what it should do, in your own words. 2. **Your AI builds it.** It creates the app from a template, writes the code, and uses the Automaton CLI to deploy it. 3. **Your server runs it.** Automaton builds the app and runs it on your Runner. A few minutes later it has its own address, such as `https://my-app.apps.yourdomain.com`. 4. **It is private at first.** Only you can open it until you share it or publish it. In Studio, **Who can open it?** controls this. 5. **Publish if you want.** Ask your AI to publish the app to your app store. ## Try it > Build a small habit tracker with Automaton, deploy it to my server and tell me the address. Keep it private for now. Later: *"Publish it to my app store."* ## Technical details - [Build an Automaton app](./build-an-automaton-app) - [Deploy and publish an app](./deploy) - [Automaton CLI reference](./automaton-cli) - [Troubleshooting](./troubleshooting) --- Source: https://automaton.monster/docs/numbers # Automaton by the numbers These figures describe specific tests, not guarantees or a capacity benchmark. The server measurements below were recorded on 5–9 November 2026. Times include only the named step. | Measure | Result and scope | Source | | --- | --- | --- | | Server install | 118 seconds on Ubuntu 22.04; about 90 seconds on Debian 12, on separate Hetzner CX23 servers. On 9 November 2026 (server 0.2.82 to 0.2.85), fresh-server runs by agents using only the public docs took 94 seconds for the installer on Debian 12 and 2-3 minutes from the first command to a working server on Ubuntu 24.04 | [Fresh-server run notes](https://github.com/rzvdaniel/automaton/issues/561) | | Server size and price | CX23, 2 vCPU and 4 GB RAM, about €6.64 per month at the time of the run; domain and other services are extra | [Fresh-server run notes](https://github.com/rzvdaniel/automaton/issues/561) | | Disk and memory after install | About 1.0 GB of memory on the early runs and 1.1 GB on the 9 November run (server 0.2.82). The 9 November run used 4.1 GB of a 38 GB disk after install and 7.4 GB after adding a Runner and one catalog app; this was not a load test | [Fresh-server run notes](https://github.com/rzvdaniel/automaton/issues/561) | | Runner install | 31 seconds on Ubuntu 22.04; 32 seconds on Debian 12 | [Fresh-server run notes](https://github.com/rzvdaniel/automaton/issues/561), [Debian Runner check](https://github.com/rzvdaniel/automaton/issues/720) | | Server update | `automaton server update --wait` took 32 seconds from 0.2.21 to 0.2.22 on Ubuntu and 26 seconds from 0.2.28 to 0.2.29 on Debian | [Fresh-server run notes](https://github.com/rzvdaniel/automaton/issues/561), [Debian updater check](https://github.com/rzvdaniel/automaton/issues/719) | | Clean-server restore | Restore script completed in 13 seconds on a new Ubuntu 24.04 CX23 after the off-site archive had been downloaded and decrypted; transfer and setup time are excluded | [Restore drill](https://github.com/rzvdaniel/automaton/issues/524) | ## Supported systems The server installer supports x86_64 Linux with systemd. Ubuntu and Debian 12 are supported, with Docker installed automatically if needed; Ubuntu 22.04, Ubuntu 24.04 and Debian 12 have been tested as described in the [install guide](./install-server). Other distributions require Docker Engine and Compose preinstalled and have not been tested. ARM, offline and private-only installs are not supported. The Ubuntu 22.04 and Debian 12 timings above are individual runs, not a complete supported-system matrix. ## Project activity Counted on 6 November 2026 from the [GitHub releases](https://github.com/rzvdaniel/automaton/releases) and [closed issues](https://github.com/rzvdaniel/automaton/issues?q=is%3Aissue+is%3Aclosed+closed%3A%3E%3D2026-11-03), using 3 November 2026 at 00:00 UTC as the start: 47 releases published and 102 issues closed. These counts change as work continues. The [CI test run](https://github.com/rzvdaniel/automaton/actions/runs/42299474665) on 6 November 2026 reported 1,904 passing .NET tests and one skipped test across the Architecture, Worker, Runner, Core, API, CLI and AI test projects. This is a test run count, not a count of every check in the repository. --- Source: https://automaton.monster/docs/install-server # Install the server This page installs your own Automaton server on one x86_64 Linux machine. **Want an AI agent to do everything, from install to a link for your password? Use [Install with your AI agent](./install-with-ai).** This page is the reference for the installer. Next steps: [create the first administrator](./first-admin), then [add a Runner](./deploy-runner). Already have a team server? Ask its administrator for the API URL and go to [Your first app](./getting-started). Measured times, memory and cost are in [Automaton by the numbers](./numbers): about 94 seconds for the installer on Debian 12, 2-3 minutes from the first command to a working server on Ubuntu 24.04. These are observations, not guarantees or minimum requirements. ## Prerequisites - An x86_64 Linux server with systemd, sudo access, outbound internet, at least 2 GB of RAM and 10 GiB of free disk space. Ubuntu and Debian 12 are supported (Ubuntu 22.04, Ubuntu 24.04 and Debian 12 have been tested); the installer adds Docker itself there. On other distributions, [install Docker Engine and the Compose plugin](https://docs.docker.com/engine/install/) first (not tested). - `curl` and `python3` (3.8 or newer). `jq` is needed only for the headless snippets in [first administrator](./first-admin) and [CLI login](./automaton-cli#log-in-without-a-browser); fresh Ubuntu and Debian lack it: `sudo apt-get install -y jq`. - A domain you can edit DNS for, and an email for certificate notices (passed as `--email`; Let's Encrypt uses it for the HTTPS certificates; it is not an account). - Inbound TCP ports 80 and 443 (HTTPS) and 5671 (TLS Runner broker). Keep the `api` hostname directly reachable on 5671. - DNS A records pointing to the server's public IP, set to DNS only (no proxy). Two records are enough: `example.com` and `*.example.com`. Alternatively one record per name: `api`, `studio`, `store`, `apps` and `*.apps` under `example.com`. Private-only, offline and ARM installs are not supported. ## Install 1. Download the installer and read it. ```bash curl -fL https://raw.githubusercontent.com/rzvdaniel/automaton/main/scripts/install-automaton.sh -o install-automaton.sh less install-automaton.sh ``` 2. Run it with your domain and email. The installer first checks that DNS resolves to this server and stops with the records to fix if it does not. ```bash sudo bash install-automaton.sh --domain example.com --email owner@example.com ``` 3. Wait for it to finish. It installs Docker if missing, downloads the latest stable release, verifies its checksum, starts the stack and prints a one-time first-administrator setup link. Open that link in the next step. The link contains a live setup token: do not paste the terminal output into chats, tickets or logs. To keep it out of a log or an agent's output, run the installer with output to a private file (`umask 077; sudo bash install-automaton.sh ... > ~/install.out 2>&1`), read only what you need, then delete the file. Or add `--quiet`, which stops the installer printing the link (it then points to the token file). You never need the link itself: the token is also stored in `/etc/automaton/secrets/setup-token` and [First administrator](./first-admin) reads it from there. Options: `--version server-vX.Y.Z` pins a release; `--public-ip ADDRESS` is for servers behind NAT (repeat for IPv6); `--tls-mode cloudflare` uses a Cloudflare wildcard certificate (enter the DNS token only at the hidden prompt). Never pass secrets as command arguments. ## For AI agents Ask the user only for what they alone can give: root SSH access, the domain (with `domain` and `*.domain` A records, DNS only) and the certificate email; also ask which email the administrator uses (default: the certificate email). Run all agent steps in one shell, as root: the generated password lives only in a shell variable and `PATH` is set for the CLI. Never print, read back or store the password. Do not paste output containing a token or link into chat. ```bash DOMAIN=example.com CERT_EMAIL=owner@example.com EMAIL=$CERT_EMAIL # administrator sign-in; use another email if the user asked API=https://api.$DOMAIN ``` 1. Install the tools. Fresh Ubuntu and Debian lack `jq`; a quiet install can fail without a message, so install visibly and check it. ```bash apt-get update && apt-get install -y jq curl python3 libicu-dev jq --version ``` Check: `jq --version` prints a version. If not, stop and fix apt. 2. Install the server with `--quiet`, which keeps the setup link out of the output (the installer checks DNS first and installs Docker itself, about 94 s on Debian 12). ```bash curl -fL https://raw.githubusercontent.com/rzvdaniel/automaton/main/scripts/install-automaton.sh -o install-automaton.sh bash install-automaton.sh --domain "$DOMAIN" --email "$CERT_EMAIL" --quiet ``` Check: `curl -fsS "$API/health"` returns HTTP 200 with `"status":"ok"`. If DNS fails, tell the user which names do not resolve, wait, and rerun the same command. Two A records were enough in every test run; if `apps.domain` itself does not resolve, add it too. Then, in the same shell: [create the administrator](./first-admin#when-an-ai-agent-sets-up-the-server-for-you), install the CLI with the install script, no .NET needed (`curl -fsSL https://github.com/rzvdaniel/automaton/releases/latest/download/install.sh | sh`, then `export PATH="$HOME/.local/bin:$PATH"` in this shell; on Windows `irm https://github.com/rzvdaniel/automaton/releases/latest/download/install.ps1 | iex`) and [sign it in without a browser](./automaton-cli#log-in-without-a-browser), [add a Runner](./deploy-runner#for-ai-agents), build and deploy any requested app ([Build an Automaton app](./build-an-automaton-app)), and last run `server password-link` and show the one-time link in your final message ([first-admin](./first-admin#when-an-ai-agent-sets-up-the-server-for-you)). Do not finish before the link is shown. The whole path takes a few minutes. ## Verify it worked ```bash curl -fsS https://api.example.com/health ``` Expected: HTTP 200 with JSON such as `{"status":"ok","version":"server-vX.Y.Z"}` where the version is the release you installed (versions before 0.2.22 return plain `ok`). Then open `https://studio.example.com` and continue with [Create the first administrator](./first-admin). ## Set up email A fresh server cannot send email, so password reset emails and email invitations reach nobody until you set it up (people can still be created with a password). Studio shows system administrators a notice until email is configured. Sign in as a system administrator, open **Settings**, find **Outgoing email** on the server section, and enter the SMTP host, port, TLS setting, account, password and sender address from your mail provider. Select **Save SMTP settings**, then **Check connection**; the notice disappears once the settings are saved. A successful check proves login to the mail server, not that a message reaches an inbox, so use **Send test email** to a real address. ## Common errors - **DNS check fails.** The installer lists the names that do not resolve to this server. Fix the records, wait for DNS, rerun the same command. Proxied (orange cloud) records break certificates and the Runner broker. - **`automaton: command not found`.** The install script put it in `~/.local/bin`, which is not on `PATH` in this shell: run `export PATH="$HOME/.local/bin:$PATH"`. - **`jq: command not found`.** The agent tools step did not finish; rerun it and read its output. - **Port in use or different installation found.** The installer refuses to take over. Free the port or use a clean server. - **Certificate not issued.** Port 80 must be reachable from the internet. If the installer reports a Let's Encrypt `429 rateLimited` error, the same host names have used their weekly limit (five certificates in seven days; repeated installs on one domain use it up). Wait until the retry time it prints or use other host names, then rerun the same command: if HTTPS is not answering it restarts Traefik so the certificates are requested again. - **Retrying.** Rerun the same command with the same domain and email; generated credentials are kept. Never run `docker compose down -v`: it deletes data. For custom paths, protected settings, adopted installs and recovery, see the [operator reference](https://github.com/rzvdaniel/automaton/blob/main/docs/prod/install-automaton.md). To update or restore later: [Update the server](./update-server), [Back up and restore](./backup-restore). --- Source: https://automaton.monster/docs/first-admin # Create the first administrator This page creates the administrator of a freshly installed server. Self sign-up is disabled on purpose; every other user is created or invited by an administrator in Studio. ## Prerequisites A server installed with [Install the server](./install-server) (`/health` returns HTTP 200 with `"status":"ok"`). ## Steps 1. Open the one-time setup link the installer printed. It fills in the setup token and Studio removes it from the address bar. 2. If you lost the link, open `https://studio.example.com/install` and paste the token from the server: ```bash sudo cat /etc/automaton/secrets/setup-token ``` 3. Select **Check installation**, then enter your name, email and a password of at least 12 characters with uppercase, lowercase, a number and a symbol. 4. Sign in with that account. **The email you type in step 3 is the administrator's sign-in.** Nothing on the server stores it for you. `/etc/automaton/installation.json` holds only the certificate email, which Let's Encrypt uses for expiry notices; it is not an account and signing in with it gives 401. If you forgot which email you used, check the confirmation you saved or ask whoever ran the setup; an AI agent must ask the person, not guess. SMTP is not needed to start: the first administrator's email is confirmed by this setup flow. The setup page does not reopen once setup is complete. ## Without a browser (SSH or an AI agent) Studio's setup page calls two endpoints, and you can call them yourself. The snippet below needs Bash (for process substitution) and `jq`, which a fresh Ubuntu 24.04 installation does not include; install it first with `sudo apt-get install -y jq`. Run this on the server, in a shell where the password is typed at a hidden prompt, never given as an argument and never echoed: ```bash API=https://api.example.com read -rp "Administrator email: " EMAIL read -rp "Name: " NAME read -rsp "Password (12+ characters, upper, lower, number, symbol): " PASSWORD; echo # No terminal (an AI agent or a script)? Read it from a file only you can read instead: # PASSWORD=$(< /root/admin-password); (create it with: umask 077; read -rs p; printf %s "$p" > /root/admin-password) # or from standard input: IFS= read -r PASSWORD # An agent generates a compliant password into the variable, never echoed and never in a file: # PASSWORD=$(printf 'Aa1!'; openssl rand -base64 24 | tr -d '\n=') # then see "When an AI agent sets up the server" # 1. Optional: ask whether setup is open and whether a token is required. curl -s "$API/api/install/status" # 2. Create the first administrator. The setup token goes from the file into a header # through a pipe, so it is never printed or stored in your shell history. jq -n --arg name "$NAME" --arg email "$EMAIL" --arg password "$PASSWORD" \ '{name: $name, email: $email, password: $password}' | curl -s -w '\nHTTP %{http_code}\n' -X POST "$API/api/install" \ -H @<(printf 'X-Automaton-Setup-Token: %s\n' "$(sudo cat /etc/automaton/secrets/setup-token)") \ -H 'Content-Type: application/json' --data-binary @- unset PASSWORD ``` `POST /api/install` takes `name`, `email` and `password` and returns 200 when the account exists. A wrong or missing token returns 403 ("A valid server setup token is required."); a password that is too weak returns 400 with the reason; 409 means setup was already completed. `POST /api/install/checks` (same header) runs the installation checks that Studio shows after the first sign-in. The token the installer printed is single-purpose: when setup completes the server replaces the content of `/etc/automaton/secrets/setup-token` with a new random value that is never printed, so the printed one stops working. Re-running the installer or the updater does not print or reset it. The new value is what lets root create [one-time password links](#when-an-ai-agent-sets-up-the-server-for-you). If you edit the token file by hand, restart the API afterwards (it is mounted into the container as one file). `EMAIL` and `PASSWORD` in the snippet are the sign-in you are creating now: the email you chose, not the certificate email. Keep both: the CLI sign-in below asks for them again. Then sign in the CLI without a browser: see [Log in the CLI on a server without a browser](./automaton-cli#log-in-without-a-browser). If an AI agent or script created the administrator this way, finish with the one-time link: run `server password-link` and show the printed link to the user, as in [When an AI agent sets up the server for you](#when-an-ai-agent-sets-up-the-server-for-you). Do not end before the link is shown. The password never goes on the command line: `jq --arg` receives it from the shell variable. If you keep it in a file, make it mode 0600 and delete it afterwards. ## When an AI agent sets up the server for you The agent steps are in [Install the server](./install-server#for-ai-agents). An agent needs a signed-in CLI to deploy, so it creates the administrator itself with a password it generates and never prints or writes to a file, used in the same shell, then `unset`. In the same shell as the install, with `API` and `EMAIL` set: ```bash PASSWORD=$(printf 'Aa1!'; openssl rand -base64 24 | tr -d '\n=') jq -n --arg name "Administrator" --arg email "$EMAIL" --arg password "$PASSWORD" \ '{name: $name, email: $email, password: $password}' | curl -s -w '\nHTTP %{http_code}\n' -X POST "$API/api/install" \ -H @<(printf 'X-Automaton-Setup-Token: %s\n' "$(cat /etc/automaton/secrets/setup-token)") \ -H 'Content-Type: application/json' --data-binary @- ``` Check: the output ends with `HTTP 200` (`403`, `400` and `409` are explained above). Keep `PASSWORD` for the [CLI sign-in](./automaton-cli#log-in-without-a-browser), then `unset` it. It ends with a one-time link for the user. Run this last, after any app you were asked to build, so the 30-minute link is fresh: ```bash "$(command -v automaton)" server password-link --email "$EMAIL" ``` As a non-root user, prefix it with `sudo`. Show the printed link in your final message, together with `https://studio.`. It is a link to Studio's set-password page; if it is not, stop. The link works once and for 30 minutes. Opening it asks for a new password; the generated one stops working at that moment and the user signs in with theirs. If the link expires or is lost, run the command again: each new link voids the earlier ones. Only someone who can read `/etc/automaton/secrets/setup-token` (root) can create it, it needs no sign-in and no email (SMTP), and it works only for administrator accounts. The command finds the server from `/etc/automaton/installation.json`, so it works under `sudo` without the CLI's sign-in; use `--config-dir` if the configuration is elsewhere and `--server ` to name another server. Treat the link like a password until it has been used. ## Verify it worked Studio opens after sign-in and **Admin → Installation** shows **Check installation** results. Public DNS and open ports show as unverified there; check them from another network. ## Common errors - **Token rejected.** Use the current value from `/etc/automaton/secrets/setup-token`. After setup is complete the printed token no longer works; the file holds a new value for root. - **Setup returns 409.** The administrator already exists. Run `server password-link` to give the user a link. - **Link rejected or rate limited.** Creating and using links is limited to 5 per hour per IP; wait and run the command again. - **Password refused.** Use 12 or more characters with all four character kinds. Next: [add a Runner](./deploy-runner). --- Source: https://automaton.monster/docs/deploy-runner # Add a Runner A Runner is the trusted service on a machine that builds and runs your apps in Docker. Every app needs one. This page installs one from the CLI. ## Prerequisites - A server with an administrator ([first administrator](./first-admin)) and the CLI signed in: `automaton login --server https://api.example.com` (see [Install with your AI agent](./install-with-ai) or [Your first app](./getting-started)). - An x86_64 Linux host running **Ubuntu or Debian** (apt based, systemd) with Docker Engine running. It can be the Automaton server itself or another machine. The install does not check the distribution name: it needs a Linux host, a running Docker daemon and `/var/run/docker.sock`, so other distributions with Docker may work but are not tested. Otherwise it stops and names what is missing. - The Runner controls the host's Docker. Install it only on machines you trust. ## Steps The `automaton` commands below need a signed-in CLI. On a host without a browser run `automaton login --device --server https://api.example.com`, then approve the printed URL and code from a signed-in session or with the terminal snippet in [Log in without a browser](./automaton-cli#log-in-without-a-browser). The `--device` output contains the code: do not share it. Pick the one that matches where you run the command. ```bash # On the Runner host itself (same host as the server: the usual case) automaton runners install --name main --local --yes # From your computer, over SSH automaton runners install --name main --ssh user@host # Print the one-time command and run it yourself before it expires automaton runners install --name main --print-command ``` For `--ssh`, the CLI runs the `ssh` executable found on your PATH. On Windows, use Windows OpenSSH (`C:\Windows\System32\OpenSSH\ssh.exe`) on PATH; a Git Bash SSH wrapper can fail with this command. The CLI asks you to confirm the Docker access. Add `--yes` for automation (required when not interactive). Add `--project NAME` to also add the Runner to a project. The command waits up to 300 seconds for the Runner to connect (`--timeout` changes this) and prints its Runner ID. `--print-command` also asks for confirmation, so give it `--yes` when there is no terminal, and it then waits for the Runner to enroll: run the printed command on the host within `--timeout` seconds (default 300), or the CLI times out. Use `--json` with `--ssh` or `--local` for `{runnerId, name, status}`. You can also use Studio: **Runners → Install Machine Management Runner**. ## For AI agents On the server host, in the shell used for the install, with the CLI signed in: ```bash automaton runners install --local --yes --name main ``` Check: `automaton runners` lists `main` as connected or online (about 30 s). ## Verify it worked ```bash automaton runners ``` Expected: your Runner listed as online. (The CLI says "connected" where Studio says "online"; both mean the Runner is reachable.) Studio shows the same under **Runners**. With exactly one online Runner, `automaton deploy` uses it automatically. With several, choose with `automaton deploy --runner NAME` or `automaton set-runner NAME`. List a project's Runners with `automaton project runners`. ## Common errors - **Docker not running on the host.** Start Docker, rerun the command. - **Times out waiting for connection.** Port 5671 on the `api` host must be reachable from the Runner machine. See [Install the server](./install-server). - **Enrollment command expired.** Run `automaton runners install` again; each command is single use. Host requirements and the trust boundary are in the [Runner reference](https://github.com/rzvdaniel/automaton/blob/main/docs/prod/self-hosted-runners.md). Next: [deploy an app](./deploy). --- Source: https://automaton.monster/docs/build-an-automaton-app # Build an Automaton app An app is a folder with `automaton.json` and source in any language. This page shows the two common shapes. The complete field list is the [automaton.json reference](./automaton-json). ## Prerequisites A server with a Runner and a signed-in CLI ([Install with your AI agent](./install-with-ai)), or your administrator's API URL (see [Install and authenticate](./automaton-cli#install-and-authenticate)). ## Create the app 1. Check the CLI and the server. ```bash automaton whoami automaton runners ``` Check: your server is shown and a Runner is connected. `automaton: command not found` means its folder is not on `PATH` (`~/.local/bin` from the install script, `%LOCALAPPDATA%\Automaton\bin` on Windows, `~/.dotnet/tools` for the NuGet tool). With several servers the new login stays a separate profile; see [several servers](./automaton-cli#several-servers). 2. Create the app in an empty folder. `--template` is `node`, `python`, `dotnet` or `static`; omit it for an empty contract. ```bash mkdir my-app && cd my-app automaton new "My App" --template node ``` This writes `automaton.json`, `AGENTS.md` and `.automaton/agents/skills/automaton-app/SKILL.md`, and creates the app on the server. For an existing app use `automaton link `; for missing guidance files use `automaton sync-ai`. 3. Edit. Read `AGENTS.md` and `automaton.json` first. Keep data that must survive redeploys in the app's data directory (the template's `AGENTS.md` and `automaton.json` name the exact variable, `AUTOMATON_APP_DATA_DIR`). Other files are replaced on every deploy. 4. Validate and deploy as described in [Deploy and publish an app](./deploy). `deploy` has no `--yes` flag, can print `Deploy complete` before the app is running, and you must wait for `Running` in `automaton apps` before you publish or open the address. ## Static site Build files, no server process. `automaton new "My Site" --template static` creates this shape: a build command that writes the files into `outputDirectory`, and a health file. ```json { "name": "My Site", "application": { "build": { "command": { "executable": "sh", "arguments": ["-c", "mkdir -p dist && cp index.html health.txt dist/"] }, "outputDirectory": "dist" }, "health": { "path": "/health.txt" } } } ``` Publishing allows only public file types (such as `.html`, `.css`, `.js`, `.txt`), so the health file is `health.txt`. Runners serve static output from one shared static server. Static sites cannot use [secrets](./secrets-and-permissions). ## Server with a runtime For APIs, server-rendered sites and anything with logic. Declare `application.runtime.command`; the process must listen on `AUTOMATON_PORT` and answer `application.health.path`. Store durable files under `AUTOMATON_APP_DATA_DIR`. `--template node|python|dotnet` creates a working example. Server-Sent Events and WebSockets work: responses stream to the browser as the app writes them, and WebSocket upgrades are relayed. Each app can hold a limited number of open streams and sockets at once (20 by default), and an idle one is closed after five minutes, so send a heartbeat. ## Deploy and verify ```bash automaton validate automaton deploy ``` Wait for `Succeeded` and verify the real behavior at the URL. See [Deploy and publish an app](./deploy). ## For coding agents `automaton new` writes `AGENTS.md` and `.automaton/agents/skills/automaton-app/SKILL.md`. An agent should read them and `automaton.json`, keep the contract accurate, run `automaton validate` then `automaton deploy`, wait for `Succeeded`, and verify the app before reporting success. Do not paste API keys into the conversation. Machine-readable entry point: . ## Common errors - **Build tools exist locally but not on the Runner.** Set `application.build.image` to an image that has them. - **`Template would overwrite files`.** Use an empty folder or omit `--template`. - **Login does nothing.** The server URL must be the API URL, not Studio. - **Deploy works, app not Running.** Read `automaton logs`; the app must listen on `AUTOMATON_PORT` and answer its health path. - **Port mismatch.** Listen on `AUTOMATON_PORT`, not a fixed port. - More: [Troubleshooting](./troubleshooting). --- Source: https://automaton.monster/docs/deploy # Deploy and publish an app This page ships an app to a Runner and makes it public. Deploy and publish are separate: deploy builds and starts the app, publish gives it a public URL. ## Prerequisites - An app folder with `automaton.json` ([Build an Automaton app](./build-an-automaton-app); overview in [Your first app](./getting-started)). - At least one online Runner ([Add a Runner](./deploy-runner)). ## Steps 1. Validate and deploy. The deployment is asynchronous; the `Deploy complete` line means the package was applied, not that the app is running. Wait for `Running` in `automaton apps`. ```bash automaton validate automaton deploy ``` With one online Runner it is assigned automatically. With several, add `--runner NAME`. `automaton deploy ` selects a remote deployment target; `--env NAME` selects a local project profile, not a remote target. `deploy` has no `--yes`: a bare `--yes` is read as an environment name. 2. Check the result. `automaton status` shows the linked app and publish settings, but does not show the deployment state; `automaton apps` shows it in the Status column. ```bash automaton status automaton logs automaton apps ``` 3. Publish when it is within what was asked for. `--yes` skips the prompt (use it for automation after approval); `--slug` sets the public URL name. Publishing waits up to a minute for the TLS certificate. If none arrives, publish says the certificate request failed and why (for example Let's Encrypt's weekly limit); the server's administrator fixes that as the install guide's HTTPS troubleshooting describes, then you publish again. ```bash automaton publish --yes --slug my-app ``` The *private address* is the `app-.apps...` URL that `automaton status` shows. It needs about a minute for its certificate, and it needs sign-in: an anonymous `curl` gets `401`, so check a private app in a signed-in browser. A static site (`--template static`) has no process behind it, so that URL answers `503` until you publish; after `automaton publish` the public URL answers. Open `https://my-app.apps.` once published and check the real behavior, not only the status. `automaton deploy --publish` deploys and publishes in one step. ## Verify it worked Open the printed URL and check the app's real behavior, not only that the deployment succeeded. `automaton installation-status` shows the server version, counts and last backup. ## Marketplace listing A published app is listed in your server's app store by default. Set how it appears: ```bash automaton listing set --summary "A bluffing game about real words" --category Games --tags "game,multiplayer" automaton listing cover cover.png automaton listing add-screenshot lobby.png automaton listing show ``` Or put `"summary"` and `"description"` at the top level of `automaton.json`: `automaton deploy` copies them to the listing while the app has none set in Studio or with `automaton listing`. Every server lists installable apps under **Apps → Catalog**. The list is read from the official [automaton-apps repository](https://github.com/rzvdaniel/automaton-apps) (its generated `catalog.json`, in the root of the repository's `main` branch); nothing is preinstalled, so the server needs to reach `raw.githubusercontent.com` (GitHub sees the server's IP address and nothing else about it). The server keeps the last list it read, so a short GitHub outage leaves the tab working with a notice; if it never read a list, the tab says so and offers **Retry**. An entry that needs a newer server version says so instead of offering Install, and one you already installed shows **Installed** with a link to **Manage**. **Catalog sources.** A system administrator can add more sources under **Apps → Catalog → Catalog sources**: a GitHub repository (`owner/name` or `https://github.com/owner/name`) with a `catalog.json` in the root of its `main` branch (another branch can be named). The server reads the file when you add the source and refuses a source it cannot read. Each entry shows the name of its source, and entries always install from that source's own repository. For a private repository, paste a fine-grained, read-only token (Contents access to that one repository): it is stored encrypted, used only by the server to read the catalog and to fetch an app that source lists at its listed commit, and is never shown again. Turn the official source off with its switch, or select the trash icon to remove a custom source: its entries disappear, apps already installed from it keep running. Only add sources you trust: an app is code that runs on your Runners. An installer sees the source, the pinned commit and the requested permissions before anything is created, permissions always need a system administrator's approval, and nothing updates automatically. Regular users and plain Admins cannot manage sources (the API answers 403). To install, choose an entry and select **Install**, or paste a public GitHub address (`https://github.com/owner/name`, optionally `/tree/branch/folder`, where the branch name has no slash) under **Install from a Git address**. Studio shows the source repository, the pinned commit (the server fetches exactly that commit) and the requested permissions, then asks for the name, Runner, settings, secrets and schedules. A system administrator can approve the permissions right there. **Install** creates your own copy, deploys it on a Runner and shows each step. This needs an online Runner ([Add a Runner](./deploy-runner)). Installing the same app again makes an independent copy. Updates are never applied automatically. From a terminal or an AI agent: `automaton catalog list` shows the entries with their ids, and `automaton catalog install --name NAME --runner RUNNER --setting NAME=VALUE --secrets-file FILE --wait` installs one, saves its settings and secrets and deploys it. Secrets come only from a file, standard input (`--secrets-stdin`) or a hidden prompt, never from an argument. See the [CLI reference](./automaton-cli). Installing a tool app that another user published on this server: the store's **Install** button opens the same install page. To let other people install a copy of your published app on their server, publish with `automaton publish --install` (`--forkable` still works). The store then shows an **Install** button; an app with no web page (Actions only) shows only **Install**, an installable web app shows **Open** and **Install**. `automaton deploy` and `automaton publish` refuse an app that declares no runtime command, static site or Action. Summary up to 200 characters, description up to 500. Images are PNG, JPG, WEBP or GIF up to 2 MB: one cover and up to 8 screenshots. Remove one with `automaton listing remove-screenshot `. The same settings are in Studio under the app's Settings. ## Delete an app ```bash automaton delete --yes ``` This permanently deletes the linked remote app, its runtime and its data. Without a linked folder (for example a catalog-installed app), use `automaton apps delete --yes`; it deletes any app you may delete. ## Pause and resume an app Pause an app to stop it without deleting it, for example while a service it depends on is gone. ```bash automaton pause # the linked app; or: automaton apps pause automaton resume # or: automaton apps resume automaton deploy --resume # resume first, then deploy ``` Pausing stops the app on its Runner, switches off its schedules and makes its public address answer 503 with a short "This app is paused" page. Its settings, secrets, data, publication and address are kept. Resuming starts it again and turns back on the schedules that were on before the pause. `automaton apps` shows `Paused` as the status. In Studio use **Pause** / **Resume** in the Apps list or on the app's Overview. A deploy while the app is paused uploads the new version but keeps the app stopped and says so; use `--resume` (or resume first) to start it. You need the same rights as for deploying the app; others get 403. An API key works too: `POST /api/apps/{id}/pause` and `POST /api/apps/{id}/resume`. ## Common errors - **No Runner available.** Deploy can upload but cannot start the app. Add or start a Runner, then deploy again. - **Health check fails.** The app must listen on `AUTOMATON_PORT` and answer the path in `application.health.path`. Read `automaton logs`. - **Secrets missing.** See [Secrets and permissions](./secrets-and-permissions). - More: [Troubleshooting](./troubleshooting). --- Source: https://automaton.monster/docs/update-server # Update the server This page updates the API, Worker, Studio and app store of your server to a newer release. Services can be briefly unavailable. Apps keep running on their Runners. ## Prerequisites - You are a system administrator, and the CLI is signed in to the server: `automaton login --server https://api.example.com`. - A server installed with the current installer. An older one needs the SSH fallback below once. - Servers still on MySQL cannot take this update; move them first with the [migration guide](https://github.com/rzvdaniel/automaton/blob/main/docs/prod/migrate-to-postgresql.md). ## Steps 1. Update and wait. ```bash automaton server update --wait ``` Pin a release with `--version server-vX.Y.Z`. The API restarts during the update; `--wait` follows it, then checks `/health` and the installed version, and exits nonzero on failure, printing the cause (the last line of `update.log`), the log tail and the recovery backup. The full log is on the server at `/var/lib/automaton/update/status/update.log`. `--timeout SECONDS` defaults to 1800. 2. In Studio instead: **Admin → Installation → Update** (the **Update to the latest release** button). 3. Upgrade the Runners. A server update does not change installed Runners; each keeps its old image until upgraded. From the CLI, `automaton server update --runners` waits for the update, names every Runner with its state and upgrades the ones on an old image (with only `--wait`, it lists them with the command to upgrade each; `automaton runners upgrade RUNNER --wait` needs a Developer API key). In Studio, open **Runners**: a Runner on an old image shows **Update available**; select **Upgrade → Upgrade now**, and Studio says whether it reconnected or rolled back. An offline Runner, or one busy with a deploy or an Action, cannot upgrade; retry when idle, or use **Run it manually** (shown there) for a Runner too old to upgrade itself. 4. Check the last run any time. ```bash automaton server update-status ``` A release older than the installed one is refused unless you pass `--allow-downgrade` (Studio does not offer it). ## Fallback over SSH ```bash sudo bash /opt/automaton-server/update.sh # latest sudo bash /opt/automaton-server/update.sh --version server-vX.Y.Z ``` ## Verify it worked ```bash curl -fsS https://api.example.com/health automaton installation-status ``` Expected: `ok`, and `Version:` shows the new release. ## Common errors - **Not picked up within two minutes.** On the server run `systemctl status automaton-update.path`. - **HTTP 409.** Another update is running; wait for it. - **Update failed.** Read `/var/lib/automaton/update/status/update.log` (also shown by `automaton server update-status`). The previous release and a recovery backup are kept; the log names the backup. Do not rerun blindly. Restore with [Back up and restore](./backup-restore) if a migration already ran. How the trigger works (the API never runs as root), adopted installs and recovery: [operator reference](https://github.com/rzvdaniel/automaton/blob/main/docs/prod/install-automaton.md#update-an-installed-server). --- Source: https://automaton.monster/docs/backup-restore # Back up and restore This page covers the server's database and keys. It does not cover files that apps keep in their own data directories. ## Prerequisites SSH access with sudo on the server. ## What you get by default The installer turns on a nightly backup between 03:00 and 04:00 server time and keeps the newest 7 in `/opt/automaton-server/backups/nightly`. Each holds a PostgreSQL dump and the keys that decrypt it. The **Backups** page in Studio shows the last, next and offsite backup, lists them, gives the restore command and has a **Back up now** button for administrators (CLI: `automaton server backup`, and `automaton server backup-status` for the next scheduled run). ## Take or list a backup ```bash sudo bash /opt/automaton-server/backup.sh # back up now sudo bash /opt/automaton-server/backup.sh --list # list backups sudo systemctl list-timers automaton-backup.timer # next scheduled run ``` These backups live on the same server. Also turn on your hosting provider's backups and copy `/opt/automaton-server/backups/nightly` and `/etc/automaton` to encrypted storage elsewhere. The **Off-site Backup** app is available in **Apps → Catalog**. Install and configure it with your own storage destination if you want automated encrypted copies. Keep its backup key separately so a clean server can decrypt an archive. ## Restore Restoring replaces the whole database and keys. Everything written after that backup is lost. ```bash sudo bash /opt/automaton-server/restore.sh automaton-20261101T031500Z ``` It verifies checksums, asks you to type the server's domain (for automation, `--confirm example.com`), saves the current state as a new backup, stops the services, restores, starts and checks Studio. A backup from an older server version restores onto a newer one; the reverse is not supported. To move to a new server, install the same release there, copy a backup folder into `/opt/automaton-server/backups/nightly/`, restore it, and copy `/etc/automaton` if existing sessions and Runners must keep working. ## Restore on a clean second server from an off-site archive Proven on a fresh server: install the same release (or newer) there, then: 1. Download the newest `.tar.age` archive from your off-site storage. 2. Decrypt it with the backup key (`/etc/automaton/secrets/backup-key` on the old server, which you saved separately) using the release's age image: ```bash AGE_IMAGE=$(grep -m1 '^AGE_IMAGE=' /opt/automaton-server/release.env | cut -d= -f2-) docker run --rm -v "$PWD":/data "$AGE_IMAGE" age -d -i /data/backup-key -o /data/backup.tar /data/automaton-YYYYMMDDTHHMMSSZ.tar.age ``` 3. Extract it into the backups folder: `sudo tar -xf backup.tar -C /opt/automaton-server/backups/nightly/`. It then shows in `backup.sh --list`. 4. Restore it: `sudo bash /opt/automaton-server/restore.sh automaton-YYYYMMDDTHHMMSSZ --confirm your-domain`. Users, projects, Runners and secrets come back, and new deployments work. Runners installed before the backup stay attached to the old server until you re-enroll them. ## Verify it worked ```bash curl -fsS https://api.example.com/health automaton installation-status ``` Expected: `ok`, and your users and apps are counted. ## Common errors - **Studio shows a Backups error.** The last backup failed or is over 36 hours old. Check `sudo journalctl -u automaton-backup.service`. - **Restore refuses.** Pick a name from `--list`; it must match a folder in `backups/nightly`. Details: [operator reference](https://github.com/rzvdaniel/automaton/blob/main/docs/prod/install-automaton.md#backups-and-restore). --- Source: https://automaton.monster/docs/secrets-and-permissions # Secrets and permissions This page covers how an app gets credentials (secrets) and platform access (permissions). The field reference is in [automaton.json](./automaton-json#app-secrets); the security model is in the [security overview](./security). ## Prerequisites An app folder linked to an app on your server ([Your first app](./getting-started)). ## Give an app a secret 1. Declare it in `automaton.json` under `application.secrets`, for example `{ "name": "STRIPE_KEY", "description": "Stripe secret key" }`. 2. Set the value. It is read from standard input (or a hidden prompt), never from arguments, so it stays out of shell history and AI chat. ```bash automaton secrets set STRIPE_KEY automaton secrets set SHARED_KEY --project # project vault, shared by the project's apps automaton secrets set MY_KEY --user # your personal vault, for personal apps ``` 3. Redeploy so a running app picks it up, and read it from the environment in code. ```bash automaton deploy ``` ## Request a platform permission Declare `application.permissions` (`backups:read`, `server:read` and `notify:email`), deploy, then show the state: ```bash automaton permissions status ``` A system administrator approves it in Studio (the command prints the approval link). Nothing is granted before that, and revoking stops the next call. ### Email people with `notify:email` With `notify:email` approved, the app can ask the server to email through the server's own SMTP (Admin > Settings); it never sees SMTP credentials. Call it with the app's key: ```bash curl -X POST "$AUTOMATON_API_URL/api/notify/email" \ -H "Authorization: Bearer $APP_AUTOMATON_API_KEY" \ -H "Content-Type: application/json" \ -d '{"to":["administrators"],"subject":"Disk almost full","text":"Only 2 GB left."}' ``` `to` takes `owner` (the app's owners), `administrators`, `project` (the members of the app's project) and addresses an administrator put on the server's allowed list (`PUT /api/admin/installation/notify-recipients`). Any other address gets 403; apps cannot email arbitrary addresses, so an app that needs that brings its own SMTP or uses a webhook. The subject is prefixed with `[app name]`, the text is plain text (up to 10,000 characters), and each recipient gets their own message. An app can send 10 emails per hour (429 with `Retry-After` beyond that). Answers: 200 `{"sent":n}`, 400 invalid request, 403 not approved or address not allowed, 404 no recipient resolved, 409 SMTP not configured. Every send and refusal appears in Admin > Audit Events (subject and counts, never the text). ## Verify it worked ```bash automaton secrets status ``` Every declared secret shows `present` and the scope that provides it. `automaton validate --strict` fails when one is missing. ## Common errors - **Secret shows `missing`.** Set it in the right scope (`--project` needs `automaton project add-app` first). - **Static site cannot use secrets.** Its files go to the browser. Put secrets in an API or server-rendered app. - **Values are never shown again.** Saving the same name replaces the value. --- Source: https://automaton.monster/docs/automaton-cli # Automaton CLI The CLI manages the local project link and sends the configured application to Automaton. Read `automaton.json` and the generated local guidance before editing. ## Install and authenticate Use the API URL from [your server installation](./install-server) or your team administrator. The CLI is a single file that needs no .NET. On Linux or macOS: ```bash curl -fsSL https://github.com/rzvdaniel/automaton/releases/latest/download/install.sh | sh ``` On Windows (PowerShell): `irm https://github.com/rzvdaniel/automaton/releases/latest/download/install.ps1 | iex`. The script puts `automaton` in `~/.local/bin` (Windows: `%LOCALAPPDATA%\Automaton\bin`) and checks its SHA-256. Set `AUTOMATON_VERSION=server-v` to pick a release. Already have the .NET 10 SDK? `dotnet tool install -g Automaton.Cli` installs the same CLI. ```bash automaton --help automaton login --server https://api.your-domain.com ``` `automaton login` prints the approval URL and a code, then notices your approval within about five seconds. On a server without a browser it prints the URL; open it on another device. Use `--device` to skip the browser attempt. ## Log in without a browser On a server over SSH, or for an AI agent, use one of these. Neither needs a browser on the machine that runs the CLI. **Device approval.** Run `automaton login --device --server https://api.your-domain.com`. It prints an approval URL and a code, and waits, so run it in the background and keep its output in a private file (it contains the code). Approve it from any signed-in session: open the URL in a browser on another device, or approve it from a terminal with an administrator or user token. The email and password are the account's own sign-in; for the first administrator that is the email chosen at [setup](./first-admin). ```bash API=https://api.your-domain.com OUT=$(umask 077; mktemp) # private file (mode 600) for the --device output automaton login --device --server "$API" > "$OUT" 2>&1 & for _ in $(seq 20); do grep -q 'cli/authorize' "$OUT" && break; sleep 1; done # wait for the URL # The output has a line: https://studio.../cli/authorize?requestId=REQUEST_ID&code=USER_CODE URL=$(grep -o 'https://[^ ]*cli/authorize?[^ ]*' "$OUT" | head -1) REQUEST_ID=${URL#*requestId=}; REQUEST_ID=${REQUEST_ID%%&*} USER_CODE=${URL#*code=}; USER_CODE=${USER_CODE%%&*} read -rp "Email: " EMAIL; read -rsp "Password: " PASSWORD; echo # no terminal: PASSWORD=$(< /root/admin-password), a 0600 file; needs jq TOKEN=$(jq -n --arg e "$EMAIL" --arg p "$PASSWORD" '{email:$e,password:$p}' | curl -s -X POST "$API/api/Account/LoginUser" -H 'Content-Type: application/json' --data-binary @- | jq -er .accessToken) curl -s -o /dev/null -w '%{http_code}\n' -X POST "$API/api/cli-auth/requests/$REQUEST_ID/approve" \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d "$(jq -n --arg c "$USER_CODE" '{userCode:$c}')" unset PASSWORD TOKEN wait; tail -n 3 "$OUT"; rm -f "$OUT" # "Signed in as ..." once approved. If the code was refused, stop the wait with Ctrl+C or `kill %1`. ``` An agent that just created the administrator skips the two `read` prompts: `EMAIL` and `PASSWORD` are already set in the same shell; it runs the script, then `unset`s them. Never print them. The code is letters, digits and a dash (for example `ABCD-1234`), so it needs no URL decoding. `200` means approved; `401` means the sign-in failed, and `403` that the account's email is not confirmed. The waiting CLI notices within about five seconds. The `--device` output contains the approval code: do not share it. Codes expire after a few minutes, so approve soon after `login` prints it. **API key.** Create a key in Studio (**API Keys**) or with `POST /api/ApiKeys` (`{"name": "my-cli"}`, bearer token as above; the response `apiKey` is shown once). Then: ```bash automaton login --server https://api.your-domain.com --key ``` `--key` with no value asks for the key at a hidden prompt. Do not put the key in a command argument, a log or an AI conversation; for unattended jobs set `AUTOMATON_SERVER` and `AUTOMATON_API_KEY` in the process environment instead. For unattended use, configure `AUTOMATON_API_KEY` in the process environment and use the command's non-interactive option where available. Do not paste keys into an AI conversation. ## Install a Runner `automaton runners upgrade RUNNER [--wait]` upgrades an online Runner to the server's current image (non-zero exit if it rolled back); `automaton server update --runners` (it implies `--wait`) names every Runner with its state (current or on an old image) and upgrades the old ones; `--wait` alone only lists them with the upgrade command. `automaton runners install --name NAME --ssh user@host | --local | --print-command [--yes] [--project NAME]`. Steps, options and errors: [Add a Runner](./deploy-runner). ## Several servers Log in to each server once. The CLI stores each server and its key together in a named profile: ```bash automaton login --server https://api.first.example --profile first automaton login --server https://api.second.example --profile second automaton profiles automaton profiles use first automaton --profile second whoami automaton logout --profile second ``` Without `--profile`, login names a profile after the server host, dropping an initial `api.`. `automaton profiles` shows the server, current selection and a key fingerprint, never the key. `automaton whoami` shows the chosen profile and server. For any command, `--profile` takes precedence over `AUTOMATON_PROFILE`; otherwise an app folder chooses the profile matching the server recorded in its selected `.automaton` environment, then falls back to the current profile outside an app folder. Existing project-local credentials for that same server remain usable. If an app's server has no matching profile or local credential, log in to that server before deploying. `automaton logout` removes only one profile and leaves other logins intact: the `--profile` or `AUTOMATON_PROFILE` selection, else (inside an app folder) the profile matching the app's server, else the current profile. After the current profile is removed no profile is current until you run `automaton profiles use NAME` or log in again; the CLI never switches silently. `--profile` (or `AUTOMATON_PROFILE`) inside an app folder whose server differs from the profile's is an error ("This app is on ..."). It cannot be combined with `AUTOMATON_SERVER` or `AUTOMATON_API_KEY`; without a profile those variables still override stored credentials. A project-local key must be stored with its server (`automaton login --local --server URL`); a key without a server is refused. `automaton config --global --server URL` saves (or updates) the profile named after that server instead of a single global server. The shared file `~/.automaton/config.json` changed format (version 2, with a `profiles` map and the `current` profile). The CLI upgrades an older file on first use, keeping its credentials. Older CLI versions cannot read the new format, so update them everywhere you use the file: `dotnet tool update -g Automaton.Cli`. Set `AUTOMATON_CONFIG_DIR` to keep the file in another directory (`$AUTOMATON_CONFIG_DIR/config.json`); the CLI test suite uses it so tests never touch your real login. ## Create or link ```bash automaton new "My App" automaton link ``` `automaton new` creates a valid local `automaton.json`, creates a remote app, and records its ID in the selected local profile. It also syncs the generated AI guidance. `automaton link` requires an existing valid `automaton.json` and records the remote app ID in the selected profile without downloading or changing local source or guidance files. After linking, deploy the local source with `automaton deploy`. `automaton pull ` creates a new local project from portable remote source. App IDs work for accessible apps; exact public slugs are resolved from the apps visible to your account, so project apps may require an app ID. Pulling into an existing linked project warns before overwriting matching files and stages downloads before exposing them locally. If remote creation fails after the local file is written, run: ```bash automaton validate automaton create automaton deploy ``` ## Configure and deploy Declare the generic `application` contract in `automaton.json`. It supports an optional build command, a runtime command, an HTTP health path, durable storage, working directories, environment values, and timeouts. ```bash automaton validate automaton deploy ``` The Runner performs the configured build and starts the configured runtime. The deployment operation is asynchronous. Wait for `Succeeded`, then verify the deployed URL. ## Other commands ```bash automaton sync-ai automaton pull automaton apps # your apps: ID, name, Runner, status (Paused when paused), public URL automaton installation-status # server version, counts, last backup automaton runners automaton project runners # list, add or remove a project's Runners automaton project list automaton publish --yes --slug NAME automaton share status # who can open the app: me, people, a team, server or internet (same choices as the Share dialog) automaton share set server # or: me, people, team --team NAME, internet --slug NAME [--list|--no-list] (store listing text: automaton listing); add/remove EMAIL manage "people" automaton pause # stop the linked app, switch its schedules off, show a paused page (also: automaton apps pause APP) automaton resume # start it again and turn its schedules back on (also: automaton apps resume APP); automaton deploy --resume resumes first automaton delete --yes # permanently delete the linked remote app automaton apps delete --yes # delete any app you may delete, no linked folder needed (for example a catalog-installed app) automaton listing show automaton catalog init my-apps # files for your own apps repository (writes only; no git, no GitHub) automaton catalog build # generate catalog.json from catalog-apps.json automaton catalog list # apps in the server's catalog: id, source, permissions automaton catalog install ID --name NAME --runner RUNNER --setting NAME=VALUE --secrets-file FILE --wait ``` `sync-ai` restores the canonical local guidance files. `pull` downloads app source. `runners` lists available Runners. `project create|list|add-app` manages projects with an API key; adding an app to a project lets it use the project secret vault. To move a deployed Live environment to another Runner, run `automaton set-runner --cleanup-runtime`, then `automaton deploy`; a published static site stays published. `publish` makes the latest deployment public and requires explicit user approval. `listing` sets the app's marketplace summary, description, category, tags, cover and screenshots; see [Deploy and publish an app](./deploy#marketplace-listing). `catalog list` and `catalog install` do from the terminal what **Apps → Catalog → Install** does: `list` shows every entry with its id, source and requested permissions (`--search`, `--category`, `--source`, `--installed`, `--json`); `install ` prints the pinned commit and the permissions the app requests, creates the app, saves its settings and secrets and deploys it (`--wait` reports whether the deployment succeeded). Give settings as `--setting NAME=VALUE` (repeat the option). Secrets are never arguments: pass `NAME=VALUE` lines in a file only you can read (`--secrets-file`) or on standard input (`--secrets-stdin`), or run the command in a terminal and type each one at a hidden prompt. The command stops before creating anything when a required setting or secret is missing, and says which. Permissions still need a system administrator's approval in Studio. If two catalog sources have the same id, add `--source `. `catalog init` and `catalog build` create and maintain an apps repository that servers can use as a catalog source: see [Publish your own app catalog](./apps-repository). System administrators of a self-hosted server can update it from the CLI: `automaton server update --wait` installs the latest release (or `--version server-vX.Y.Z`), then checks `/health` and the installed version and exits nonzero on failure with the log tail and recovery backup (an older release needs `--allow-downgrade`); `automaton server update-status` shows the last run. See [Update an installed server](./update-server). The optional positional argument to `automaton deploy` selects a remote deployment target. Its `--env ` option instead selects a local project profile for credentials and app linkage. Publish when it is within the requested app delivery or an existing standing authorization. Do not ask for separate publication approval again; honor any user restriction to keep the app private or unpublished. For authorized non-interactive publication, use `automaton publish --yes` and verify the published URL. --- Source: https://automaton.monster/docs/automaton-json # automaton.json reference `automaton.json` lives at the project root. It links the project to an Automaton app and declares the application contract. ## Generic application shape ```json { "appId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "My App", "summary": "One line for the store card", "application": { "build": { "image": "runtime-image:tag", "command": { "executable": "build-tool", "arguments": ["build"], "workingDirectory": ".", "environment": {} }, "outputDirectory": "dist", "timeoutSeconds": 900 }, "runtime": { "image": "runtime-image:tag", "command": { "executable": "app-server", "arguments": [], "workingDirectory": ".", "environment": {} }, "port": 8080 }, "health": { "path": "/health", "startupTimeoutSeconds": 60 }, "storage": { "dataDirectory": "data", "mounts": [] } } } ``` Automaton does not prescribe a language, framework, package manager, source layout, build system, or application architecture. ## Fields | Field | Meaning | |---|---| | `appId` | Remote app ID after linking. | | `name` | App display name. | | `server` | Optional configured Automaton server. | | `application.build.command` | Optional executable, arguments, working directory, environment, and shell setting. | | `application.build.image` | Optional OCI image used to execute the build command; defaults to `runtime.image`. | | `application.build.outputDirectory` | Optional directory containing the build artifact. | | `application.build.singlePageApp` | Static sites only, default `false`. A missing path answers 404. Set `true` for a single-page app whose router needs unknown paths to get `index.html` (200). Sites published by an older release lose the fallback when the Runner next rewrites its configuration: declare the field and redeploy them. | | `application.build.timeoutSeconds` | Maximum build duration. | | `application.runtime.command` | Executable and arguments for the long-running app process. | | `application.runtime.image` | OCI image that executes the runtime command; required for new container deployments when `runtime.command` is present. Historical stored contracts remain readable until redeployed with an image. | | `application.runtime.user` | Optional numeric UID or UID:GID for the runtime process. Keep the identity stable when the app must update files it created in persistent storage. | | `application.runtime.port` | Port requested by the app contract. | | `application.health.path` | Optional HTTP readiness path. | | `application.health.startupTimeoutSeconds` | Maximum startup duration. | | `application.storage.dataDirectory` | Durable app data directory. | | `application.kind` | Optional, `web` or `api`, for a running app. Missing means web. Static sites and actions-only apps are known from the rest of the contract. | | `application.description` | Optional one line about the app, at most 200 characters. Each Action can add its own one-line `metadata.description` in `action.yaml` (same limit). | | `application.pages` | Optional key paths of a web app, up to 10 entries such as `{ "path": "/", "label": "Home" }` (path starts with `/`, label up to 40 characters). | | `application.openApi` | Optional path of an API app's OpenAPI document, such as `/openapi.json`. Studio lists the endpoints from it and shows the API reference button. | | `application.openPaths` | Optional paths of the private address that answer without Automaton sign-in, for webhooks and callbacks. See below. | | `application.publicAccess` | Optional hint `{ "level": "required" or "recommended", "reason": "..." }` for an app that outside callers reach (check-in URLs, a tracking script). New apps are private, so the install page shows it before installing and says to choose "Anyone on the internet" under "Who can open it". It changes nothing by itself. | The Runner supplies the effective port in `AUTOMATON_PORT` and the durable data path in `AUTOMATON_APP_DATA_DIR`. Every runtime and Action also gets `AUTOMATON_API_URL`, the server's public API base URL. A configured health path is ready only after a 2xx HTTP response. On managed Linux Runners, the data directory and declared storage mounts are prepared as writable directories for the app container. File ownership and permissions still follow the UID that creates each file. Build and runtime artifacts can be replaced. Commands use structured arguments by default. Set `useShell` only when needed. Keep managed paths inside the project workspace. Run `automaton validate`, then `automaton deploy`. Deployment is asynchronous. Wait for `Succeeded` before treating the app as deployed. Publishing is a separate action and requires explicit approval. ## Open paths: webhooks and callbacks An app's private address (`app-.apps.`) asks every visitor to sign in to Automaton. An outside service that calls the app (a payment webhook, an OAuth callback) cannot sign in, so declare the paths it calls: ```json "application": { "openPaths": ["/hooks/*", "/oauth/callback"] } ``` `/oauth/callback` opens exactly that path; `/hooks/*` opens `/hooks/` and everything below it. Name at least one folder or file (`/` and `/*` are refused) using only letters, digits and `/ - _ . ~`, at most 20 paths. Requests with `..` or encoded slashes never match an open path. Those paths answer anyone who knows the address, so the app must check the caller itself (a signature or secret). Everything else stays behind sign-in, and publishing the app still makes the whole public address open. The routes update with the next deployment. ## App secrets Keep credentials out of `automaton.json` and source files. Set them as app secrets in Studio under **App → Settings → Secrets**: a name such as `S3_SECRET_KEY` (capital letters, digits and underscores) and its value. Values are stored encrypted, are never shown again after saving, and are passed as environment variables to each **Action** run and to the **running app** (API or server-rendered site) whenever it starts, including after a server restart. Action logs and results have them replaced by `[REDACTED]`. Saving an existing name replaces its value; a running app picks up the change on its next start, so press **Restart** on the app's Overview page (or in the Settings card) or redeploy it with `automaton deploy`. Read secrets from the environment in code (for example `process.env.STRIPE_KEY` or `os.environ["STRIPE_KEY"]`); never write them into source or build output. Static sites cannot use secrets, because their files are sent to the browser. Compose apps do not receive app secrets yet. ### Declared secrets and shared vaults Declare the secrets an app needs in `application.secrets`: ```json "secrets": [ { "name": "STRIPE_KEY", "description": "Stripe secret key for payments" }, { "name": "PAYMENTS_KEY", "from": "project:STRIPE_LIVE_KEY" } ] ``` An app receives every app secret, and for each declared name it does not have itself, a value from a shared vault: 1. The **project vault**, when the app belongs to exactly one project. Project administrators and developers set its secrets; every app of the project that declares the name receives it. 2. The **user vault** of the app's owner, only for personal apps (in no project, with one owner), so nobody else who can change an app's code can read a person's own secrets. `"optional": true` marks a secret the app works without: the install page and `automaton catalog install` let it stay empty, and `automaton secrets status` does not report it as missing. `from` takes the value from a vault under another name (`project:NAME` or `user:NAME`). Vault secrets an app does not declare never reach it. A changed vault secret applies to every app using it at its next Action run or start. From the app's folder: | Command | Does | |---|---| | `automaton secrets status` | Lists declared secrets: present or missing, and which scope provides each. | | `automaton secrets set NAME [--project\|--user] [--description TEXT]` | Saves a value read from standard input, or prompts without echo. Without a flag, it is an app secret. | | `automaton secrets list [--project\|--user]` | Lists names, versions and descriptions, never values. | | `automaton secrets delete NAME [--project\|--user]` | Deletes a secret. | | `automaton project create NAME` | Creates a project; you become its administrator. | | `automaton project list` | Lists the projects you belong to. | | `automaton project add-app [PROJECT]` | Adds the current app to a project (name or id; optional when you are in one project). Needed before `secrets set --project`. | `automaton validate` and `automaton deploy` warn about declared secrets without a value (`validate --strict` fails). ### Platform permissions `application.permissions` requests platform access for the app, for example `"permissions": ["backups:read", "server:read"]`. `notify:email` lets it email the app's owners, the server's administrators, its project's members and addresses on the administrators' allowed list through the server's SMTP, at `POST /api/notify/email` (10 emails per hour; see [Secrets and permissions](secrets-and-permissions.md)). `backups:read` lets the app list and download the encrypted server backups (`GET /api/install/backups`). `server:read` lets it read the server's health, read-only, at `GET /api/server/health`: server and Runner versions and whether a newer release exists, OS name and version, uptime, CPU, load, memory and disk, Docker disk use, pending and security OS updates, restart required, certificate expiry and the last backup. The Machine Management Runner measures the host figures every 5 minutes; apps get no shell or file access. An app with both permissions uses one key for both. A system administrator of the server approves each request in Studio; the app then receives a scoped key as `APP_AUTOMATON_API_KEY` at its next Action run or start. `automaton permissions status` shows each request (waiting, approved, rejected or revoked) and the approval link to send to an administrator, and `automaton validate` and `automaton deploy` warn while a request waits. An app cannot approve its own requests, and neither can an API key. ## Install manifest: settings and default schedules Anyone can install a published app that allows forking from Studio. The installer is asked for what `automaton.json` declares, so an app needs no README steps: ```json "settings": [ { "name": "S3_REGION", "description": "Object storage region", "default": "us-east-1" }, { "name": "S3_PREFIX", "description": "Folder for this install's copies", "required": true } ], "schedules": [ { "name": "Nightly upload", "action": "upload", "cron": "30 3 * * *", "timeZone": "Etc/UTC", "input": {} } ] ``` - **`settings`** are plain, non-secret values (`name` as for secrets, optional `description`, `default`, `required`). The installer fills them in; they reach Actions and the running app as environment variables, like secrets, and a `default` applies until a value is saved. Unlike secrets, their values are not redacted from Action output, but they are write-only in Studio like secrets. Anything sensitive belongs in `secrets`, not here. - **`schedules`** are offered to the installer, who accepts or skips each; accepted ones become ordinary Action schedules of the installed app. `action` is an Action key; give exactly one of `cron` (five fields) or `intervalSeconds`; `input` is an object. - **`secrets`** and **`permissions`** work as above. Infrastructure permissions still need a system administrator's approval. Every installed app also receives `AUTOMATON_APP_ID`, its own app ID. Use it (or the install's name) in a storage prefix so two installs of one app never share data. `automaton validate` checks the shape of all of these (names, cron fields, one of cron or interval); a cron value or interval the server rejects only shows when the schedule is created. **Installing.** On a published app's store page, **Install on your server** opens Studio's install page (`/apps/install/`; the app must allow forking). It asks for a name, the settings and secrets, shows the permissions (a system administrator can approve them right there; otherwise one must approve them on the app page) and lists the schedules. Install creates your own copy, saves the values, deploys it and creates the schedules you kept. Installing the same app again makes an independent copy with its own name, values, approvals and schedules. ## Create or link Use `automaton new ""` for a new remote app. Use `automaton link ` for an existing remote app. Both commands refresh `AGENTS.md` and the local app skill. --- Source: https://automaton.monster/docs/troubleshooting # Troubleshooting Common problems and how to fix them. For install problems see [Install with your AI agent](./install-with-ai) and [Install the server](./install-server#common-errors). --- ## App deployed but not running **Symptom:** `automaton deploy` printed `Deploy complete`, but the app does not answer or `automaton apps` does not say `Running`. - **Not finished yet** - `Deploy complete` only means the package was applied. Wait for `Running` in `automaton apps`. - **No Runner online** - Check `automaton runners` (or **Runners** in Studio). Add or start a Runner, then deploy again. - **Build or startup error** - Read `automaton logs` for the failing command and file or line. - **Health check failure** - The app must listen on `AUTOMATON_PORT` and answer the health path in `automaton.json`. - **Request to server code fails** - Deploy the changed code first; then check the route, method, body and the response status in the logs. --- ## Runner shows as Offline or Unknown **Symptom:** `automaton runners` or the Runners page shows a Runner as offline after it started. - **Port 5671** - The Runner connects outbound to the `api` host on port 5671 (TLS). It must be reachable from the Runner machine; the `api` host must not be proxied. - **Runner stopped** - Check `docker ps` on the Runner host and the container's logs for startup errors. - **Enrollment expired** - Run `automaton runners install` again; each command is single use. --- ## AI chat does not respond (App Editor) **Symptom:** Typing a prompt in the App Editor AI chat produces no response. **Fix:** Go to **Settings → AI Keys** and confirm that at least one valid AI provider key is saved. The editor will not make any AI requests until a key is configured. --- ## `automaton deploy` fails with authentication error **Symptom:** `automaton deploy` exits with a 401 or 403 error. - Sign in again: `automaton login --server ` (on a server without a browser, `automaton login --device`, see [Log in without a browser](./automaton-cli#log-in-without-a-browser)). - Check which server is active with `automaton profiles` and `automaton whoami`. - A user without permission on the app gets 403; ask the app's owner. --- ## The app loads but shows blank content **Symptom:** The app loads without an error but the page is empty. **Causes and fixes:** - **Entry point mismatch** — Confirm that the public target's configured output directory contains the entry file expected by the runtime. - **Build step not run** — Run the configured build command before deploying, or put that command and its output directory in `automaton.json` so the CLI can do it automatically. - **JavaScript error on load** — Open the browser console in the preview and look for errors. --- Source: https://automaton.monster/docs/limitations # Current limitations Automaton is early. These are the specific things to know before you rely on it. ## Platform - **x86_64 Linux only.** Ubuntu and Debian 12 are supported (apt, systemd, Docker Engine) for the server and the Runner host. Other x86_64 Linux with systemd and Docker may work but is untested, and you install Docker yourself. ARM and fully offline or private-only installs are not supported yet. - **Public DNS and HTTPS.** The server needs two DNS records (`example.com` and `*.example.com`, DNS only) and public ports 80, 443 and 5671. - **A Runner is the agent you install on a server to run apps.** With one Runner, `automaton deploy` uses it automatically; with several you pick one per app. There is no automatic balancing. - **No high availability.** The API, database and each Runner run on single machines. A machine that goes down takes its apps with it until you restore it. - **No autoscaling or cross-region failover.** Each app runs where it is assigned. - **Open streams and WebSockets are capped per app.** Server-Sent Events and WebSockets work, but one app holds at most 20 at a time and an idle one closes after five minutes. - **Routing at large scale is unvalidated.** The proxy configuration holds per-app rules and is regenerated on change; behavior with thousands of apps has not been measured (see the [self-hosted core notes](https://github.com/rzvdaniel/automaton/blob/main/docs/roadmap/self-hosted-core.md)). ## Operations - **Updates cause brief interruptions.** An update replaces the API, Worker, Studio and the app store; they can be unavailable for a short time. - **Updates are not fully automatic.** Infrastructure changes and Runner-machine upgrades are separate steps you run. - **Backups need an off-site copy.** Nightly backups of the database and keys (not your apps' data files) are kept for 7 days on the server. An [off-site backup app](./backup-restore) is available from the catalog, but you must install and configure it or arrange your own off-site copies. ## Security and teams - **Built for people you trust.** Public multi-tenant hosting is out of scope. Two-user isolation and restart checks passed on the test server ([#521](https://github.com/rzvdaniel/automaton/issues/521)); that does not make the platform suitable for hosting strangers' code. - **No self sign-up.** Users are added by an administrator. - **The Runner is trusted.** The Runner controls the machine's Docker, so treat Runner machines as part of your trusted base. See the [security overview](./security). ## Tooling - **The standalone CLI needs no .NET.** Only the NuGet tool (`dotnet tool install`) needs the .NET 10 SDK or later; standalone builds cover Linux x64/arm64, macOS x64/arm64 and Windows x64. We update this page as these are fixed. See the [roadmap](https://github.com/rzvdaniel/automaton/blob/main/docs/roadmap/README.md) for what is planned. --- Source: https://automaton.monster/docs/security # Security overview How an Automaton installation protects apps, secrets and administrator control today. This page describes current behaviour, not plans. To report a problem, see [SECURITY.md](https://github.com/rzvdaniel/automaton/blob/main/SECURITY.md). ## Who runs what - **Your server** runs the API, Worker, Studio and app store in containers behind Traefik with HTTPS. You install and update it; the Automaton project hosts nothing for you. - **Self sign-up is disabled.** Accounts are created or invited by an administrator. - **Runners** are trusted management services, one per machine. A Runner connects to the server's message broker over TLS (port 5671) with its own credential and receives work only for the deployment targets assigned to it. ## How apps are isolated User code does not run inside the Runner process. APIs, server-rendered sites, Actions and builds run in separate Docker containers that the Runner creates with: - all Linux capabilities dropped and `no-new-privileges`; - the CPU, memory and process limits the server configures; - the deployed app files mounted read-only, plus a per-app data directory for persistent data; - for Actions and builds, a non-root user; Actions also get a read-only root filesystem. Generic deployments do not accept host mounts, privileged containers, host networking or the Docker socket. Static sites share one Nginx serving container that serves each app's published files. Containers are a process boundary, not a virtual machine. On a shared machine they suit people who trust each other (one person, a team, an organisation), not mutually untrusted tenants. Put workloads you do not trust on a separate machine with its own Runner. ## Secrets - App, project and personal secrets are stored encrypted on the server (ASP.NET Core Data Protection). - Values are write-only: Studio, the API and the CLI never return a value after it is saved. `automaton secrets set` reads the value from standard input so it stays out of shell history and AI chat. - Secrets reach an app's Actions and runtime containers as environment variables. A Runner fetches them with its own credential, only for targets assigned to it. Secret values are redacted from Action logs and results. - Static sites cannot use secrets, because everything they ship reaches the browser. ## App permissions An app that needs platform access declares permissions in `automaton.json` (for example `backups:read` the read-only `server:read`, or `notify:email`, which only emails the server's own users and an administrator-managed allowed list). Nothing is granted until a system administrator approves the request in Studio; the platform then issues a scoped key and stores it as an app secret. The API rejects calls outside the approved permissions, and revoking the approval stops the next call. ## Audit log Secret changes and uses (names, scopes and versions, never values) are recorded in the platform audit log and kept for 180 days; app permission requests, approvals and revocations are recorded there too. App owners see their app's history under **Secrets → Show history**; administrators see all events under **Admin → Audit Events**. ## Out of scope - Public multi-tenant hosting: letting strangers run code on a shared machine. - Protection against someone with root on your server or Runner machines, or with an administrator account. - Your hosting provider, DNS and domain, and the apps you deploy (their code, dependencies and authentication). - Off-server backups: nightly backups stay on the server; copies elsewhere are yours to arrange (see [backups and restore](./backup-restore)). --- # Automaton MCP MCP is optional. Use it to inspect accessible Automaton apps and source files when those tools are available. Use the CLI for local authentication, creating or linking a project, validation, deployment, and publishing. Do not ask for MCP setup during a normal local app workflow. For an existing app, inspect its files with MCP, edit the local source, and use the CLI for delivery. Publishing remains separate and requires explicit approval. --- # Automaton Actions Use an Action for finite work such as an import, export, report, notification, maintenance task, or job. An Action is not an HTTP application runtime. The manifest lives at: ```text actions/{action-key}/action.yaml ``` The manifest selects the Action entrypoint and its declared settings. Keep the entrypoint inside its Action directory. An Action receives one UTF-8 JSON value on standard input, writes one UTF-8 JSON result to standard output, writes operational logs to standard error, and uses a zero exit code for success. It must not write secrets to output or logs. Read credentials such as API keys from environment variables, never from source files. Declare each one the app needs in `automaton.json` under `application.secrets`, for example `"secrets": [{ "name": "STRIPE_KEY", "description": "Stripe secret key" }]`. Each Action run and the running app receive them as environment variables, and their values are removed from Action logs and results. Run `automaton secrets status` to see which declared secrets are missing. Never ask for a secret value in chat and never put one in a command argument. Ask the user to run `automaton secrets set NAME` themselves (it prompts without echo), or pipe a value that is already in an environment variable: `printf '%s' "$VALUE" | automaton secrets set NAME`. Add `--project` to share it with every app in the project, or `--user` for all of the user's personal apps. An app joins a project with `automaton project add-app [PROJECT]`; create a project with `automaton project create NAME` and list yours with `automaton project list`. Platform access such as reading server backups is requested in `application.permissions` (for example `["backups:read"]`) and needs a system administrator's approval in Studio; no agent or API key can approve it. After deploying, run `automaton permissions status` and give the user the approval link it prints. The app receives its key as `APP_AUTOMATON_API_KEY` on its next start after approval. Deploy Actions with `automaton deploy`. Validate with `automaton action validate `. Invoke a deployed Action with `automaton action run --input '{...}'`. --- # Automaton Studio app editor You are editing an Automaton app inside Studio. Studio file tools edit the app source directly. Inspect relevant files before changing them. The shared Automaton app contract defines build, runtime, health, and storage. Treat `automaton.json` as the source of truth. Source layout and technology remain application choices. Use `write_file` for a coordinated rewrite and `edit_file` for a small isolated change. Do not delete a file just to replace its contents. Keep user-facing updates concise. Use preview, runtime, and log tools to check the result. When source or its contract changes, deploy before claiming that the new runtime is active. Ask before publishing publicly. Use `list_automaton_guidance` and `get_automaton_guidance` when a focused Automaton rule is needed. Do not assume that optional guidance is already loaded.